From d5290e9834f370abfdc821605cf0aab81fd4bcff Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 21 Nov 2024 14:31:08 +0100 Subject: [PATCH 001/298] Detach v4.20.3 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 08d0d5938..48add76d4 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index c8a6df0ca..11ae5702a 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.20.3+dev-release" +const defaultVersion = "v4.20.3+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index faceb267a..c0b0ad73d 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From b66d768d64a780d4382740209a66f62cd3887a96 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 21 Nov 2024 15:50:09 +0100 Subject: [PATCH 002/298] chore: publish snap to the stable channel --- .goreleaser.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 32079274e..bfb604eea 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -161,8 +161,6 @@ snapcrafts: Usage: * `sudo snap install lego` * `sudo lego --email="you@example.com" --domains="example.com" --server=https://acme-staging-v02.api.letsencrypt.org/directory --http --http.port :8080 run - channel_templates: - - edge apps: lego: command: lego From 3fc9ae13e6ec4591e2d9caa2712277936a63fe85 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 21 Nov 2024 15:51:41 +0100 Subject: [PATCH 003/298] Prepare release v4.20.4 --- CHANGELOG.md | 4 ++++ acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f0f0ed27..e5be646f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [v4.20.4](https://github.com/go-acme/lego/releases/tag/v4.20.4) (2024-11-21) + +Publish the Snap to the Snapcraft stable channel. + ## [v4.20.3](https://github.com/go-acme/lego/releases/tag/v4.20.3) (2024-11-21) ### Fixed diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 48add76d4..33cfb2813 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.20.3" + ourUserAgent = "xenolf-acme/4.20.4" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 11ae5702a..09d6b7269 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.20.3+dev-detach" +const defaultVersion = "v4.20.4+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index c0b0ad73d..4b6d9f3ea 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.20.3" + ourUserAgent = "goacme-lego/4.20.4" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From 645169e3e53b5b5a8a945ec3f205dd681ec5f214 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 21 Nov 2024 15:51:56 +0100 Subject: [PATCH 004/298] Detach v4.20.4 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 33cfb2813..6755fe77a 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 09d6b7269..d449e84cf 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.20.4+dev-release" +const defaultVersion = "v4.20.4+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 4b6d9f3ea..987dfcad9 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From 6fccca616ae6057b1c1603f397457c343e49fd1e Mon Sep 17 00:00:00 2001 From: Cikaros <46598867+Cikaros@users.noreply.github.com> Date: Fri, 22 Nov 2024 00:28:38 +0800 Subject: [PATCH 005/298] =?UTF-8?q?Add=20DNS=20provider=20for=20Rainyun/?= =?UTF-8?q?=E9=9B=A8=E4=BA=91=20(#2354)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fernandez Ludovic --- README.md | 20 +- cmd/zz_gen_cmd_dnshelp.go | 21 ++ docs/content/dns/zz_gen_rainyun.md | 67 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/rainyun/internal/client.go | 182 ++++++++++++++++ providers/dns/rainyun/internal/client_test.go | 161 ++++++++++++++ .../rainyun/internal/fixtures/domains.json | 16 ++ .../dns/rainyun/internal/fixtures/error.json | 4 + .../rainyun/internal/fixtures/records.json | 24 +++ providers/dns/rainyun/internal/types.go | 37 ++++ providers/dns/rainyun/rainyun.go | 197 ++++++++++++++++++ providers/dns/rainyun/rainyun.toml | 22 ++ providers/dns/rainyun/rainyun_test.go | 113 ++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 14 files changed, 858 insertions(+), 11 deletions(-) create mode 100644 docs/content/dns/zz_gen_rainyun.md create mode 100644 providers/dns/rainyun/internal/client.go create mode 100644 providers/dns/rainyun/internal/client_test.go create mode 100644 providers/dns/rainyun/internal/fixtures/domains.json create mode 100644 providers/dns/rainyun/internal/fixtures/error.json create mode 100644 providers/dns/rainyun/internal/fixtures/records.json create mode 100644 providers/dns/rainyun/internal/types.go create mode 100644 providers/dns/rainyun/rainyun.go create mode 100644 providers/dns/rainyun/rainyun.toml create mode 100644 providers/dns/rainyun/rainyun_test.go diff --git a/README.md b/README.md index a430446c3..0e4cf617c 100644 --- a/README.md +++ b/README.md @@ -182,55 +182,55 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). PowerDNS Rackspace + Rain Yun/雨云 RcodeZero reg.ru Regfish - RFC2136 + RFC2136 RimuHosting Sakura Cloud Scaleway - Selectel + Selectel Selectel v2 SelfHost.(de|eu) Servercow - Shellrent + Shellrent Simply.com Sonic Stackpath - Technitium + Technitium Tencent Cloud DNS Timeweb Cloud TransIP - UKFast SafeDNS + UKFast SafeDNS Ultradns Variomedia VegaDNS - Vercel + Vercel Versio.[nl|eu|uk] VinylDNS VK Cloud - Volcano Engine/火山引擎 + Volcano Engine/火山引擎 Vscale Vultr Webnames - Websupport + Websupport WEDOS Yandex 360 Yandex Cloud - Yandex PDD + Yandex PDD Zone.ee Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 52eb0f11f..1a9d0fa55 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -115,6 +115,7 @@ func allDNSCodes() string { "plesk", "porkbun", "rackspace", + "rainyun", "rcodezero", "regfish", "regru", @@ -2357,6 +2358,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/rackspace`) + case "rainyun": + // generated from: providers/dns/rainyun/rainyun.toml + ew.writeln(`Configuration for Rain Yun/雨云.`) + ew.writeln(`Code: 'rainyun'`) + ew.writeln(`Since: 'v4.21.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "RAINYUN_API_KEY": API key`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "RAINYUN_HTTP_TIMEOUT": API request timeout`) + ew.writeln(` - "RAINYUN_POLLING_INTERVAL": Time between DNS propagation check`) + ew.writeln(` - "RAINYUN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "RAINYUN_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/rainyun`) + case "rcodezero": // generated from: providers/dns/rcodezero/rcodezero.toml ew.writeln(`Configuration for RcodeZero.`) diff --git a/docs/content/dns/zz_gen_rainyun.md b/docs/content/dns/zz_gen_rainyun.md new file mode 100644 index 000000000..c0ff646b8 --- /dev/null +++ b/docs/content/dns/zz_gen_rainyun.md @@ -0,0 +1,67 @@ +--- +title: "Rain Yun/雨云" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: rainyun +dnsprovider: + since: "v4.21.0" + code: "rainyun" + url: "https://www.rainyun.com" +--- + + + + + + +Configuration for [Rain Yun/雨云](https://www.rainyun.com). + + + + +- Code: `rainyun` +- Since: v4.21.0 + + +Here is an example bash command using the Rain Yun/雨云 provider: + +```bash +RAINYUN_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns rainyun -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `RAINYUN_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 | +|--------------------------------|-------------| +| `RAINYUN_HTTP_TIMEOUT` | API request timeout | +| `RAINYUN_POLLING_INTERVAL` | Time between DNS propagation check | +| `RAINYUN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `RAINYUN_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://www.apifox.cn/apidoc/shared-a4595cc8-44c5-4678-a2a3-eed7738dab03/api-151416609) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index ad95fe40d..dd086b795 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -142,7 +142,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, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, stackpath, technitium, 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, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, stackpath, technitium, 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/providers/dns/rainyun/internal/client.go b/providers/dns/rainyun/internal/client.go new file mode 100644 index 000000000..3d99bd9be --- /dev/null +++ b/providers/dns/rainyun/internal/client.go @@ -0,0 +1,182 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + querystring "github.com/google/go-querystring/query" +) + +const defaultBaseURL = "https://api.v2.rainyun.com/product/" + +// Client the Rain Yun API client. +type Client struct { + apiKey string + + baseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(apiKey string) (*Client, error) { + if apiKey == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + apiKey: apiKey, + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) AddRecord(ctx context.Context, domainID int, record Record) error { + endpoint := c.baseURL.JoinPath("domain", strconv.Itoa(domainID), "dns") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) DeleteRecord(ctx context.Context, domainID, recordID int) error { + endpoint := c.baseURL.JoinPath("domain", strconv.Itoa(domainID), "dns") + + values, err := querystring.Values(Record{ID: recordID}) + if err != nil { + return err + } + + endpoint.RawQuery = values.Encode() + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) ListRecords(ctx context.Context, domainID int) ([]Record, error) { + endpoint := c.baseURL.JoinPath("domain", strconv.Itoa(domainID), "dns") + + query := endpoint.Query() + query.Set("limit", "100") + query.Set("page_no", "1") + endpoint.RawQuery = query.Encode() + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var recordData APIResponse[Record] + err = c.do(req, &recordData) + if err != nil { + return nil, err + } + + return recordData.Data.Records, nil +} + +func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) { + endpoint := c.baseURL.JoinPath("domain") + + query := endpoint.Query() + query.Set("options", `{"columnFilters":{"domains.Domain":""},"sort":[],"page":1,"perPage":100}`) + endpoint.RawQuery = query.Encode() + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var domainData APIResponse[Domain] + + err = c.do(req, &domainData) + if err != nil { + return nil, err + } + + return domainData.Data.Records, nil +} + +func (c *Client) do(req *http.Request, result any) error { + req.Header.Add("x-api-key", c.apiKey) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/rainyun/internal/client_test.go b/providers/dns/rainyun/internal/client_test.go new file mode 100644 index 000000000..ee6477c0c --- /dev/null +++ b/providers/dns/rainyun/internal/client_test.go @@ -0,0 +1,161 @@ +package internal + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupTest(t *testing.T, pattern string, status int, filename string) *Client { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { + if filename == "" { + rw.WriteHeader(status) + return + } + + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + defer func() { _ = file.Close() }() + + rw.WriteHeader(status) + _, err = io.Copy(rw, file) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + }) + + client, err := NewClient("secret") + require.NoError(t, err) + + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) + + return client +} + +func TestClient_ListDomains(t *testing.T) { + client := setupTest(t, "GET /domain", http.StatusOK, "domains.json") + + domains, err := client.ListDomains(context.Background()) + require.NoError(t, err) + + expected := []Domain{ + {ID: 1, Domain: "example.com"}, + {ID: 2, Domain: "example.org"}, + } + + assert.Equal(t, expected, domains) +} + +func TestClient_ListDomains_error(t *testing.T) { + client := setupTest(t, "GET /domain", http.StatusForbidden, "error.json") + + _, err := client.ListDomains(context.Background()) + require.Error(t, err) + + assert.EqualError(t, err, "30039: 密钥认证错误或已失效") +} + +func TestClient_ListRecords(t *testing.T) { + client := setupTest(t, "GET /domain/123/dns", http.StatusOK, "records.json") + + records, err := client.ListRecords(context.Background(), 123) + require.NoError(t, err) + + expected := []Record{ + { + ID: 1, + Host: "_acme-challenge.foo.example.com", + Line: "DEFAULT", + TTL: 120, + Type: "TXT", + Value: "foo", + }, + { + ID: 2, + Host: "_acme-challenge.bar.example.com", + Line: "DEFAULT", + TTL: 300, + Type: "TXT", + Value: "bar", + }, + } + + assert.Equal(t, expected, records) +} + +func TestClient_ListRecords_error(t *testing.T) { + client := setupTest(t, "GET /domain/123/dns", http.StatusForbidden, "error.json") + + _, err := client.ListRecords(context.Background(), 123) + require.Error(t, err) + + assert.EqualError(t, err, "30039: 密钥认证错误或已失效") +} + +func TestClient_AddRecord(t *testing.T) { + client := setupTest(t, "POST /domain/123/dns", http.StatusOK, "") + + record := Record{ + Host: "_acme-challenge.foo.example.com", + Line: "DEFAULT", + TTL: 120, + Type: "TXT", + Value: "foo", + } + + err := client.AddRecord(context.Background(), 123, record) + require.NoError(t, err) +} + +func TestClient_AddRecord_error(t *testing.T) { + client := setupTest(t, "POST /domain/123/dns", http.StatusForbidden, "error.json") + + record := Record{ + Host: "_acme-challenge.foo.example.com", + Line: "DEFAULT", + TTL: 120, + Type: "TXT", + Value: "foo", + } + + err := client.AddRecord(context.Background(), 123, record) + require.Error(t, err) + + assert.EqualError(t, err, "30039: 密钥认证错误或已失效") +} + +func TestClient_DeleteRecord(t *testing.T) { + client := setupTest(t, "DELETE /domain/123/dns", http.StatusOK, "") + + err := client.DeleteRecord(context.Background(), 123, 456) + require.NoError(t, err) +} + +func TestClient_DeleteRecord_error(t *testing.T) { + client := setupTest(t, "DELETE /domain/123/dns", http.StatusForbidden, "error.json") + + err := client.DeleteRecord(context.Background(), 123, 456) + require.Error(t, err) + + assert.EqualError(t, err, "30039: 密钥认证错误或已失效") +} diff --git a/providers/dns/rainyun/internal/fixtures/domains.json b/providers/dns/rainyun/internal/fixtures/domains.json new file mode 100644 index 000000000..930e4e189 --- /dev/null +++ b/providers/dns/rainyun/internal/fixtures/domains.json @@ -0,0 +1,16 @@ +{ + "code": 0, + "data": { + "TotalRecords": 2, + "Records": [ + { + "id": 1, + "domain": "example.com" + }, + { + "id": 2, + "domain": "example.org" + } + ] + } +} diff --git a/providers/dns/rainyun/internal/fixtures/error.json b/providers/dns/rainyun/internal/fixtures/error.json new file mode 100644 index 000000000..31e9f7138 --- /dev/null +++ b/providers/dns/rainyun/internal/fixtures/error.json @@ -0,0 +1,4 @@ +{ + "code": 30039, + "message": "密钥认证错误或已失效" +} diff --git a/providers/dns/rainyun/internal/fixtures/records.json b/providers/dns/rainyun/internal/fixtures/records.json new file mode 100644 index 000000000..d24c0c9ec --- /dev/null +++ b/providers/dns/rainyun/internal/fixtures/records.json @@ -0,0 +1,24 @@ +{ + "code": 0, + "data": { + "TotalRecords": 2, + "Records": [ + { + "record_id": 1, + "host": "_acme-challenge.foo.example.com", + "type": "TXT", + "TTL": 120, + "value": "foo", + "line": "DEFAULT" + }, + { + "record_id": 2, + "host": "_acme-challenge.bar.example.com", + "type": "TXT", + "TTL": 300, + "value": "bar", + "line": "DEFAULT" + } + ] + } +} diff --git a/providers/dns/rainyun/internal/types.go b/providers/dns/rainyun/internal/types.go new file mode 100644 index 000000000..8ce559112 --- /dev/null +++ b/providers/dns/rainyun/internal/types.go @@ -0,0 +1,37 @@ +package internal + +import "fmt" + +type APIError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +func (a *APIError) Error() string { + return fmt.Sprintf("%d: %s", a.Code, a.Message) +} + +type Record struct { + ID int `json:"record_id,omitempty" url:"record_id,omitempty"` + Host string `json:"host,omitempty" url:"host,omitempty"` + Priority int `json:"level,omitempty" url:"level,omitempty"` + Line string `json:"line,omitempty" url:"line,omitempty"` + TTL int `json:"ttl,omitempty" url:"ttl,omitempty"` + Type string `json:"type,omitempty" url:"type,omitempty"` + Value string `json:"value,omitempty" url:"value,omitempty"` +} + +type Domain struct { + ID int `json:"id,omitempty"` + Domain string `json:"domain,omitempty"` +} + +type APIResponse[T any] struct { + Code int `json:"code"` + Data *Data[T] `json:"data"` +} + +type Data[T any] struct { + TotalRecords int `json:"TotalRecords"` + Records []T `json:"Records"` +} diff --git a/providers/dns/rainyun/rainyun.go b/providers/dns/rainyun/rainyun.go new file mode 100644 index 000000000..43ef9cb1b --- /dev/null +++ b/providers/dns/rainyun/rainyun.go @@ -0,0 +1,197 @@ +// Package rainyun implements a DNS provider for solving the DNS-01 challenge using Rain Yun. +package rainyun + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/rainyun/internal" +) + +// Environment variables names. +const ( + envNamespace = "RAINYUN_" + + EnvAPIKey = envNamespace + "API_KEY" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// 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, 2*time.Minute), + 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for Rain Yun. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIKey) + if err != nil { + return nil, fmt.Errorf("rainyun: %w", err) + } + + config := NewDefaultConfig() + config.APIKey = values[EnvAPIKey] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Rain Yun. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("rainyun: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.APIKey) + if err != nil { + return nil, fmt.Errorf("rainyun: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("rainyun: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("rainyun: %w", err) + } + + domainID, err := d.findDomainID(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("rainyun: find domain ID: %w", err) + } + + record := internal.Record{ + Host: subDomain, + Priority: 10, + Line: "DEFAULT", + TTL: d.config.TTL, + Type: "TXT", + Value: info.Value, + } + + err = d.client.AddRecord(ctx, domainID, record) + if err != nil { + return fmt.Errorf("rainyun: add record: %w", err) + } + + 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) + + ctx := context.Background() + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("rainyun: could not find zone for domain %q: %w", domain, err) + } + + domainID, err := d.findDomainID(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("rainyun: find domain ID: %w", err) + } + + recordID, err := d.findRecordID(ctx, domainID, info) + if err != nil { + return fmt.Errorf("rainyun: find record ID: %w", err) + } + + err = d.client.DeleteRecord(ctx, domainID, recordID) + if err != nil { + return fmt.Errorf("rainyun: delete record: %w", err) + } + + 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 +} + +func (d *DNSProvider) findDomainID(ctx context.Context, domain string) (int, error) { + domains, err := d.client.ListDomains(ctx) + if err != nil { + return 0, err + } + + for _, dom := range domains { + if dom.Domain == domain { + return dom.ID, nil + } + } + + return 0, fmt.Errorf("domain not found: %s", domain) +} + +func (d *DNSProvider) findRecordID(ctx context.Context, domainID int, info dns01.ChallengeInfo) (int, error) { + records, err := d.client.ListRecords(ctx, domainID) + if err != nil { + return 0, fmt.Errorf("list records: %w", err) + } + + zone := dns01.UnFqdn(info.EffectiveFQDN) + + for _, record := range records { + if strings.HasPrefix(zone, record.Host) && record.Value == info.Value { + return record.ID, nil + } + } + + return 0, fmt.Errorf("record not found: domainID=%d, fqdn=%s", domainID, info.EffectiveFQDN) +} diff --git a/providers/dns/rainyun/rainyun.toml b/providers/dns/rainyun/rainyun.toml new file mode 100644 index 000000000..ea12b475f --- /dev/null +++ b/providers/dns/rainyun/rainyun.toml @@ -0,0 +1,22 @@ +Name = "Rain Yun/雨云" +Description = '''''' +URL = "https://www.rainyun.com" +Code = "rainyun" +Since = "v4.21.0" + +Example = ''' +RAINYUN_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns rainyun -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + RAINYUN_API_KEY = "API key" + [Configuration.Additional] + RAINYUN_POLLING_INTERVAL = "Time between DNS propagation check" + RAINYUN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" + RAINYUN_TTL = "The TTL of the TXT record used for the DNS challenge" + RAINYUN_HTTP_TIMEOUT = "API request timeout" + +[Links] + API = "https://www.apifox.cn/apidoc/shared-a4595cc8-44c5-4678-a2a3-eed7738dab03/api-151416609" diff --git a/providers/dns/rainyun/rainyun_test.go b/providers/dns/rainyun/rainyun_test.go new file mode 100644 index 000000000..d0048e5d0 --- /dev/null +++ b/providers/dns/rainyun/rainyun_test.go @@ -0,0 +1,113 @@ +package rainyun + +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: "rainyun: some credentials information are missing: RAINYUN_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: "rainyun: 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 3d9f4965d..701fc83ff 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -109,6 +109,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/plesk" "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/rainyun" "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" @@ -359,6 +360,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return porkbun.NewDNSProvider() case "rackspace": return rackspace.NewDNSProvider() + case "rainyun": + return rainyun.NewDNSProvider() case "rcodezero": return rcodezero.NewDNSProvider() case "regfish": From b34902160df8901fbccef71fe69d3ea92e5fe2cf Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 21 Nov 2024 17:47:07 +0100 Subject: [PATCH 006/298] =?UTF-8?q?Add=20DNS=20provider=20for=20West.cn/?= =?UTF-8?q?=E8=A5=BF=E9=83=A8=E6=95=B0=E7=A0=81=20(#2318)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- cmd/zz_gen_cmd_dnshelp.go | 22 ++ docs/content/dns/zz_gen_westcn.md | 69 ++++++ docs/data/zz_cli_help.toml | 2 +- go.mod | 2 +- providers/dns/westcn/internal/client.go | 211 +++++++++++++++++ providers/dns/westcn/internal/client_test.go | 215 ++++++++++++++++++ .../internal/fixtures/adddnsrecord.json | 7 + .../internal/fixtures/deldnsrecord.json | 4 + .../dns/westcn/internal/fixtures/error.json | 6 + providers/dns/westcn/internal/types.go | 28 +++ providers/dns/westcn/westcn.go | 169 ++++++++++++++ providers/dns/westcn/westcn.toml | 24 ++ providers/dns/westcn/westcn_test.go | 143 ++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 15 files changed, 905 insertions(+), 4 deletions(-) create mode 100644 docs/content/dns/zz_gen_westcn.md create mode 100644 providers/dns/westcn/internal/client.go create mode 100644 providers/dns/westcn/internal/client_test.go create mode 100644 providers/dns/westcn/internal/fixtures/adddnsrecord.json create mode 100644 providers/dns/westcn/internal/fixtures/deldnsrecord.json create mode 100644 providers/dns/westcn/internal/fixtures/error.json create mode 100644 providers/dns/westcn/internal/types.go create mode 100644 providers/dns/westcn/westcn.go create mode 100644 providers/dns/westcn/westcn.toml create mode 100644 providers/dns/westcn/westcn_test.go diff --git a/README.md b/README.md index 0e4cf617c..53e9e529e 100644 --- a/README.md +++ b/README.md @@ -224,13 +224,13 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Websupport WEDOS + West.cn/西部数码 Yandex 360 - Yandex Cloud + Yandex Cloud Yandex PDD Zone.ee Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 1a9d0fa55..b7f6e6c8c 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -150,6 +150,7 @@ func allDNSCodes() string { "webnames", "websupport", "wedos", + "westcn", "yandex", "yandex360", "yandexcloud", @@ -3113,6 +3114,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/wedos`) + case "westcn": + // generated from: providers/dns/westcn/westcn.toml + ew.writeln(`Configuration for West.cn/西部数码.`) + ew.writeln(`Code: 'westcn'`) + ew.writeln(`Since: 'v4.21.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "WESTCN_PASSWORD": API password`) + ew.writeln(` - "WESTCN_USERNAME": Username`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "WESTCN_HTTP_TIMEOUT": API request timeout`) + ew.writeln(` - "WESTCN_POLLING_INTERVAL": Time between DNS propagation check`) + ew.writeln(` - "WESTCN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "WESTCN_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/westcn`) + case "yandex": // generated from: providers/dns/yandex/yandex.toml ew.writeln(`Configuration for Yandex PDD.`) diff --git a/docs/content/dns/zz_gen_westcn.md b/docs/content/dns/zz_gen_westcn.md new file mode 100644 index 000000000..fdda3b246 --- /dev/null +++ b/docs/content/dns/zz_gen_westcn.md @@ -0,0 +1,69 @@ +--- +title: "West.cn/西部数码" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: westcn +dnsprovider: + since: "v4.21.0" + code: "westcn" + url: "https://www.west.cn" +--- + + + + + + +Configuration for [West.cn/西部数码](https://www.west.cn). + + + + +- Code: `westcn` +- Since: v4.21.0 + + +Here is an example bash command using the West.cn/西部数码 provider: + +```bash +WESTCN_USERNAME="xxx" \ +WESTCN_PASSWORD="yyy" \ +lego --email you@example.com --dns westcn -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `WESTCN_PASSWORD` | API password | +| `WESTCN_USERNAME` | Username | + +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 | +|--------------------------------|-------------| +| `WESTCN_HTTP_TIMEOUT` | API request timeout | +| `WESTCN_POLLING_INTERVAL` | Time between DNS propagation check | +| `WESTCN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `WESTCN_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://www.west.cn/CustomerCenter/doc/domain_v2.html) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index dd086b795..b6eec239b 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -142,7 +142,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, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, stackpath, technitium, 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, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/go.mod b/go.mod index ed000aac2..052b4dd29 100644 --- a/go.mod +++ b/go.mod @@ -83,6 +83,7 @@ require ( golang.org/x/crypto v0.28.0 golang.org/x/net v0.30.0 golang.org/x/oauth2 v0.23.0 + golang.org/x/text v0.19.0 golang.org/x/time v0.7.0 google.golang.org/api v0.204.0 gopkg.in/ns1/ns1-go.v2 v2.12.2 @@ -198,7 +199,6 @@ require ( golang.org/x/mod v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.25.0 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect diff --git a/providers/dns/westcn/internal/client.go b/providers/dns/westcn/internal/client.go new file mode 100644 index 000000000..4d967f5e1 --- /dev/null +++ b/providers/dns/westcn/internal/client.go @@ -0,0 +1,211 @@ +package internal + +import ( + "bytes" + "context" + "crypto/md5" + "encoding/hex" + "encoding/json" + "errors" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + querystring "github.com/google/go-querystring/query" + "github.com/nrdcg/mailinabox/errutils" + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/simplifiedchinese" + "golang.org/x/text/transform" +) + +const defaultBaseURL = "https://api.west.cn/api/v2" + +// Client the West.cn API client. +type Client struct { + username string + password string + + encoder *encoding.Encoder + + baseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(username, password string) (*Client, error) { + if username == "" || password == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + username: username, + password: password, + encoder: simplifiedchinese.GBK.NewEncoder(), + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +// AddRecord adds a record. +// https://www.west.cn/CustomerCenter/doc/domain_v2.html#37u3001u6dfbu52a0u57dfu540du89e3u67900a3ca20id3d37u3001u6dfbu52a0u57dfu540du89e3u67903e203ca3e +func (c *Client) AddRecord(ctx context.Context, record Record) (int, error) { + values, err := querystring.Values(record) + if err != nil { + return 0, err + } + + req, err := c.newRequest(ctx, "domain", "adddnsrecord", values) + if err != nil { + return 0, err + } + + results := &APIResponse[RecordID]{} + + err = c.do(req, results) + if err != nil { + return 0, err + } + + if results.Result != http.StatusOK { + return 0, results + } + + return results.Data.ID, nil +} + +// DeleteRecord deleted a record. +// https://www.west.cn/CustomerCenter/doc/domain_v2.html#39u3001u5220u9664u57dfu540du89e3u67900a3ca20id3d39u3001u5220u9664u57dfu540du89e3u67903e203ca3e +func (c *Client) DeleteRecord(ctx context.Context, domain string, recordID int) error { + values := url.Values{} + values.Set("domain", domain) + values.Set("id", strconv.Itoa(recordID)) + + req, err := c.newRequest(ctx, "domain", "deldnsrecord", values) + if err != nil { + return err + } + + results := &APIResponse[any]{} + + err = c.do(req, results) + if err != nil { + return err + } + + if results.Result != http.StatusOK { + return results + } + + return nil +} + +func (c *Client) newRequest(ctx context.Context, p, act string, form url.Values) (*http.Request, error) { + if form == nil { + form = url.Values{} + } + + c.sign(form, time.Now()) + + values, err := c.convertURLValues(form) + if err != nil { + return nil, err + } + + endpoint := c.baseURL.JoinPath(p, "/") + + query := endpoint.Query() + query.Set("act", act) + endpoint.RawQuery = query.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), strings.NewReader(values.Encode())) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + return req, nil +} + +func (c *Client) sign(form url.Values, now time.Time) { + timestamp := strconv.FormatInt(now.UnixMilli(), 10) + + sum := md5.Sum([]byte(c.username + c.password + timestamp)) + + form.Set("token", hex.EncodeToString(sum[:])) + form.Set("username", c.username) + form.Set("time", timestamp) +} + +func (c *Client) do(req *http.Request, result any) error { + resp, err := c.HTTPClient.Do(req) + if err != nil { + return err + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = gbkDecoder(raw).Decode(result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func (c *Client) convertURLValues(values url.Values) (url.Values, error) { + results := make(url.Values) + + for key, vs := range values { + encKey, err := c.encoder.String(key) + if err != nil { + return nil, err + } + + for _, value := range vs { + encValue, err := c.encoder.String(value) + if err != nil { + return nil, err + } + + results.Add(encKey, encValue) + } + } + + return results, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + result := &APIResponse[any]{} + + err := gbkDecoder(raw).Decode(result) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return result +} + +func gbkDecoder(raw []byte) *json.Decoder { + return json.NewDecoder(transform.NewReader(bytes.NewBuffer(raw), simplifiedchinese.GBK.NewDecoder())) +} diff --git a/providers/dns/westcn/internal/client_test.go b/providers/dns/westcn/internal/client_test.go new file mode 100644 index 000000000..ed0c7dc1a --- /dev/null +++ b/providers/dns/westcn/internal/client_test.go @@ -0,0 +1,215 @@ +package internal + +import ( + "context" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/text/encoding/simplifiedchinese" +) + +type formExpectation func(values url.Values) error + +func setupTest(t *testing.T, filename string, expectations ...formExpectation) *Client { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc("POST /", func(rw http.ResponseWriter, req *http.Request) { + err := req.ParseForm() + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + commons := []formExpectation{ + expectValue("username", "user"), + expectNotEmpty("time"), + expectNotEmpty("token"), + } + + for _, common := range commons { + err = common(req.Form) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + } + + for _, expectation := range expectations { + err = expectation(req.Form) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + } + + rw.Header().Set("Content-Type", "application/json; Charset=gb2312") + + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + defer func() { _ = file.Close() }() + + rw.WriteHeader(http.StatusOK) + _, err = io.Copy(rw, file) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + }) + + client, err := NewClient("user", "secret") + require.NoError(t, err) + + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) + + return client +} + +func expectValue(key, value string) formExpectation { + return func(values url.Values) error { + if values.Get(key) != value { + return fmt.Errorf("expected %s, got %s", value, values.Get(key)) + } + + return nil + } +} + +func expectNotEmpty(key string) formExpectation { + return func(values url.Values) error { + if values.Get(key) == "" { + return fmt.Errorf("%s missing", key) + } + + return nil + } +} + +func noop() formExpectation { + return func(_ url.Values) error { + return nil + } +} + +func TestClientAddRecord(t *testing.T) { + expectValue("act", "adddnsrecord") + + client := setupTest(t, "adddnsrecord.json", + expectValue("act", "adddnsrecord"), + expectValue("domain", "example.com"), + expectValue("host", "@"), + expectValue("type", "TXT"), + expectValue("value", "txtTXTtxt"), + expectValue("ttl", "60"), + ) + + record := Record{ + Domain: "example.com", + Host: "@", + Type: "TXT", + Value: "txtTXTtxt", + TTL: 60, + } + + id, err := client.AddRecord(context.Background(), record) + require.NoError(t, err) + + assert.Equal(t, 123456, id) +} + +func TestClientAddRecord_error(t *testing.T) { + client := setupTest(t, "error.json", noop()) + + record := Record{ + Domain: "example.com", + Host: "@", + Type: "TXT", + Value: "txtTXTtxt", + TTL: 60, + } + + _, err := client.AddRecord(context.Background(), record) + require.Error(t, err) + + require.EqualError(t, err, "10000: username,time,token必传 (500)") +} + +func TestClientDeleteRecord(t *testing.T) { + client := setupTest(t, "deldnsrecord.json", + expectValue("act", "deldnsrecord"), + expectValue("domain", "example.com"), + ) + + err := client.DeleteRecord(context.Background(), "example.com", 123) + require.NoError(t, err) +} + +func TestClientDeleteRecord_error(t *testing.T) { + client := setupTest(t, "error.json", noop()) + + err := client.DeleteRecord(context.Background(), "example.com", 123) + require.Error(t, err) + + require.EqualError(t, err, "10000: username,time,token必传 (500)") +} + +func Test_convertURLValues(t *testing.T) { + client, err := NewClient("user", "secret") + require.NoError(t, err) + + key := "你好abc" + value := "世界def" + + form := url.Values{} + form.Set(key, value) + + values, err := client.convertURLValues(form) + require.NoError(t, err) + + encoder := simplifiedchinese.GBK.NewEncoder() + + k, err := encoder.String(key) + require.NoError(t, err) + + v, err := encoder.String(value) + require.NoError(t, err) + + assert.Equal(t, v, values.Get(k)) + + decoder := simplifiedchinese.GBK.NewDecoder() + + decValue, err := decoder.String(values.Get(k)) + require.NoError(t, err) + + assert.Equal(t, value, decValue) +} + +func TestClient_sign(t *testing.T) { + client, err := NewClient("zhangsan", "5dh232kfg!*") + require.NoError(t, err) + + form := url.Values{} + + client.sign(form, time.UnixMilli(1554691950854)) + + assert.Equal(t, "zhangsan", form.Get("username")) + assert.Equal(t, "1554691950854", form.Get("time")) + assert.Equal(t, "f17581fb2535b2a7ee4468eb3f96a2a9", form.Get("token")) +} diff --git a/providers/dns/westcn/internal/fixtures/adddnsrecord.json b/providers/dns/westcn/internal/fixtures/adddnsrecord.json new file mode 100644 index 000000000..f1c135206 --- /dev/null +++ b/providers/dns/westcn/internal/fixtures/adddnsrecord.json @@ -0,0 +1,7 @@ +{ + "result": 200, + "clientid": "54880064508339547956", + "data": { + "id": 123456 + } +} diff --git a/providers/dns/westcn/internal/fixtures/deldnsrecord.json b/providers/dns/westcn/internal/fixtures/deldnsrecord.json new file mode 100644 index 000000000..e97e92f74 --- /dev/null +++ b/providers/dns/westcn/internal/fixtures/deldnsrecord.json @@ -0,0 +1,4 @@ +{ + "result": 200, + "clientid": "54880064508339547956" +} diff --git a/providers/dns/westcn/internal/fixtures/error.json b/providers/dns/westcn/internal/fixtures/error.json new file mode 100644 index 000000000..1c92415de --- /dev/null +++ b/providers/dns/westcn/internal/fixtures/error.json @@ -0,0 +1,6 @@ +{ + "result": 500, + "clientid": "54880064508339547956", + "msg": "username,time,tokenش", + "errcode": 10000 +} diff --git a/providers/dns/westcn/internal/types.go b/providers/dns/westcn/internal/types.go new file mode 100644 index 000000000..d8d66be2c --- /dev/null +++ b/providers/dns/westcn/internal/types.go @@ -0,0 +1,28 @@ +package internal + +import "fmt" + +type APIResponse[T any] struct { + Result int `json:"result,omitempty"` + ClientID string `json:"clientid,omitempty"` + Message string `json:"msg,omitempty"` + ErrorCode int `json:"errcode,omitempty"` + Data T `json:"data,omitempty"` +} + +func (a APIResponse[T]) Error() string { + return fmt.Sprintf("%d: %s (%d)", a.ErrorCode, a.Message, a.Result) +} + +type Record struct { + Domain string `url:"domain,omitempty"` + Host string `url:"host,omitempty"` + Type string `url:"type,omitempty"` + Value string `url:"value,omitempty"` + TTL int `url:"ttl,omitempty"` // 60~86400 seconds + Priority int `url:"level,omitempty"` +} + +type RecordID struct { + ID int `json:"id,omitempty"` +} diff --git a/providers/dns/westcn/westcn.go b/providers/dns/westcn/westcn.go new file mode 100644 index 000000000..37f357b70 --- /dev/null +++ b/providers/dns/westcn/westcn.go @@ -0,0 +1,169 @@ +// Package westcn implements a DNS provider for solving the DNS-01 challenge using West.cn/西部数码. +package westcn + +import ( + "context" + "errors" + "fmt" + "net/http" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/westcn/internal" +) + +// Environment variables names. +const ( + envNamespace = "WESTCN_" + + EnvUsername = envNamespace + "USERNAME" + EnvPassword = envNamespace + "PASSWORD" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + Username string + Password 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, 60), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client + + recordIDs map[string]int + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for West.cn/西部数码. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvUsername, EnvPassword) + if err != nil { + return nil, fmt.Errorf("westcn: %w", err) + } + + config := NewDefaultConfig() + config.Username = values[EnvUsername] + config.Password = values[EnvPassword] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for West.cn/西部数码. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("westcn: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.Username, config.Password) + if err != nil { + return nil, fmt.Errorf("westcn: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + 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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("westcn: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("westcn: %w", err) + } + + record := internal.Record{ + Domain: dns01.UnFqdn(authZone), + Host: subDomain, + Type: "TXT", + Value: info.Value, + TTL: d.config.TTL, + } + + recordID, err := d.client.AddRecord(context.Background(), record) + if err != nil { + return fmt.Errorf("westcn: add record: %w", err) + } + + d.recordIDsMu.Lock() + d.recordIDs[token] = recordID + 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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("westcn: could not find zone for domain %q: %w", domain, err) + } + + // gets the record's unique ID + d.recordIDsMu.Lock() + recordID, ok := d.recordIDs[token] + d.recordIDsMu.Unlock() + if !ok { + return fmt.Errorf("westcn: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) + } + + err = d.client.DeleteRecord(context.Background(), dns01.UnFqdn(authZone), recordID) + if err != nil { + return fmt.Errorf("westcn: delete record: %w", err) + } + + // deletes 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/westcn/westcn.toml b/providers/dns/westcn/westcn.toml new file mode 100644 index 000000000..3b3914eac --- /dev/null +++ b/providers/dns/westcn/westcn.toml @@ -0,0 +1,24 @@ +Name = "West.cn/西部数码" +Description = '''''' +URL = "https://www.west.cn" +Code = "westcn" +Since = "v4.21.0" + +Example = ''' +WESTCN_USERNAME="xxx" \ +WESTCN_PASSWORD="yyy" \ +lego --email you@example.com --dns westcn -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + WESTCN_USERNAME = "Username" + WESTCN_PASSWORD = "API password" + [Configuration.Additional] + WESTCN_POLLING_INTERVAL = "Time between DNS propagation check" + WESTCN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" + WESTCN_TTL = "The TTL of the TXT record used for the DNS challenge" + WESTCN_HTTP_TIMEOUT = "API request timeout" + +[Links] + API = "https://www.west.cn/CustomerCenter/doc/domain_v2.html" diff --git a/providers/dns/westcn/westcn_test.go b/providers/dns/westcn/westcn_test.go new file mode 100644 index 000000000..71632d99f --- /dev/null +++ b/providers/dns/westcn/westcn_test.go @@ -0,0 +1,143 @@ +package westcn + +import ( + "testing" + + "github.com/go-acme/lego/v4/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: "user", + EnvPassword: "secret", + }, + }, + { + desc: "missing username", + envVars: map[string]string{ + EnvUsername: "", + EnvPassword: "secret", + }, + expected: "westcn: some credentials information are missing: WESTCN_USERNAME", + }, + { + desc: "missing password", + envVars: map[string]string{ + EnvUsername: "user", + EnvPassword: "", + }, + expected: "westcn: some credentials information are missing: WESTCN_PASSWORD", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "westcn: some credentials information are missing: WESTCN_USERNAME,WESTCN_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 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 + username string + password string + expected string + }{ + { + desc: "success", + username: "user", + password: "secret", + }, + { + desc: "missing username", + password: "secret", + expected: "westcn: credentials missing", + }, + { + desc: "missing password", + username: "user", + expected: "westcn: credentials missing", + }, + { + desc: "missing credentials", + expected: "westcn: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Username = test.username + config.Password = test.password + + 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 701fc83ff..a60b48b70 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -144,6 +144,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/webnames" "github.com/go-acme/lego/v4/providers/dns/websupport" "github.com/go-acme/lego/v4/providers/dns/wedos" + "github.com/go-acme/lego/v4/providers/dns/westcn" "github.com/go-acme/lego/v4/providers/dns/yandex" "github.com/go-acme/lego/v4/providers/dns/yandex360" "github.com/go-acme/lego/v4/providers/dns/yandexcloud" @@ -430,6 +431,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return websupport.NewDNSProvider() case "wedos": return wedos.NewDNSProvider() + case "westcn": + return westcn.NewDNSProvider() case "yandex": return yandex.NewDNSProvider() case "yandex360": From 87b7e7191f52db48722de068eaf519b95e728ed6 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 22 Nov 2024 02:58:37 +0100 Subject: [PATCH 007/298] chore: fix AUR configuration --- .goreleaser.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index bfb604eea..c3812ec01 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -170,7 +170,7 @@ snapcrafts: - network-bind aurs: - - description: "Let's Encrypt client and ACME library written in Go" + - description: "Let s Encrypt client and ACME library written in Go" skip_upload: false homepage: https://go-acme.github.io/lego/ name: 'lego-bin' @@ -186,7 +186,7 @@ aurs: email: ldez@users.noreply.github.com package: |- # Bin - install -Dm755 "./prm" "${pkgdir}/usr/bin/lego" + install -Dm755 "./lego" "${pkgdir}/usr/bin/lego" # License install -Dm644 "./LICENSE" "${pkgdir}/usr/share/licenses/lego/LICENSE" From abccd21e7556c7967e1455ff41e4e17ee7bbf717 Mon Sep 17 00:00:00 2001 From: Lucas Savva Date: Mon, 25 Nov 2024 23:29:35 +0000 Subject: [PATCH 008/298] feat: add --force-cert-domains flag to renew (#2355) --- cmd/cmd_renew.go | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index 1f9c08168..3cce35b45 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -26,6 +26,7 @@ const ( flgReuseKey = "reuse-key" flgRenewHook = "renew-hook" flgNoRandomSleep = "no-random-sleep" + flgForceCertDomains = "force-cert-domains" ) const ( @@ -53,6 +54,9 @@ func createRenew() *cli.Command { if !hasDomains && !hasCsr { log.Fatal("Please specify --%s/-d (or --%s/-c if you already have a CSR)", flgDomains, flgCSR) } + if ctx.Bool(flgForceCertDomains) && hasCsr { + log.Fatal("--%s only works with --%s/-d, --%s/-c doesn't support this option.", flgForceCertDomains, flgDomains, flgCSR) + } return nil }, Flags: []cli.Flag{ @@ -110,6 +114,10 @@ func createRenew() *cli.Command { Usage: "Do not add a random sleep before the renewal." + " We do not recommend using this flag if you are doing your renewals in an automated way.", }, + &cli.BoolFlag{ + Name: flgForceCertDomains, + Usage: "Check and ensure that the cert's domain list matches those passed in the domains argument.", + }, }, } } @@ -172,7 +180,12 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif } } - if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int(flgDays)) { + forceDomains := ctx.Bool(flgForceCertDomains) + + certDomains := certcrypto.ExtractDomains(cert) + + if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int(flgDays)) && + (!forceDomains || slices.Equal(certDomains, domains)) { return nil } @@ -180,8 +193,6 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif timeLeft := cert.NotAfter.Sub(time.Now().UTC()) log.Infof("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours())) - certDomains := certcrypto.ExtractDomains(cert) - var privateKey crypto.PrivateKey if ctx.Bool(flgReuseKey) { keyBytes, errR := certsStorage.ReadFile(domain, keyExt) @@ -207,8 +218,13 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif time.Sleep(sleepTime) } + renewalDomains := domains + if !forceDomains { + renewalDomains = merge(certDomains, domains) + } + request := certificate.ObtainRequest{ - Domains: merge(certDomains, domains), + Domains: renewalDomains, PrivateKey: privateKey, MustStaple: ctx.Bool(flgMustStaple), NotBefore: getTime(ctx, flgNotBefore), From 2c42b264d0ba99e67172d10c5379a2a2e8ac66dc Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 27 Nov 2024 14:34:46 +0100 Subject: [PATCH 009/298] dnsmadeeasy: use default transport (#2362) --- providers/dns/dnsmadeeasy/dnsmadeeasy.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/providers/dns/dnsmadeeasy/dnsmadeeasy.go b/providers/dns/dnsmadeeasy/dnsmadeeasy.go index e4e77726f..fcfe6714c 100644 --- a/providers/dns/dnsmadeeasy/dnsmadeeasy.go +++ b/providers/dns/dnsmadeeasy/dnsmadeeasy.go @@ -47,15 +47,22 @@ type Config struct { // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { + tr := &http.Transport{} + + defaultTransport, ok := http.DefaultTransport.(*http.Transport) + if ok { + tr = defaultTransport.Clone() + } + + tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + 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, 10*time.Second), - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }, + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second), + Transport: tr, }, } } From 8e5448ccd7c69ccd56f8cfe9443ea8af523c929a Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 27 Nov 2024 14:42:03 +0100 Subject: [PATCH 010/298] otc: use default transport (#2363) --- providers/dns/otc/otc.go | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/providers/dns/otc/otc.go b/providers/dns/otc/otc.go index 3bb11cecc..3569e6343 100644 --- a/providers/dns/otc/otc.go +++ b/providers/dns/otc/otc.go @@ -5,7 +5,6 @@ import ( "context" "errors" "fmt" - "net" "net/http" "time" @@ -55,6 +54,16 @@ type Config struct { // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { + tr := &http.Transport{} + + defaultTransport, ok := http.DefaultTransport.(*http.Transport) + if ok { + tr = defaultTransport.Clone() + } + + // Workaround for keep alive bug in otc api + tr.DisableKeepAlives = true + return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), @@ -62,21 +71,8 @@ func NewDefaultConfig() *Config { IdentityEndpoint: env.GetOrDefaultString(EnvIdentityEndpoint, defaultIdentityEndpoint), SequenceInterval: env.GetOrDefaultSecond(EnvSequenceInterval, dns01.DefaultPropagationTimeout), HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second), - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - }).DialContext, - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - - // Workaround for keep alive bug in otc api - DisableKeepAlives: true, - }, + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second), + Transport: tr, }, } } From c2f179f1440614549fe0a85537a30d4e08b68269 Mon Sep 17 00:00:00 2001 From: Nick J Lange Date: Fri, 29 Nov 2024 09:06:30 -0500 Subject: [PATCH 011/298] docs: add note about --dns.resolvers (#2364) Co-authored-by: Fernandez Ludovic --- docs/content/usage/cli/Obtain-a-Certificate.md | 15 +++++++++++++++ docs/data/zz_cli_help.toml | 1 + 2 files changed, 16 insertions(+) diff --git a/docs/content/usage/cli/Obtain-a-Certificate.md b/docs/content/usage/cli/Obtain-a-Certificate.md index c92f4ecf0..c7f25dfc0 100644 --- a/docs/content/usage/cli/Obtain-a-Certificate.md +++ b/docs/content/usage/cli/Obtain-a-Certificate.md @@ -58,6 +58,21 @@ GANDI_API_KEY=xxx \ lego --email "you@example.com" --dns gandi --domains "example.org" --domains "*.example.org" run ``` +{{% notice title="For a zone that has multiple SOAs" icon="info-circle" %}} + +This can often be found where your DNS provider has a zone entry for an internal network (i.e. a corporate network, or home LAN) as well as the public internet. +In this case, point lego at an external authoritative server for the zone using the additional parameter `--dns.resolvers`. + +```bash +GANDI_API_KEY=xxx \ +lego --email "you@example.com" --dns gandi --dns.resolvers 9.9.9.9:53 --domains "example.org" --domains "*.example.org" run + +``` + +[More information about resolvers.]({{% ref "options#dns-resolvers-and-challenge-verification" %}}) + +{{% /notice %}} + ## Using a custom certificate signing request (CSR) diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index b6eec239b..638a596ae 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -99,6 +99,7 @@ OPTIONS: --always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful. --renew-hook value Define a hook. The hook is executed only when the certificates are effectively renewed. --no-random-sleep Do not add a random sleep before the renewal. We do not recommend using this flag if you are doing your renewals in an automated way. (default: false) + --force-cert-domains Check and ensure that the cert's domain list matches those passed in the domains argument. (default: false) --help, -h show help """ From 19a02023b4f22680f404add31b9ec5bf9da8935f Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 1 Dec 2024 16:29:02 +0100 Subject: [PATCH 012/298] fix(cli): clone the transport with tls-skip-verify (#2369) --- cmd/setup.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/setup.go b/cmd/setup.go index 4a802ba13..6adc60d41 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -1,7 +1,6 @@ package cmd import ( - "crypto/tls" "crypto/x509" "encoding/pem" "fmt" @@ -51,8 +50,11 @@ func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyTy } if ctx.Bool(flgTLSSkipVerify) { - config.HTTPClient.Transport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + defaultTransport, ok := config.HTTPClient.Transport.(*http.Transport) + if ok { // This is always true because the default client used by the CLI defined the transport. + tr := defaultTransport.Clone() + tr.TLSClientConfig.InsecureSkipVerify = true + config.HTTPClient.Transport = tr } } From aacfa2b06974d13bad84e9495bd9b56bfea16c35 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 3 Dec 2024 14:03:26 +0100 Subject: [PATCH 013/298] infomaniak: increase default propagation timeout (#2371) --- providers/dns/infomaniak/infomaniak.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/providers/dns/infomaniak/infomaniak.go b/providers/dns/infomaniak/infomaniak.go index 84f494214..79c6f577e 100644 --- a/providers/dns/infomaniak/infomaniak.go +++ b/providers/dns/infomaniak/infomaniak.go @@ -47,9 +47,9 @@ type Config struct { func NewDefaultConfig() *Config { return &Config{ APIEndpoint: env.GetOrDefaultString(EnvEndpoint, internal.DefaultBaseURL), - TTL: env.GetOrDefaultInt(EnvTTL, 7200), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + TTL: env.GetOrDefaultInt(EnvTTL, 300), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), }, From eb041044b8c28a7309b69dddb4bf6c7cd99a33b6 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 3 Dec 2024 14:03:49 +0100 Subject: [PATCH 014/298] fix(cli): create client only when needed (#2372) --- .golangci.yml | 4 ++++ cmd/cmd_renew.go | 27 +++++++++++++++++++++------ cmd/cmd_revoke.go | 8 +++++--- cmd/cmd_run.go | 5 +++-- cmd/setup.go | 15 +++++++++++---- 5 files changed, 44 insertions(+), 15 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index b3383969a..68fd32a68 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -249,6 +249,10 @@ issues: text: 'cyclomatic complexity \d+ of func `(renewForDomains|renewForCSR)` is high' linters: - gocyclo + - path: cmd/cmd_renew.go + text: "Function 'renewForDomains' has too many statements" + linters: + - funlen - path: providers/dns/cpanel/cpanel.go text: 'cyclomatic complexity 13 of func `\(\*DNSProvider\)\.CleanUp` is high' linters: diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index 3cce35b45..c4c680234 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -123,8 +123,7 @@ func createRenew() *cli.Command { } func renew(ctx *cli.Context) error { - account, client := setup(ctx, NewAccountsStorage(ctx)) - setupChallenges(ctx, client) + account, keyType := setupAccount(ctx, NewAccountsStorage(ctx)) if account.Registration == nil { log.Fatalf("Account %s is not registered. Use 'run' to register a new account.\n", account.Email) @@ -138,14 +137,14 @@ func renew(ctx *cli.Context) error { // CSR if ctx.IsSet(flgCSR) { - return renewForCSR(ctx, client, certsStorage, bundle, meta) + return renewForCSR(ctx, account, keyType, certsStorage, bundle, meta) } // Domains - return renewForDomains(ctx, client, certsStorage, bundle, meta) + return renewForDomains(ctx, account, keyType, certsStorage, bundle, meta) } -func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error { +func renewForDomains(ctx *cli.Context, account *Account, keyType certcrypto.KeyType, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error { domains := ctx.StringSlice(flgDomains) domain := domains[0] @@ -162,7 +161,11 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif var ariRenewalTime *time.Time var replacesCertID string + var client *lego.Client + if !ctx.Bool(flgARIDisable) { + client = setupClient(ctx, account, keyType) + ariRenewalTime = getARIRenewalTime(ctx, cert, domain, client) if ariRenewalTime != nil { now := time.Now().UTC() @@ -189,6 +192,10 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif return nil } + if client == nil { + client = setupClient(ctx, account, keyType) + } + // This is just meant to be informal for the user. timeLeft := cert.NotAfter.Sub(time.Now().UTC()) log.Infof("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours())) @@ -250,7 +257,7 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif return launchHook(ctx.String(flgRenewHook), meta) } -func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error { +func renewForCSR(ctx *cli.Context, account *Account, keyType certcrypto.KeyType, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error { csr, err := readCSRFile(ctx.String(flgCSR)) if err != nil { log.Fatal(err) @@ -274,7 +281,11 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat var ariRenewalTime *time.Time var replacesCertID string + var client *lego.Client + if !ctx.Bool(flgARIDisable) { + client = setupClient(ctx, account, keyType) + ariRenewalTime = getARIRenewalTime(ctx, cert, domain, client) if ariRenewalTime != nil { now := time.Now().UTC() @@ -296,6 +307,10 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat return nil } + if client == nil { + client = setupClient(ctx, account, keyType) + } + // This is just meant to be informal for the user. timeLeft := cert.NotAfter.Sub(time.Now().UTC()) log.Infof("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours())) diff --git a/cmd/cmd_revoke.go b/cmd/cmd_revoke.go index 2ecfd3017..667bebe12 100644 --- a/cmd/cmd_revoke.go +++ b/cmd/cmd_revoke.go @@ -38,12 +38,14 @@ func createRevoke() *cli.Command { } func revoke(ctx *cli.Context) error { - acc, client := setup(ctx, NewAccountsStorage(ctx)) + account, keyType := setupAccount(ctx, NewAccountsStorage(ctx)) - if acc.Registration == nil { - log.Fatalf("Account %s is not registered. Use 'run' to register a new account.\n", acc.Email) + if account.Registration == nil { + log.Fatalf("Account %s is not registered. Use 'run' to register a new account.\n", account.Email) } + client := newClient(ctx, account, keyType) + certsStorage := NewCertificatesStorage(ctx) certsStorage.CreateRootFolder() diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index a1d4cd514..f2cec5655 100644 --- a/cmd/cmd_run.go +++ b/cmd/cmd_run.go @@ -93,8 +93,9 @@ backups of this folder is ideal. func run(ctx *cli.Context) error { accountsStorage := NewAccountsStorage(ctx) - account, client := setup(ctx, accountsStorage) - setupChallenges(ctx, client) + account, keyType := setupAccount(ctx, accountsStorage) + + client := setupClient(ctx, account, keyType) if account.Registration == nil { reg, err := register(ctx, client) diff --git a/cmd/setup.go b/cmd/setup.go index 6adc60d41..5e878827d 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -18,7 +18,16 @@ import ( const filePerm os.FileMode = 0o600 -func setup(ctx *cli.Context, accountsStorage *AccountsStorage) (*Account, *lego.Client) { +// setupClient creates a new client with challenge settings. +func setupClient(ctx *cli.Context, account *Account, keyType certcrypto.KeyType) *lego.Client { + client := newClient(ctx, account, keyType) + + setupChallenges(ctx, client) + + return client +} + +func setupAccount(ctx *cli.Context, accountsStorage *AccountsStorage) (*Account, certcrypto.KeyType) { keyType := getKeyType(ctx) privateKey := accountsStorage.GetPrivateKey(keyType) @@ -29,9 +38,7 @@ func setup(ctx *cli.Context, accountsStorage *AccountsStorage) (*Account, *lego. account = &Account{Email: accountsStorage.GetUserID(), key: privateKey} } - client := newClient(ctx, account, keyType) - - return account, client + return account, keyType } func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyType) *lego.Client { From 2c13835084e398eb5b358374414b0d758de65444 Mon Sep 17 00:00:00 2001 From: Jan-Henrik Bruhn Date: Tue, 3 Dec 2024 19:51:37 +0100 Subject: [PATCH 015/298] inwx: delete only the TXT record related to the DNS challenge (#2373) Co-authored-by: Fernandez Ludovic --- providers/dns/inwx/inwx.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/providers/dns/inwx/inwx.go b/providers/dns/inwx/inwx.go index cefea832e..6ed416a27 100644 --- a/providers/dns/inwx/inwx.go +++ b/providers/dns/inwx/inwx.go @@ -180,15 +180,26 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("inwx: %w", err) } - var lastErr error + var recordID int for _, record := range response.Records { - err = d.client.Nameservers.DeleteRecord(record.ID) - if err != nil { - lastErr = fmt.Errorf("inwx: %w", err) + if record.Content != challengeInfo.Value { + continue } + + recordID = record.ID + break } - return lastErr + if recordID == 0 { + return errors.New("inwx: TXT record not found") + } + + err = d.client.Nameservers.DeleteRecord(recordID) + if err != nil { + return fmt.Errorf("inwx: %w", err) + } + + return nil } // Timeout returns the timeout and interval to use when checking for DNS propagation. From 1a62bbab40778016d6df8af1a495304f537a8903 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 5 Dec 2024 14:24:23 +0100 Subject: [PATCH 016/298] bunny: fix zone detection (#2375) --- providers/dns/bunny/bunny.go | 74 +++++++++++-------- providers/dns/bunny/bunny_test.go | 116 ++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 28 deletions(-) diff --git a/providers/dns/bunny/bunny.go b/providers/dns/bunny/bunny.go index 63a5a01e9..870b8c8da 100644 --- a/providers/dns/bunny/bunny.go +++ b/providers/dns/bunny/bunny.go @@ -5,12 +5,15 @@ import ( "context" "errors" "fmt" + "slices" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/miekg/dns" "github.com/nrdcg/bunny-go" + "golang.org/x/net/publicsuffix" ) // Environment variables names. @@ -94,19 +97,14 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - authZone, err := getZoneName(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("bunny: could not find zone for domain %q: %w", domain, err) - } - ctx := context.Background() - zone, err := d.findZone(ctx, authZone) + zone, err := d.findZone(ctx, dns01.UnFqdn(info.EffectiveFQDN)) if err != nil { return fmt.Errorf("bunny: %w", err) } - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, deref(zone.Domain)) if err != nil { return fmt.Errorf("bunny: %w", err) } @@ -129,19 +127,14 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - authZone, err := getZoneName(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("bunny: could not find zone for domain %q: %w", domain, err) - } - ctx := context.Background() - zone, err := d.findZone(ctx, authZone) + zone, err := d.findZone(ctx, dns01.UnFqdn(info.EffectiveFQDN)) if err != nil { return fmt.Errorf("bunny: %w", err) } - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, deref(zone.Domain)) if err != nil { return fmt.Errorf("bunny: %w", err) } @@ -172,28 +165,53 @@ func (d *DNSProvider) findZone(ctx context.Context, authZone string) (*bunny.DNS return nil, err } - var zone *bunny.DNSZone - for _, item := range zones.Items { - if item != nil && deref(item.Domain) == authZone { - zone = item - break - } - } - + zone := findZone(zones, authZone) if zone == nil { - return nil, fmt.Errorf("could not find DNSZone zone=%s", authZone) + return nil, fmt.Errorf("could not find DNSZone domain=%s", authZone) } return zone, nil } -func getZoneName(fqdn string) (string, error) { - authZone, err := dns01.FindZoneByFqdn(fqdn) - if err != nil { - return "", err +func findZone(zones *bunny.DNSZones, domain string) *bunny.DNSZone { + domains := possibleDomains(domain) + + var domainLength int + + var zone *bunny.DNSZone + for _, item := range zones.Items { + if item == nil { + continue + } + + curr := deref(item.Domain) + + if slices.Contains(domains, curr) && domainLength < len(curr) { + domainLength = len(curr) + + zone = item + } } - return dns01.UnFqdn(authZone), nil + return zone +} + +func possibleDomains(domain string) []string { + var domains []string + + labelIndexes := dns.Split(domain) + + for _, index := range labelIndexes { + tld, _ := publicsuffix.PublicSuffix(domain) + if tld == domain[index:] { + // skip the TLD + break + } + + domains = append(domains, dns01.UnFqdn(domain[index:])) + } + + return domains } func pointer[T string | int | int32 | int64](v T) *T { return &v } diff --git a/providers/dns/bunny/bunny_test.go b/providers/dns/bunny/bunny_test.go index e5724bcd2..34605f619 100644 --- a/providers/dns/bunny/bunny_test.go +++ b/providers/dns/bunny/bunny_test.go @@ -4,6 +4,8 @@ import ( "testing" "github.com/go-acme/lego/v4/platform/tester" + "github.com/nrdcg/bunny-go" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -123,3 +125,117 @@ func TestLiveCleanUp(t *testing.T) { err = provider.CleanUp(envTest.GetDomain(), "", "123d==") require.NoError(t, err) } + +func Test_findZone(t *testing.T) { + testCases := []struct { + desc string + domain string + items []*bunny.DNSZone + expected *bunny.DNSZone + }{ + { + desc: "found subdomain", + domain: "_acme-challenge.foo.bar.example.com", + items: []*bunny.DNSZone{ + {ID: pointer[int64](1), Domain: pointer("example.com")}, + {ID: pointer[int64](2), Domain: pointer("example.org")}, + {ID: pointer[int64](4), Domain: pointer("bar.example.org")}, + {ID: pointer[int64](5), Domain: pointer("bar.example.com")}, + {ID: pointer[int64](6), Domain: pointer("foo.example.com")}, + }, + expected: &bunny.DNSZone{ + ID: pointer[int64](5), + Domain: pointer("bar.example.com"), + }, + }, + { + desc: "found the longest subdomain", + domain: "_acme-challenge.foo.bar.example.com", + items: []*bunny.DNSZone{ + {ID: pointer[int64](7), Domain: pointer("foo.bar.example.com")}, + {ID: pointer[int64](1), Domain: pointer("example.com")}, + {ID: pointer[int64](2), Domain: pointer("example.org")}, + {ID: pointer[int64](4), Domain: pointer("bar.example.org")}, + {ID: pointer[int64](5), Domain: pointer("bar.example.com")}, + {ID: pointer[int64](6), Domain: pointer("foo.example.com")}, + }, + expected: &bunny.DNSZone{ + ID: pointer[int64](7), + Domain: pointer("foo.bar.example.com"), + }, + }, + { + desc: "found apex", + domain: "_acme-challenge.foo.bar.example.com", + items: []*bunny.DNSZone{ + {ID: pointer[int64](1), Domain: pointer("example.com")}, + {ID: pointer[int64](2), Domain: pointer("example.org")}, + {ID: pointer[int64](4), Domain: pointer("bar.example.org")}, + {ID: pointer[int64](6), Domain: pointer("foo.example.com")}, + }, + expected: &bunny.DNSZone{ + ID: pointer[int64](1), + Domain: pointer("example.com"), + }, + }, + { + desc: "not found", + domain: "_acme-challenge.foo.bar.example.com", + items: []*bunny.DNSZone{ + {ID: pointer[int64](2), Domain: pointer("example.org")}, + {ID: pointer[int64](4), Domain: pointer("bar.example.org")}, + {ID: pointer[int64](6), Domain: pointer("foo.example.com")}, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + zones := &bunny.DNSZones{Items: test.items} + + zone := findZone(zones, test.domain) + + assert.Equal(t, test.expected, zone) + }) + } +} + +func Test_possibleDomains(t *testing.T) { + testCases := []struct { + desc string + domain string + expected []string + }{ + { + desc: "apex", + domain: "example.com", + expected: []string{"example.com"}, + }, + { + desc: "CCTLD", + domain: "example.co.uk", + expected: []string{"example.co.uk"}, + }, + { + desc: "long domain", + domain: "_acme-challenge.foo.bar.example.com", + expected: []string{"_acme-challenge.foo.bar.example.com", "foo.bar.example.com", "bar.example.com", "example.com"}, + }, + { + desc: "empty", + domain: "", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + domains := possibleDomains(test.domain) + + assert.Equal(t, test.expected, domains) + }) + } +} From 0bbf5ab59cda8beaedf5b1ce21a3d1bf0eb48fc5 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 5 Dec 2024 19:49:13 +0100 Subject: [PATCH 017/298] chore: shared deref and pointer functions (#2376) --- providers/dns/azuredns/azuredns.go | 9 ---- providers/dns/azuredns/private.go | 3 +- providers/dns/azuredns/public.go | 3 +- providers/dns/azuredns/servicediscovery.go | 3 +- providers/dns/bunny/bunny.go | 38 ++++++-------- providers/dns/bunny/bunny_test.go | 49 ++++++++++--------- providers/dns/huaweicloud/huaweicloud.go | 38 ++++++-------- providers/dns/internal/ptr/types.go | 12 +++++ providers/dns/inwx/inwx.go | 35 ++++++------- .../lightsail/lightsail_integration_test.go | 12 +---- providers/dns/route53/route53.go | 22 +++------ .../dns/route53/route53_integration_test.go | 3 +- providers/dns/volcengine/volcengine.go | 28 ++++------- 13 files changed, 107 insertions(+), 148 deletions(-) create mode 100644 providers/dns/internal/ptr/types.go diff --git a/providers/dns/azuredns/azuredns.go b/providers/dns/azuredns/azuredns.go index dd591d92b..860d19691 100644 --- a/providers/dns/azuredns/azuredns.go +++ b/providers/dns/azuredns/azuredns.go @@ -278,12 +278,3 @@ func getZoneName(config *Config, fqdn string) (string, error) { return authZone, nil } - -func deref[T any](v *T) T { - if v == nil { - var zero T - return zero - } - - return *v -} diff --git a/providers/dns/azuredns/private.go b/providers/dns/azuredns/private.go index c3d6cf354..24fb3d5ee 100644 --- a/providers/dns/azuredns/private.go +++ b/providers/dns/azuredns/private.go @@ -14,6 +14,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" ) var _ challenge.ProviderTimeout = (*DNSProviderPrivate)(nil) @@ -184,7 +185,7 @@ func privateUniqueRecords(recordSet armprivatedns.RecordSet, value string) map[s for _, txtRecord := range recordSet.Properties.TxtRecords { // Assume Value doesn't contain multiple strings if len(txtRecord.Value) > 0 { - uniqRecords[deref(txtRecord.Value[0])] = struct{}{} + uniqRecords[ptr.Deref(txtRecord.Value[0])] = struct{}{} } } } diff --git a/providers/dns/azuredns/public.go b/providers/dns/azuredns/public.go index f6c00b2a7..f7e46150d 100644 --- a/providers/dns/azuredns/public.go +++ b/providers/dns/azuredns/public.go @@ -14,6 +14,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" ) var _ challenge.ProviderTimeout = (*DNSProviderPublic)(nil) @@ -182,7 +183,7 @@ func publicUniqueRecords(recordSet armdns.RecordSet, value string) map[string]st for _, txtRecord := range recordSet.Properties.TxtRecords { // Assume Value doesn't contain multiple strings if len(txtRecord.Value) > 0 { - uniqRecords[deref(txtRecord.Value[0])] = struct{}{} + uniqRecords[ptr.Deref(txtRecord.Value[0])] = struct{}{} } } } diff --git a/providers/dns/azuredns/servicediscovery.go b/providers/dns/azuredns/servicediscovery.go index 62dfd6623..882e19241 100644 --- a/providers/dns/azuredns/servicediscovery.go +++ b/providers/dns/azuredns/servicediscovery.go @@ -9,6 +9,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" ) type ServiceDiscoveryZone struct { @@ -88,7 +89,7 @@ func discoverDNSZones(ctx context.Context, config *Config, credentials azcore.To *requestOptions.Skip += ResourceGraphQueryOptionsTop if result.TotalRecords != nil { - if int64(deref(requestOptions.Skip)) >= deref(result.TotalRecords) { + if int64(ptr.Deref(requestOptions.Skip)) >= ptr.Deref(result.TotalRecords) { break } } diff --git a/providers/dns/bunny/bunny.go b/providers/dns/bunny/bunny.go index 870b8c8da..9716a20c7 100644 --- a/providers/dns/bunny/bunny.go +++ b/providers/dns/bunny/bunny.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" "github.com/miekg/dns" "github.com/nrdcg/bunny-go" "golang.org/x/net/publicsuffix" @@ -104,20 +105,20 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("bunny: %w", err) } - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, deref(zone.Domain)) + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, ptr.Deref(zone.Domain)) if err != nil { return fmt.Errorf("bunny: %w", err) } record := &bunny.AddOrUpdateDNSRecordOptions{ - Type: pointer(bunny.DNSRecordTypeTXT), - Name: pointer(subDomain), - Value: pointer(info.Value), - TTL: pointer(int32(d.config.TTL)), + Type: ptr.Pointer(bunny.DNSRecordTypeTXT), + Name: ptr.Pointer(subDomain), + Value: ptr.Pointer(info.Value), + TTL: ptr.Pointer(int32(d.config.TTL)), } - if _, err := d.client.DNSZone.AddDNSRecord(ctx, deref(zone.ID), record); err != nil { - return fmt.Errorf("bunny: failed to add TXT record: fqdn=%s, zoneID=%d: %w", info.EffectiveFQDN, deref(zone.ID), err) + if _, err := d.client.DNSZone.AddDNSRecord(ctx, ptr.Deref(zone.ID), record); err != nil { + return fmt.Errorf("bunny: failed to add TXT record: fqdn=%s, zoneID=%d: %w", info.EffectiveFQDN, ptr.Deref(zone.ID), err) } return nil @@ -134,14 +135,14 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("bunny: %w", err) } - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, deref(zone.Domain)) + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, ptr.Deref(zone.Domain)) if err != nil { return fmt.Errorf("bunny: %w", err) } var record *bunny.DNSRecord for _, r := range zone.Records { - if deref(r.Name) == subDomain && deref(r.Type) == bunny.DNSRecordTypeTXT { + if ptr.Deref(r.Name) == subDomain && ptr.Deref(r.Type) == bunny.DNSRecordTypeTXT { r := r record = &r break @@ -149,11 +150,11 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } if record == nil { - return fmt.Errorf("bunny: could not find TXT record zone=%d, subdomain=%s", deref(zone.ID), subDomain) + return fmt.Errorf("bunny: could not find TXT record zone=%d, subdomain=%s", ptr.Deref(zone.ID), subDomain) } - if err := d.client.DNSZone.DeleteDNSRecord(ctx, deref(zone.ID), deref(record.ID)); err != nil { - return fmt.Errorf("bunny: failed to delete TXT record: id=%d, name=%s: %w", deref(record.ID), deref(record.Name), err) + if err := d.client.DNSZone.DeleteDNSRecord(ctx, ptr.Deref(zone.ID), ptr.Deref(record.ID)); err != nil { + return fmt.Errorf("bunny: failed to delete TXT record: id=%d, name=%s: %w", ptr.Deref(record.ID), ptr.Deref(record.Name), err) } return nil @@ -184,7 +185,7 @@ func findZone(zones *bunny.DNSZones, domain string) *bunny.DNSZone { continue } - curr := deref(item.Domain) + curr := ptr.Deref(item.Domain) if slices.Contains(domains, curr) && domainLength < len(curr) { domainLength = len(curr) @@ -213,14 +214,3 @@ func possibleDomains(domain string) []string { return domains } - -func pointer[T string | int | int32 | int64](v T) *T { return &v } - -func deref[T string | int | int32 | int64](v *T) T { - if v == nil { - var zero T - return zero - } - - return *v -} diff --git a/providers/dns/bunny/bunny_test.go b/providers/dns/bunny/bunny_test.go index 34605f619..4cf0f6b01 100644 --- a/providers/dns/bunny/bunny_test.go +++ b/providers/dns/bunny/bunny_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" "github.com/nrdcg/bunny-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -137,54 +138,54 @@ func Test_findZone(t *testing.T) { desc: "found subdomain", domain: "_acme-challenge.foo.bar.example.com", items: []*bunny.DNSZone{ - {ID: pointer[int64](1), Domain: pointer("example.com")}, - {ID: pointer[int64](2), Domain: pointer("example.org")}, - {ID: pointer[int64](4), Domain: pointer("bar.example.org")}, - {ID: pointer[int64](5), Domain: pointer("bar.example.com")}, - {ID: pointer[int64](6), Domain: pointer("foo.example.com")}, + {ID: ptr.Pointer[int64](1), Domain: ptr.Pointer("example.com")}, + {ID: ptr.Pointer[int64](2), Domain: ptr.Pointer("example.org")}, + {ID: ptr.Pointer[int64](4), Domain: ptr.Pointer("bar.example.org")}, + {ID: ptr.Pointer[int64](5), Domain: ptr.Pointer("bar.example.com")}, + {ID: ptr.Pointer[int64](6), Domain: ptr.Pointer("foo.example.com")}, }, expected: &bunny.DNSZone{ - ID: pointer[int64](5), - Domain: pointer("bar.example.com"), + ID: ptr.Pointer[int64](5), + Domain: ptr.Pointer("bar.example.com"), }, }, { desc: "found the longest subdomain", domain: "_acme-challenge.foo.bar.example.com", items: []*bunny.DNSZone{ - {ID: pointer[int64](7), Domain: pointer("foo.bar.example.com")}, - {ID: pointer[int64](1), Domain: pointer("example.com")}, - {ID: pointer[int64](2), Domain: pointer("example.org")}, - {ID: pointer[int64](4), Domain: pointer("bar.example.org")}, - {ID: pointer[int64](5), Domain: pointer("bar.example.com")}, - {ID: pointer[int64](6), Domain: pointer("foo.example.com")}, + {ID: ptr.Pointer[int64](7), Domain: ptr.Pointer("foo.bar.example.com")}, + {ID: ptr.Pointer[int64](1), Domain: ptr.Pointer("example.com")}, + {ID: ptr.Pointer[int64](2), Domain: ptr.Pointer("example.org")}, + {ID: ptr.Pointer[int64](4), Domain: ptr.Pointer("bar.example.org")}, + {ID: ptr.Pointer[int64](5), Domain: ptr.Pointer("bar.example.com")}, + {ID: ptr.Pointer[int64](6), Domain: ptr.Pointer("foo.example.com")}, }, expected: &bunny.DNSZone{ - ID: pointer[int64](7), - Domain: pointer("foo.bar.example.com"), + ID: ptr.Pointer[int64](7), + Domain: ptr.Pointer("foo.bar.example.com"), }, }, { desc: "found apex", domain: "_acme-challenge.foo.bar.example.com", items: []*bunny.DNSZone{ - {ID: pointer[int64](1), Domain: pointer("example.com")}, - {ID: pointer[int64](2), Domain: pointer("example.org")}, - {ID: pointer[int64](4), Domain: pointer("bar.example.org")}, - {ID: pointer[int64](6), Domain: pointer("foo.example.com")}, + {ID: ptr.Pointer[int64](1), Domain: ptr.Pointer("example.com")}, + {ID: ptr.Pointer[int64](2), Domain: ptr.Pointer("example.org")}, + {ID: ptr.Pointer[int64](4), Domain: ptr.Pointer("bar.example.org")}, + {ID: ptr.Pointer[int64](6), Domain: ptr.Pointer("foo.example.com")}, }, expected: &bunny.DNSZone{ - ID: pointer[int64](1), - Domain: pointer("example.com"), + ID: ptr.Pointer[int64](1), + Domain: ptr.Pointer("example.com"), }, }, { desc: "not found", domain: "_acme-challenge.foo.bar.example.com", items: []*bunny.DNSZone{ - {ID: pointer[int64](2), Domain: pointer("example.org")}, - {ID: pointer[int64](4), Domain: pointer("bar.example.org")}, - {ID: pointer[int64](6), Domain: pointer("foo.example.com")}, + {ID: ptr.Pointer[int64](2), Domain: ptr.Pointer("example.org")}, + {ID: ptr.Pointer[int64](4), Domain: ptr.Pointer("bar.example.org")}, + {ID: ptr.Pointer[int64](6), Domain: ptr.Pointer("foo.example.com")}, }, }, } diff --git a/providers/dns/huaweicloud/huaweicloud.go b/providers/dns/huaweicloud/huaweicloud.go index 9d20c27ab..fbb594f58 100644 --- a/providers/dns/huaweicloud/huaweicloud.go +++ b/providers/dns/huaweicloud/huaweicloud.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" hwauthbasic "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic" hwconfig "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/config" hwdns "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/dns/v2" @@ -155,7 +156,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return false, fmt.Errorf("show record set: %w", errShow) } - return !strings.HasSuffix(deref(rs.Status), "PENDING_"), nil + return !strings.HasSuffix(ptr.Deref(rs.Status), "PENDING_"), nil }) if err != nil { return fmt.Errorf("huaweicloud: %w", err) @@ -208,7 +209,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { func (d *DNSProvider) getOrCreateRecordSetID(domain, zoneID string, info dns01.ChallengeInfo) (string, error) { records, err := d.client.ListRecordSetsByZone(&hwmodel.ListRecordSetsByZoneRequest{ ZoneId: zoneID, - Name: pointer(info.EffectiveFQDN), + Name: ptr.Pointer(info.EffectiveFQDN), }) if err != nil { return "", fmt.Errorf("record list: unable to get record %s for zone %s: %w", info.EffectiveFQDN, domain, err) @@ -216,8 +217,8 @@ func (d *DNSProvider) getOrCreateRecordSetID(domain, zoneID string, info dns01.C var existingRecordSet *hwmodel.ListRecordSets - for _, record := range deref(records.Recordsets) { - if deref(record.Type) == "TXT" && deref(record.Name) == info.EffectiveFQDN { + for _, record := range ptr.Deref(records.Recordsets) { + if ptr.Deref(record.Type) == "TXT" && ptr.Deref(record.Name) == info.EffectiveFQDN { existingRecordSet = &record } } @@ -229,9 +230,9 @@ func (d *DNSProvider) getOrCreateRecordSetID(domain, zoneID string, info dns01.C ZoneId: zoneID, Body: &hwmodel.CreateRecordSetRequestBody{ Name: info.EffectiveFQDN, - Description: pointer("Added TXT record for ACME dns-01 challenge using lego client"), + Description: ptr.Pointer("Added TXT record for ACME dns-01 challenge using lego client"), Type: "TXT", - Ttl: pointer(d.config.TTL), + Ttl: ptr.Pointer(d.config.TTL), Records: []string{value}, }, } @@ -241,18 +242,18 @@ func (d *DNSProvider) getOrCreateRecordSetID(domain, zoneID string, info dns01.C return "", fmt.Errorf("create record set: %w", errCreate) } - return deref(resp.Id), nil + return ptr.Deref(resp.Id), nil } updateRequest := &hwmodel.UpdateRecordSetRequest{ ZoneId: zoneID, - RecordsetId: deref(existingRecordSet.Id), + RecordsetId: ptr.Deref(existingRecordSet.Id), Body: &hwmodel.UpdateRecordSetReq{ Name: existingRecordSet.Name, Description: existingRecordSet.Description, Type: existingRecordSet.Type, Ttl: existingRecordSet.Ttl, - Records: pointer(append(deref(existingRecordSet.Records), value)), + Records: ptr.Pointer(append(ptr.Deref(existingRecordSet.Records), value)), }, } @@ -261,7 +262,7 @@ func (d *DNSProvider) getOrCreateRecordSetID(domain, zoneID string, info dns01.C return "", fmt.Errorf("update record set: %w", err) } - return deref(resp.Id), nil + return ptr.Deref(resp.Id), nil } func (d *DNSProvider) getZoneID(authZone string) (string, error) { @@ -270,22 +271,11 @@ func (d *DNSProvider) getZoneID(authZone string) (string, error) { return "", fmt.Errorf("unable to get zone: %w", err) } - for _, zone := range deref(zones.Zones) { - if deref(zone.Name) == authZone { - return deref(zone.Id), nil + for _, zone := range ptr.Deref(zones.Zones) { + if ptr.Deref(zone.Name) == authZone { + return ptr.Deref(zone.Id), nil } } return "", fmt.Errorf("zone %q not found", authZone) } - -func pointer[T any](v T) *T { return &v } - -func deref[T any](v *T) T { - if v == nil { - var zero T - return zero - } - - return *v -} diff --git a/providers/dns/internal/ptr/types.go b/providers/dns/internal/ptr/types.go new file mode 100644 index 000000000..b0c7974e0 --- /dev/null +++ b/providers/dns/internal/ptr/types.go @@ -0,0 +1,12 @@ +package ptr + +func Deref[T any](v *T) T { + if v == nil { + var zero T + return zero + } + + return *v +} + +func Pointer[T any](v T) *T { return &v } diff --git a/providers/dns/inwx/inwx.go b/providers/dns/inwx/inwx.go index 6ed416a27..f316fd548 100644 --- a/providers/dns/inwx/inwx.go +++ b/providers/dns/inwx/inwx.go @@ -97,14 +97,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - challengeInfo := dns01.GetChallengeInfo(domain, keyAuth) + info := dns01.GetChallengeInfo(domain, keyAuth) - authZone, err := dns01.FindZoneByFqdn(challengeInfo.EffectiveFQDN) + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) if err != nil { - return fmt.Errorf("inwx: could not find zone for domain %q (%s): %w", domain, challengeInfo.EffectiveFQDN, err) + return fmt.Errorf("inwx: could not find zone for domain %q (%s): %w", domain, info.EffectiveFQDN, err) } - info, err := d.client.Account.Login() + login, err := d.client.Account.Login() if err != nil { return fmt.Errorf("inwx: %w", err) } @@ -116,27 +116,24 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { } }() - err = d.twoFactorAuth(info) + err = d.twoFactorAuth(login) if err != nil { return fmt.Errorf("inwx: %w", err) } request := &goinwx.NameserverRecordRequest{ Domain: dns01.UnFqdn(authZone), - Name: dns01.UnFqdn(challengeInfo.EffectiveFQDN), + Name: dns01.UnFqdn(info.EffectiveFQDN), Type: "TXT", - Content: challengeInfo.Value, + Content: info.Value, TTL: d.config.TTL, } _, err = d.client.Nameservers.CreateRecord(request) if err != nil { var er *goinwx.ErrorResponse - if errors.As(err, &er) { - if er.Message == "Object exists" { - return nil - } - return fmt.Errorf("inwx: %w", err) + if errors.As(err, &er) && er.Message == "Object exists" { + return nil } return fmt.Errorf("inwx: %w", err) @@ -147,14 +144,14 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - challengeInfo := dns01.GetChallengeInfo(domain, keyAuth) + info := dns01.GetChallengeInfo(domain, keyAuth) - authZone, err := dns01.FindZoneByFqdn(challengeInfo.EffectiveFQDN) + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) if err != nil { - return fmt.Errorf("inwx: could not find zone for domain %q (%s): %w", domain, challengeInfo.EffectiveFQDN, err) + return fmt.Errorf("inwx: could not find zone for domain %q (%s): %w", domain, info.EffectiveFQDN, err) } - info, err := d.client.Account.Login() + login, err := d.client.Account.Login() if err != nil { return fmt.Errorf("inwx: %w", err) } @@ -166,14 +163,14 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } }() - err = d.twoFactorAuth(info) + err = d.twoFactorAuth(login) if err != nil { return fmt.Errorf("inwx: %w", err) } response, err := d.client.Nameservers.Info(&goinwx.NameserverInfoRequest{ Domain: dns01.UnFqdn(authZone), - Name: dns01.UnFqdn(challengeInfo.EffectiveFQDN), + Name: dns01.UnFqdn(info.EffectiveFQDN), Type: "TXT", }) if err != nil { @@ -182,7 +179,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { var recordID int for _, record := range response.Records { - if record.Content != challengeInfo.Value { + if record.Content != info.Value { continue } diff --git a/providers/dns/lightsail/lightsail_integration_test.go b/providers/dns/lightsail/lightsail_integration_test.go index 20e45ee26..1b96e87f0 100644 --- a/providers/dns/lightsail/lightsail_integration_test.go +++ b/providers/dns/lightsail/lightsail_integration_test.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" awsconfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/lightsail" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" "github.com/stretchr/testify/require" ) @@ -52,19 +53,10 @@ func TestLiveTTL(t *testing.T) { entries := resp.Domain.DomainEntries for _, entry := range entries { - if deref(entry.Type) == "TXT" && deref(entry.Name) == fqdn { + if ptr.Deref(entry.Type) == "TXT" && ptr.Deref(entry.Name) == fqdn { return } } t.Fatalf("Could not find a TXT record for _acme-challenge.%s", domain) } - -func deref[T string | int | int32 | int64 | bool](v *T) T { - if v == nil { - var zero T - return zero - } - - return *v -} diff --git a/providers/dns/route53/route53.go b/providers/dns/route53/route53.go index c0a3146a0..8246cd0ad 100644 --- a/providers/dns/route53/route53.go +++ b/providers/dns/route53/route53.go @@ -21,6 +21,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" ) // Environment variables names. @@ -151,7 +152,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { var found bool for _, record := range records { - if deref(record.Value) == realValue { + if ptr.Deref(record.Value) == realValue { found = true } } @@ -196,7 +197,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { var nonLegoRecords []awstypes.ResourceRecord for _, record := range existingRecords { - if deref(record.Value) != `"`+info.Value+`"` { + if ptr.Deref(record.Value) != `"`+info.Value+`"` { nonLegoRecords = append(nonLegoRecords, record) } } @@ -255,7 +256,7 @@ func (d *DNSProvider) changeRecord(ctx context.Context, action awstypes.ChangeAc return true, nil } - return false, fmt.Errorf("unable to retrieve change: ID=%s", deref(changeID)) + return false, fmt.Errorf("unable to retrieve change: ID=%s", ptr.Deref(changeID)) }) } @@ -281,7 +282,7 @@ func (d *DNSProvider) getExistingRecordSets(ctx context.Context, hostedZoneID, f var records []awstypes.ResourceRecord for _, recordSet := range recordSetsOutput.ResourceRecordSets { - if deref(recordSet.Name) == fqdn { + if ptr.Deref(recordSet.Name) == fqdn { records = append(records, recordSet.ResourceRecords...) } } @@ -311,8 +312,8 @@ func (d *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string, var hostedZoneID string for _, hostedZone := range resp.HostedZones { // .Name has a trailing dot - if !hostedZone.Config.PrivateZone && deref(hostedZone.Name) == authZone { - hostedZoneID = deref(hostedZone.Id) + if !hostedZone.Config.PrivateZone && ptr.Deref(hostedZone.Name) == authZone { + hostedZoneID = ptr.Deref(hostedZone.Id) break } } @@ -394,12 +395,3 @@ func createAWSConfigCheckParams(config *Config) error { return nil } - -func deref[T string | int | int32 | int64 | bool](v *T) T { - if v == nil { - var zero T - return zero - } - - return *v -} diff --git a/providers/dns/route53/route53_integration_test.go b/providers/dns/route53/route53_integration_test.go index 2fbcf5206..9467fb77a 100644 --- a/providers/dns/route53/route53_integration_test.go +++ b/providers/dns/route53/route53_integration_test.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" awsconfig "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/route53" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" "github.com/stretchr/testify/require" ) @@ -52,7 +53,7 @@ func TestLiveTTL(t *testing.T) { require.NoError(t, err) for _, v := range resp.ResourceRecordSets { - if deref(v.Name) == fqdn && v.Type == "TXT" && deref(v.TTL) == 10 { + if ptr.Deref(v.Name) == fqdn && v.Type == "TXT" && ptr.Deref(v.TTL) == 10 { return } } diff --git a/providers/dns/volcengine/volcengine.go b/providers/dns/volcengine/volcengine.go index 2fcba1b05..ed5544592 100644 --- a/providers/dns/volcengine/volcengine.go +++ b/providers/dns/volcengine/volcengine.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" "github.com/miekg/dns" "github.com/volcengine/volc-sdk-golang/base" volc "github.com/volcengine/volc-sdk-golang/service/dns" @@ -126,16 +127,16 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("volcengine: get zone ID: %w", err) } - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, deref(zone.ZoneName)) + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, ptr.Deref(zone.ZoneName)) if err != nil { return fmt.Errorf("volcengine: %w", err) } crr := &volc.CreateRecordRequest{ - Host: pointer(subDomain), - TTL: pointer(int64(d.config.TTL)), - Type: pointer("TXT"), - Value: pointer(info.Value), + Host: ptr.Pointer(subDomain), + TTL: ptr.Pointer(int64(d.config.TTL)), + Type: ptr.Pointer("TXT"), + Value: ptr.Pointer(info.Value), ZID: zone.ZID, } @@ -178,8 +179,8 @@ func (d *DNSProvider) getZone(ctx context.Context, fqdn string) (volc.TopZoneRes domain := fqdn[index:] lzr := &volc.ListZonesRequest{ - Key: pointer(dns01.UnFqdn(domain)), - SearchMode: pointer("exact"), + Key: ptr.Pointer(dns01.UnFqdn(domain)), + SearchMode: ptr.Pointer("exact"), } zones, err := d.client.ListZones(ctx, lzr) @@ -187,7 +188,7 @@ func (d *DNSProvider) getZone(ctx context.Context, fqdn string) (volc.TopZoneRes return volc.TopZoneResponse{}, fmt.Errorf("list zones: %w", err) } - total := deref(zones.Total) + total := ptr.Deref(zones.Total) if total == 0 || len(zones.Zones) == 0 { continue @@ -233,14 +234,3 @@ func newClient(config *Config) *volc.Client { return volc.NewClient(caller) } - -func pointer[T any](v T) *T { return &v } - -func deref[T any](v *T) T { - if v == nil { - var zero T - return zero - } - - return *v -} From 65250372eea535b0b38917bc3b9bf78c833390d3 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 10 Dec 2024 15:02:07 +0100 Subject: [PATCH 018/298] fix(cli): use retryable client for ACME server calls (#2368) --- cmd/setup.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/setup.go b/cmd/setup.go index 5e878827d..3fc05038e 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/registration" + "github.com/hashicorp/go-retryablehttp" "github.com/urfave/cli/v2" ) @@ -65,6 +66,12 @@ func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyTy } } + retryClient := retryablehttp.NewClient() + retryClient.RetryMax = 5 + retryClient.HTTPClient = config.HTTPClient + + config.HTTPClient = retryClient.StandardClient() + client, err := lego.NewClient(config) if err != nil { log.Fatalf("Could not create client: %v", err) From eac62e3037f34bf35809f49cac376c2877a4bd91 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 10 Dec 2024 15:22:49 +0100 Subject: [PATCH 019/298] netcup: increase default propagation values (#2379) --- providers/dns/netcup/netcup.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/providers/dns/netcup/netcup.go b/providers/dns/netcup/netcup.go index 014e09a15..b0ef4a2bf 100644 --- a/providers/dns/netcup/netcup.go +++ b/providers/dns/netcup/netcup.go @@ -47,8 +47,8 @@ type Config struct { func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 5*time.Second), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 15*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 30*time.Second), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second), }, From bfe36067932e4594d3baf01cb6545c43b8e1f79c Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 12 Dec 2024 00:03:29 +0100 Subject: [PATCH 020/298] chore: update dependencies (#2381) --- go.mod | 105 +++++---- go.sum | 216 +++++++++--------- providers/dns/huaweicloud/huaweicloud_test.go | 24 +- 3 files changed, 160 insertions(+), 185 deletions(-) diff --git a/go.mod b/go.mod index 052b4dd29..591a21681 100644 --- a/go.mod +++ b/go.mod @@ -16,17 +16,17 @@ require ( github.com/BurntSushi/toml v1.4.0 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 - github.com/aliyun/alibaba-cloud-sdk-go v1.63.47 - github.com/aws/aws-sdk-go-v2 v1.32.3 - github.com/aws/aws-sdk-go-v2/config v1.28.1 - github.com/aws/aws-sdk-go-v2/credentials v1.17.42 - github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.3 - github.com/aws/aws-sdk-go-v2/service/route53 v1.46.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2 - github.com/aws/aws-sdk-go-v2/service/sts v1.32.3 + github.com/aliyun/alibaba-cloud-sdk-go v1.63.66 + github.com/aws/aws-sdk-go-v2 v1.32.6 + github.com/aws/aws-sdk-go-v2/config v1.28.6 + github.com/aws/aws-sdk-go-v2/credentials v1.17.47 + github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.7 + github.com/aws/aws-sdk-go-v2/service/route53 v1.46.3 + github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 github.com/cenkalti/backoff/v4 v4.3.0 github.com/civo/civogo v0.3.11 - github.com/cloudflare/cloudflare-go v0.108.0 + github.com/cloudflare/cloudflare-go v0.111.0 github.com/cpu/goacmedns v0.1.1 github.com/dnsimple/dnsimple-go v1.7.0 github.com/exoscale/egoscale/v3 v3.1.7 @@ -37,11 +37,11 @@ require ( github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/hashicorp/go-retryablehttp v0.7.7 github.com/hashicorp/go-version v1.7.0 - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.120 + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.126 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df github.com/infobloxopen/infoblox-go-client v1.1.1 github.com/labbsr0x/bindman-dns-webhook v1.0.2 - github.com/linode/linodego v1.42.0 + github.com/linode/linodego v1.43.0 github.com/liquidweb/liquidweb-go v1.6.4 github.com/mattn/go-isatty v0.0.20 github.com/miekg/dns v1.1.62 @@ -49,16 +49,16 @@ require ( github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 github.com/nrdcg/auroradns v1.1.0 github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 - github.com/nrdcg/desec v0.8.0 + github.com/nrdcg/desec v0.10.0 github.com/nrdcg/dnspod-go v0.4.0 - github.com/nrdcg/freemyip v0.2.0 + github.com/nrdcg/freemyip v0.3.0 github.com/nrdcg/goinwx v0.10.0 github.com/nrdcg/mailinabox v0.2.0 github.com/nrdcg/namesilo v0.2.1 github.com/nrdcg/nodion v0.1.0 github.com/nrdcg/porkbun v0.4.0 github.com/nzdjb/go-metaname v1.0.0 - github.com/oracle/oci-go-sdk/v65 v65.77.1 + github.com/oracle/oci-go-sdk/v65 v65.80.0 github.com/ovh/go-ovh v1.6.0 github.com/pquerna/otp v1.4.0 github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2 @@ -67,33 +67,33 @@ require ( github.com/sacloud/iaas-api-go v1.12.0 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 github.com/selectel/domains-go v1.1.0 - github.com/selectel/go-selvpcclient/v3 v3.1.1 + github.com/selectel/go-selvpcclient/v3 v3.2.1 github.com/softlayer/softlayer-go v1.1.7 - github.com/stretchr/testify v1.9.0 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1034 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1034 + github.com/stretchr/testify v1.10.0 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1058 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1058 github.com/transip/gotransip/v6 v6.26.0 github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec github.com/urfave/cli/v2 v2.27.5 github.com/vinyldns/go-vinyldns v0.9.16 - github.com/volcengine/volc-sdk-golang v1.0.183 + github.com/volcengine/volc-sdk-golang v1.0.188 github.com/vultr/govultr/v3 v3.9.1 - github.com/yandex-cloud/go-genproto v0.0.0-20241101135610-76a0cfc1a773 - github.com/yandex-cloud/go-sdk v0.0.0-20241101143304-947cf519f6bd - golang.org/x/crypto v0.28.0 - golang.org/x/net v0.30.0 - golang.org/x/oauth2 v0.23.0 - golang.org/x/text v0.19.0 - golang.org/x/time v0.7.0 - google.golang.org/api v0.204.0 + github.com/yandex-cloud/go-genproto v0.0.0-20241206133605-07e4a676108b + github.com/yandex-cloud/go-sdk v0.0.0-20241206142255-6c3760d17eea + golang.org/x/crypto v0.31.0 + golang.org/x/net v0.32.0 + golang.org/x/oauth2 v0.24.0 + golang.org/x/text v0.21.0 + golang.org/x/time v0.8.0 + google.golang.org/api v0.211.0 gopkg.in/ns1/ns1-go.v2 v2.12.2 gopkg.in/yaml.v2 v2.4.0 software.sslmate.com/src/go-pkcs12 v0.5.0 ) require ( - cloud.google.com/go/auth v0.10.0 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect + cloud.google.com/go/auth v0.12.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect @@ -103,19 +103,19 @@ require ( github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.24.3 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3 // indirect - github.com/aws/smithy-go v1.22.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect + github.com/aws/smithy-go v1.22.1 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect @@ -132,16 +132,15 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.16.0 // indirect - github.com/go-resty/resty/v2 v2.13.1 // indirect + github.com/go-resty/resty/v2 v2.15.3 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect - github.com/googleapis/gax-go/v2 v2.13.0 // indirect + github.com/googleapis/gax-go/v2 v2.14.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -163,6 +162,7 @@ require ( github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/peterhellberg/link v1.2.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -187,7 +187,6 @@ require ( github.com/tjfoc/gmsm v1.4.1 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect go.mongodb.org/mongo-driver v1.12.0 // indirect - go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect go.opentelemetry.io/otel v1.29.0 // indirect go.opentelemetry.io/otel/metric v1.29.0 // indirect @@ -195,16 +194,16 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/ratelimit v0.3.0 // indirect - golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect - golang.org/x/mod v0.21.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/tools v0.25.0 // indirect + golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/tools v0.28.0 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 // indirect google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.35.1 // indirect + google.golang.org/protobuf v1.35.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 40bdeb669..097f9c4e6 100644 --- a/go.sum +++ b/go.sum @@ -13,10 +13,10 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/auth v0.10.0 h1:tWlkvFAh+wwTOzXIjrwM64karR1iTBZ/GRr0S/DULYo= -cloud.google.com/go/auth v0.10.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= -cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk= -cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go/auth v0.12.1 h1:n2Bj25BUMM0nvE9D2XLTiImanwZhO3DkfWSYS/SAJP4= +cloud.google.com/go/auth v0.12.1/go.mod h1:BFMu+TNpF3DmvfBO9ClqTR/SiqVIm7LukKF9mbendF4= +cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= +cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -113,8 +113,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/aliyun/alibaba-cloud-sdk-go v1.63.47 h1:B8ApNodSpIM5ST9INmhMG4d0rRwNY/63/XjXUDO/XIo= -github.com/aliyun/alibaba-cloud-sdk-go v1.63.47/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= +github.com/aliyun/alibaba-cloud-sdk-go v1.63.66 h1:DnbaZyCaDJfCeo/0+qiI6XLIHu29NMa6iVXOJdcu6f4= +github.com/aliyun/alibaba-cloud-sdk-go v1.63.66/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -124,48 +124,48 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk= -github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA= -github.com/aws/aws-sdk-go-v2/config v1.28.1 h1:oxIvOUXy8x0U3fR//0eq+RdCKimWI900+SV+10xsCBw= -github.com/aws/aws-sdk-go-v2/config v1.28.1/go.mod h1:bRQcttQJiARbd5JZxw6wG0yIK3eLeSCPdg6uqmmlIiI= -github.com/aws/aws-sdk-go-v2/credentials v1.17.42 h1:sBP0RPjBU4neGpIYyx8mkU2QqLPl5u9cmdTWVzIpHkM= -github.com/aws/aws-sdk-go-v2/credentials v1.17.42/go.mod h1:FwZBfU530dJ26rv9saAbxa9Ej3eF/AK0OAY86k13n4M= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18 h1:68jFVtt3NulEzojFesM/WVarlFpCaXLKaBxDpzkQ9OQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18/go.mod h1:Fjnn5jQVIo6VyedMc0/EhPpfNlPl7dHV916O6B+49aE= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 h1:Jw50LwEkVjuVzE1NzkhNKkBf9cRN7MtE1F/b2cOKTUM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22/go.mod h1:Y/SmAyPcOTmpeVaWSzSKiILfXTVJwrGmYZhcRbhWuEY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 h1:981MHwBaRZM7+9QSR6XamDzF/o7ouUGxFzr+nVSIhrs= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22/go.mod h1:1RA1+aBEfn+CAB/Mh0MB6LsdCYCnjZm7tKXtnk499ZQ= +github.com/aws/aws-sdk-go-v2 v1.32.6 h1:7BokKRgRPuGmKkFMhEg/jSul+tB9VvXhcViILtfG8b4= +github.com/aws/aws-sdk-go-v2 v1.32.6/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= +github.com/aws/aws-sdk-go-v2/config v1.28.6 h1:D89IKtGrs/I3QXOLNTH93NJYtDhm8SYa9Q5CsPShmyo= +github.com/aws/aws-sdk-go-v2/config v1.28.6/go.mod h1:GDzxJ5wyyFSCoLkS+UhGB0dArhb9mI+Co4dHtoTxbko= +github.com/aws/aws-sdk-go-v2/credentials v1.17.47 h1:48bA+3/fCdi2yAwVt+3COvmatZ6jUDNkDTIsqDiMUdw= +github.com/aws/aws-sdk-go-v2/credentials v1.17.47/go.mod h1:+KdckOejLW3Ks3b0E3b5rHsr2f9yuORBum0WPnE5o5w= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 h1:AmoU1pziydclFT/xRV+xXE/Vb8fttJCLRPv8oAkprc0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21/go.mod h1:AjUdLYe4Tgs6kpH4Bv7uMZo7pottoyHMn4eTcIcneaY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 h1:s/fF4+yDQDoElYhfIVvSNyeCydfbuTKzhxSXDXCPasU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25/go.mod h1:IgPfDv5jqFIzQSNbUEMoitNooSMXjRSDkhXv8jiROvU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 h1:ZntTCl5EsYnhN/IygQEUugpdwbhdkom9uHcbCftiGgA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25/go.mod h1:DBdPrgeocww+CSl1C8cEV8PN1mHMBhuCDLpXezyvWkE= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22 h1:yV+hCAHZZYJQcwAaszoBNwLbPItHvApxT0kVIw6jRgs= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22/go.mod h1:kbR1TL8llqB1eGnVbybcA4/wgScxdylOdyAd51yxPdw= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 h1:r67ps7oHCYnflpgDy2LZU0MAQtQbYIOqNNnqGO6xQkE= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25/go.mod h1:GrGY+Q4fIokYLtjCVB/aFfCVL6hhGUFl8inD18fDalE= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3 h1:kT6BcZsmMtNkP/iYMcRG+mIEA/IbeiUimXtGmqF39y0= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3/go.mod h1:Z8uGua2k4PPaGOYn66pK02rhMrot3Xk3tpBuUFPomZU= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 h1:qcxX0JYlgWH3hpPUnd6U0ikcl6LLA9sLkXE2w1fpMvY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3/go.mod h1:cLSNEmI45soc+Ef8K/L+8sEA3A3pYFEYf5B5UI+6bH4= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3 h1:ZC7Y/XgKUxwqcdhO5LE8P6oGP1eh6xlQReWNKfhvJno= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3/go.mod h1:WqfO7M9l9yUAw0HcHaikwRd/H6gzYdz7vjejCA5e2oY= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.3 h1:lcsqV11EaB74iNKr/PaXV0Og1D/lCZIhIf+kPucTfPw= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.3/go.mod h1:IyYNP3fIP5/BvFKqQFj7wwQnKuH0wndcv6j4DyG9pRk= -github.com/aws/aws-sdk-go-v2/service/route53 v1.46.0 h1:AaOWmXBSDSIEsTzx8Y2nYAxckgmBPNiRU5mjn/a9ynI= -github.com/aws/aws-sdk-go-v2/service/route53 v1.46.0/go.mod h1:IN9bx4yLAa3a3J7A41skQefcYObNv6ARAd2i5WxvGKg= -github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2 h1:p9TNFL8bFUMd+38YIpTAXpoxyz0MxC7FlbFEH4P4E1U= -github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2/go.mod h1:fNjyo0Coen9QTwQLWeV6WO2Nytwiu+cCcWaTdKCAqqE= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.3 h1:UTpsIf0loCIWEbrqdLb+0RxnTXfWh2vhw4nQmFi4nPc= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.3/go.mod h1:FZ9j3PFHHAR+w0BSEjK955w5YD2UwB/l/H0yAK3MJvI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3 h1:2YCmIXv3tmiItw0LlYf6v7gEHebLY45kBEnPezbUKyU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3/go.mod h1:u19stRyNPxGhj6dRm+Cdgu6N75qnbW7+QN0q0dsAk58= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.3 h1:wVnQ6tigGsRqSWDEEyH6lSAJ9OyFUsSnbaUWChuSGzs= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.3/go.mod h1:VZa9yTFyj4o10YGsmDO4gbQJUvvhY72fhumT8W4LqsE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 h1:HCpPsWqmYQieU7SS6E9HXfdAMSud0pteVXieJmcpIRI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6/go.mod h1:ngUiVRCco++u+soRRVBIvBZxSMMvOVMXA4PJ36JLfSw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 h1:50+XsN70RS7dwJ2CkVNXzj7U2L1HKP8nqTd3XWEXBN4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6/go.mod h1:WqgLmwY7so32kG01zD8CPTJWVWM+TzJoOVHwTg4aPug= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 h1:BbGDtTi0T1DYlmjBiCr/le3wzhA37O8QTC5/Ab8+EXk= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6/go.mod h1:hLMJt7Q8ePgViKupeymbqI0la+t9/iYFBjxQCFwuAwI= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.7 h1:pVO3tnwny+c+XIfNkmrReAkNd4Gyy7TVvro1ZTfzY4g= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.7/go.mod h1:yveTbfkp9hhabgl3aXbd2/AvWCgJRi0O+mhm3REyvE8= +github.com/aws/aws-sdk-go-v2/service/route53 v1.46.3 h1:pDBrvz7CMK381q5U+nPqtSQZZid5z1XH8lsI6kHNcSY= +github.com/aws/aws-sdk-go-v2/service/route53 v1.46.3/go.mod h1:rDMeB13C/RS0/zw68RQD4LLiWChf5tZBKjEQmjtHa/c= +github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 h1:nyuzXooUNJexRT0Oy0UQY6AhOzxPxhtt4DcBIHyCnmw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0/go.mod h1:sT/iQz8JK3u/5gZkT+Hmr7GzVZehUMkRZpOaAwYXeGY= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 h1:rLnYAfXQ3YAccocshIH5mzNNwZBkBo+bP6EhIxak6Hw= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.7/go.mod h1:ZHtuQJ6t9A/+YDuxOLnbryAmITtr8UysSny3qcyvJTc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 h1:JnhTZR3PiYDNKlXy50/pNeix9aGMo6lLpXwJ1mw8MD4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6/go.mod h1:URronUEGfXZN1VpdktPSD1EkAL9mfrV+2F4sjH38qOY= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 h1:s4074ZO1Hk8qv65GqNXqDjmkf4HSQqJukaLuuW0TpDA= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.2/go.mod h1:mVggCnIWoM09jP71Wh+ea7+5gAp53q+49wDFs1SW5z8= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= -github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= +github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -198,8 +198,8 @@ github.com/civo/civogo v0.3.11 h1:mON/fyrV946Sbk6paRtOSGsN+asCgCmHCgArf5xmGxM= github.com/civo/civogo v0.3.11/go.mod h1:7+GeeFwc4AYTULaEshpT2vIcl3Qq8HPoxA17viX3l6g= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.108.0 h1:C4Skfjd8I8X3uEOGmQUT4/iGyZcWdkIU7HwvMoLkEE0= -github.com/cloudflare/cloudflare-go v0.108.0/go.mod h1:m492eNahT/9MsN7Ppnoge8AaI7QhVFtEgVm3I9HJFeU= +github.com/cloudflare/cloudflare-go v0.111.0 h1:bFgl5OyR7iaV9DkTaoI2jU8X4rXDzEaFDaPfMTp+Ewo= +github.com/cloudflare/cloudflare-go v0.111.0/go.mod h1:w5c4Vm00JjZM+W0mPi6QOC+eWLncGQPURtgDck3z5xU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= @@ -298,8 +298,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g= -github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0= +github.com/go-resty/resty/v2 v2.15.3 h1:bqff+hcqAflpiF591hhJzNdkRsFhlB96CYfBwSFvql8= +github.com/go-resty/resty/v2 v2.15.3/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -332,7 +332,6 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -406,8 +405,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gT github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= -github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= +github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw= github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= @@ -476,8 +475,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.120 h1:i+rlH2xzkEMGbol86Fq/ioxgAaOnX2vkH4i/bLptc5s= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.120/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.126 h1:+Lt/FRG1SfmdS7hxpGDdNdtFgs7kiVyDC3/BXsa3RAw= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.126/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -550,8 +549,8 @@ github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk= github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/linode/linodego v1.42.0 h1:ZSbi4MtvwrfB9Y6bknesorvvueBGGilcmh2D5dq76RM= -github.com/linode/linodego v1.42.0/go.mod h1:2yzmY6pegPBDgx2HDllmt0eIk2IlzqcgK6NR0wFCFRY= +github.com/linode/linodego v1.43.0 h1:sGeBB3caZt7vKBoPS5p4AVzmlG4JoqQOdigIibx3egk= +github.com/linode/linodego v1.43.0/go.mod h1:n4TMFu1UVNala+icHqrTEFFaicYSF74cSAUG5zkTwfA= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= @@ -633,12 +632,12 @@ github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk= github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 h1:ouZ2JWDl8IW5k1qugYbmpbmW8hn85Ig6buSMBRlz3KI= github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3/go.mod h1:ZwadWt7mVhMHMbAQ1w8IhDqtWO3eWqWq72W7trnaiE8= -github.com/nrdcg/desec v0.8.0 h1:FJbRWUAluTCUi9nHFnhqPhLSIHiNnB9elZVWYgFtIqA= -github.com/nrdcg/desec v0.8.0/go.mod h1:BsnYPtSlBttJL3Gyzv0kDH7zkk60obwThlnqiiKzn+o= +github.com/nrdcg/desec v0.10.0 h1:qrEDiqnsvNU9QE7lXIXi/tIHAfyaFXKxF2/8/52O8uM= +github.com/nrdcg/desec v0.10.0/go.mod h1:5+4vyhMRTs49V9CNoODF/HwT8Mwxv9DJ6j+7NekUnBs= github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U= github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= -github.com/nrdcg/freemyip v0.2.0 h1:/GscavT4GVqAY13HExl5UyoB4wlchv6Cg5NYDGsUoJ8= -github.com/nrdcg/freemyip v0.2.0/go.mod h1:HjF0Yz0lSb37HD2ihIyGz9esyGcxbCrrGFLPpKevbx4= +github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc= +github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM= github.com/nrdcg/goinwx v0.10.0 h1:6W630bjDxQD6OuXKqrFRYVpTt0G/9GXXm3CeOrN0zJM= github.com/nrdcg/goinwx v0.10.0/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4= github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk= @@ -679,8 +678,8 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= -github.com/oracle/oci-go-sdk/v65 v65.77.1 h1:gqjTXIUWvTihkn470AclxSAMcR1JecqjD2IUtp+sDIU= -github.com/oracle/oci-go-sdk/v65 v65.77.1/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0= +github.com/oracle/oci-go-sdk/v65 v65.80.0 h1:Rr7QLMozd2DfDBKo6AB3DzLYQxAwuOG118+K5AAD5E8= +github.com/oracle/oci-go-sdk/v65 v65.80.0/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0= github.com/ovh/go-ovh v1.6.0 h1:ixLOwxQdzYDx296sXcgS35TOPEahJkpjMGtzPadCjQI= github.com/ovh/go-ovh v1.6.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -692,6 +691,8 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= +github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c= +github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -772,8 +773,8 @@ github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30/go.mod h1:sH0u6fq6x4R5M7WxkoQ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/selectel/domains-go v1.1.0 h1:futG50J43ALLKQAnZk9H9yOtLGnSUh7c5hSvuC5gSHo= github.com/selectel/domains-go v1.1.0/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA= -github.com/selectel/go-selvpcclient/v3 v3.1.1 h1:C1q2LqqosiapoLpnGITGmysg0YCSQYDo2Gh69CioevM= -github.com/selectel/go-selvpcclient/v3 v3.1.1/go.mod h1:NM7IXhh1IzqZ88DOw1Qc5Ez3tULLViXo95l5+rKPuyQ= +github.com/selectel/go-selvpcclient/v3 v3.2.1 h1:ny6WIAMiHzKxOgOEnwcWE79wIQij1AHHylzPA41MXCw= +github.com/selectel/go-selvpcclient/v3 v3.2.1/go.mod h1:3EfSf8aEWyhspOGbvZ6mvnFg7JN5uckxNyBFPGWsXNQ= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -837,18 +838,17 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1034 h1:T7ewuO2DD+5R2LRpD2kTRy25aCkVDVdYkmmyUS63i08= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1034/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1034 h1:hXxv58/eSlDj80n0P0ISXh91pC/2vqurJNwn5SpXFPI= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1034/go.mod h1:hwTIplwF9IYWz5HQcyw0+R8aqJB0lEZB8sI0pIA5Htw= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1058 h1:VVv5rEFtGbxEB23V3gJO5pFHEXGlOh9duEWEtNm+w6c= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1058/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1058 h1:3pviSO0EEt1arn54TaCcL9QG45YJxtddubJjGFoqK7Q= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1058/go.mod h1:bVNaLGg19+7pJ9Y4nIIR7w2LQ/3PQMRLBA7jcs+cggU= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -866,8 +866,8 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= -github.com/volcengine/volc-sdk-golang v1.0.183 h1:V6M/lhgnBxZS3pLDNwMXSLw+i4VowphNCfVzai6JjWE= -github.com/volcengine/volc-sdk-golang v1.0.183/go.mod h1:u0VtPvlXWpXDTmc9IHkaW1q+5Jjwus4oAqRhNMDRInE= +github.com/volcengine/volc-sdk-golang v1.0.188 h1:s90SUVGPUX2oAVeM/FgzQeSCXZ80XCXsuXliT3s0CtI= +github.com/volcengine/volc-sdk-golang v1.0.188/go.mod h1:u0VtPvlXWpXDTmc9IHkaW1q+5Jjwus4oAqRhNMDRInE= github.com/vultr/govultr/v3 v3.9.1 h1:uxSIb8Miel7tqTs3ee+z3t+JelZikwqBBsZzCOPBy/8= github.com/vultr/govultr/v3 v3.9.1/go.mod h1:Rd8ebpXm7jxH3MDmhnEs+zrlYW212ouhx+HeUMfHm2o= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -881,10 +881,10 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yandex-cloud/go-genproto v0.0.0-20241101135610-76a0cfc1a773 h1:xkWrnYFWxiwCKVbmuOEMR030UCFklpglmOcPv9yJz2c= -github.com/yandex-cloud/go-genproto v0.0.0-20241101135610-76a0cfc1a773/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= -github.com/yandex-cloud/go-sdk v0.0.0-20241101143304-947cf519f6bd h1:LcA5pQoWjS2hhG6bV2ZL9eBEV2wLSVbM2KcpDphYP/w= -github.com/yandex-cloud/go-sdk v0.0.0-20241101143304-947cf519f6bd/go.mod h1:oku4OkbdLLOOpZEz2XxYGXI7rFhxBI5W0cLPmpStdqA= +github.com/yandex-cloud/go-genproto v0.0.0-20241206133605-07e4a676108b h1:+xsB23dmxN3hBSGZLAiyLsUADnqr6ASOiZJmLd8++nk= +github.com/yandex-cloud/go-genproto v0.0.0-20241206133605-07e4a676108b/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-sdk v0.0.0-20241206142255-6c3760d17eea h1:XvnMWpD249l3rhJjDWEAGOQmYZ3Rw0XjEwREDzm9wDs= +github.com/yandex-cloud/go-sdk v0.0.0-20241206142255-6c3760d17eea/go.mod h1:6JH4ZTrHlyTtKwf1VoEGfbHl+or8NFdOyxwYzID0UdI= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -905,8 +905,6 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= @@ -957,8 +955,8 @@ golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIi golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -972,8 +970,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= +golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -999,8 +997,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1052,17 +1050,16 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1076,8 +1073,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1155,8 +1152,8 @@ golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -1165,8 +1162,8 @@ golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1183,17 +1180,16 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1248,8 +1244,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1274,8 +1270,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.204.0 h1:3PjmQQEDkR/ENVZZwIYB4W/KzYtN8OrqnNcHWpeR8E4= -google.golang.org/api v0.204.0/go.mod h1:69y8QSoKIbL9F94bWgWAq6wGqGwyjBgi2y8rAK8zLag= +google.golang.org/api v0.211.0 h1:IUpLjq09jxBSV1lACO33CGY3jsRcbctfGzhj+ZSE/Bg= +google.golang.org/api v0.211.0/go.mod h1:XOloB4MXFH4UTlQSGuNUxw0UT74qdENK8d6JNsXKLi0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1316,10 +1312,10 @@ google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxH google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU= google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4= -google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= -google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ= +google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 h1:IfdSdTcLFy4lqUQrQJLkLt1PB+AsqVz6lwkWPzWEz10= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1353,8 +1349,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/providers/dns/huaweicloud/huaweicloud_test.go b/providers/dns/huaweicloud/huaweicloud_test.go index 02ba1576d..6787650ca 100644 --- a/providers/dns/huaweicloud/huaweicloud_test.go +++ b/providers/dns/huaweicloud/huaweicloud_test.go @@ -20,18 +20,7 @@ func TestNewDNSProvider(t *testing.T) { envVars map[string]string expected string }{ - { - desc: "success", - envVars: map[string]string{ - EnvAccessKeyID: "123", - EnvSecretAccessKey: "456", - EnvRegion: hwregion.CN_EAST_2.Id, - }, - // The "success" cannot be tested because there is an API call that require a valid authentication. - // Also, there is a bug during the error message creation: - // https://github.com/huaweicloud/huaweicloud-sdk-go-v3/pull/81 - expected: "huaweicloud: client build: runtime error: invalid memory address or nil pointer dereference", - }, + // The "success" cannot be tested because there is an API call that require a valid authentication. { desc: "missing credentials", envVars: map[string]string{ @@ -99,16 +88,7 @@ func TestNewDNSProviderConfig(t *testing.T) { region string expected string }{ - { - desc: "success", - accessKeyID: "123", - secretAccessKey: "456", - region: hwregion.CN_EAST_2.Id, - // The "success" cannot be tested because there is an API call that require a valid authentication. - // Also, there is a bug during the error message creation: - // https://github.com/huaweicloud/huaweicloud-sdk-go-v3/pull/81 - expected: "huaweicloud: client build: runtime error: invalid memory address or nil pointer dereference", - }, + // The "success" cannot be tested because there is an API call that require a valid authentication. { desc: "missing credentials", expected: "huaweicloud: credentials missing", From 52e711e04978cd8b20f8da52b370812aa4bf5017 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 20 Dec 2024 13:49:38 +0100 Subject: [PATCH 021/298] Add DNS provider for ManageEngine CloudDNS (#2365) --- README.md | 35 ++- cmd/zz_gen_cmd_dnshelp.go | 22 ++ docs/content/dns/zz_gen_manageengine.md | 69 +++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/manageengine/internal/client.go | 197 +++++++++++++ .../dns/manageengine/internal/client_test.go | 262 ++++++++++++++++++ .../manageengine/internal/fixtures/error.json | 3 + .../internal/fixtures/error_bad_request.json | 3 + .../internal/fixtures/zone_domains_all.json | 146 ++++++++++ .../internal/fixtures/zone_record_create.json | 3 + .../internal/fixtures/zone_record_delete.json | 3 + .../internal/fixtures/zone_record_update.json | 3 + .../internal/fixtures/zone_records_all.json | 40 +++ .../dns/manageengine/internal/identity.go | 20 ++ providers/dns/manageengine/internal/types.go | 63 +++++ providers/dns/manageengine/manageengine.go | 262 ++++++++++++++++++ providers/dns/manageengine/manageengine.toml | 24 ++ .../dns/manageengine/manageengine_test.go | 143 ++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 19 files changed, 1287 insertions(+), 16 deletions(-) create mode 100644 docs/content/dns/zz_gen_manageengine.md create mode 100644 providers/dns/manageengine/internal/client.go create mode 100644 providers/dns/manageengine/internal/client_test.go create mode 100644 providers/dns/manageengine/internal/fixtures/error.json create mode 100644 providers/dns/manageengine/internal/fixtures/error_bad_request.json create mode 100644 providers/dns/manageengine/internal/fixtures/zone_domains_all.json create mode 100644 providers/dns/manageengine/internal/fixtures/zone_record_create.json create mode 100644 providers/dns/manageengine/internal/fixtures/zone_record_delete.json create mode 100644 providers/dns/manageengine/internal/fixtures/zone_record_update.json create mode 100644 providers/dns/manageengine/internal/fixtures/zone_records_all.json create mode 100644 providers/dns/manageengine/internal/identity.go create mode 100644 providers/dns/manageengine/internal/types.go create mode 100644 providers/dns/manageengine/manageengine.go create mode 100644 providers/dns/manageengine/manageengine.toml create mode 100644 providers/dns/manageengine/manageengine_test.go diff --git a/README.md b/README.md index 53e9e529e..cebd033c2 100644 --- a/README.md +++ b/README.md @@ -152,85 +152,90 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). LuaDNS Mail-in-a-Box + ManageEngine CloudDNS Manual Metaname mijn.host - Mittwald + Mittwald MyDNS.jp MythicBeasts Name.com - Namecheap + Namecheap Namesilo NearlyFreeSpeech.NET Netcup - Netlify + Netlify Nicmanager NIFCloud Njalla - Nodion + Nodion NS1 Open Telekom Cloud Oracle Cloud - OVH + OVH plesk.com Porkbun PowerDNS - Rackspace + Rackspace Rain Yun/雨云 RcodeZero reg.ru - Regfish + Regfish RFC2136 RimuHosting Sakura Cloud - Scaleway + Scaleway Selectel Selectel v2 SelfHost.(de|eu) - Servercow + Servercow Shellrent Simply.com Sonic - Stackpath + Stackpath Technitium Tencent Cloud DNS Timeweb Cloud - TransIP + TransIP UKFast SafeDNS Ultradns Variomedia - VegaDNS + VegaDNS Vercel Versio.[nl|eu|uk] VinylDNS - VK Cloud + VK Cloud Volcano Engine/火山引擎 Vscale Vultr - Webnames + Webnames Websupport WEDOS West.cn/西部数码 - 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 b7f6e6c8c..e5ae3b46d 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -92,6 +92,7 @@ func allDNSCodes() string { "loopia", "luadns", "mailinabox", + "manageengine", "metaname", "mijnhost", "mittwald", @@ -1858,6 +1859,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/mailinabox`) + case "manageengine": + // generated from: providers/dns/manageengine/manageengine.toml + ew.writeln(`Configuration for ManageEngine CloudDNS.`) + ew.writeln(`Code: 'manageengine'`) + ew.writeln(`Since: 'v4.21.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "MANAGEENGINE_CLIENT_ID": Client ID`) + ew.writeln(` - "MANAGEENGINE_CLIENT_SECRET": Client Secret`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "MANAGEENGINE_HTTP_TIMEOUT": API request timeout`) + ew.writeln(` - "MANAGEENGINE_POLLING_INTERVAL": Time between DNS propagation check`) + ew.writeln(` - "MANAGEENGINE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "MANAGEENGINE_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/manageengine`) + case "metaname": // generated from: providers/dns/metaname/metaname.toml ew.writeln(`Configuration for Metaname.`) diff --git a/docs/content/dns/zz_gen_manageengine.md b/docs/content/dns/zz_gen_manageengine.md new file mode 100644 index 000000000..32266f2d2 --- /dev/null +++ b/docs/content/dns/zz_gen_manageengine.md @@ -0,0 +1,69 @@ +--- +title: "ManageEngine CloudDNS" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: manageengine +dnsprovider: + since: "v4.21.0" + code: "manageengine" + url: "https://clouddns.manageengine.com" +--- + + + + + + +Configuration for [ManageEngine CloudDNS](https://clouddns.manageengine.com). + + + + +- Code: `manageengine` +- Since: v4.21.0 + + +Here is an example bash command using the ManageEngine CloudDNS provider: + +```bash +MANAGEENGINE_CLIENT_ID="xxx" \ +MANAGEENGINE_CLIENT_SECRET="yyy" \ +lego --email you@example.com --dns manageengine -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `MANAGEENGINE_CLIENT_ID` | Client ID | +| `MANAGEENGINE_CLIENT_SECRET` | Client Secret | + +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 | +|--------------------------------|-------------| +| `MANAGEENGINE_HTTP_TIMEOUT` | API request timeout | +| `MANAGEENGINE_POLLING_INTERVAL` | Time between DNS propagation check | +| `MANAGEENGINE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `MANAGEENGINE_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://pitstop.manageengine.com/portal/en/kb/articles/manageengine-clouddns-rest-api-documentation) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 638a596ae..84615c54d 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -143,7 +143,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, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, 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, manageengine, manual, metaname, mijnhost, mittwald, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/manageengine/internal/client.go b/providers/dns/manageengine/internal/client.go new file mode 100644 index 000000000..89c426b02 --- /dev/null +++ b/providers/dns/manageengine/internal/client.go @@ -0,0 +1,197 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const defaultBaseURL = "https://clouddns.manageengine.com/v1" + +// Client the ManageEngine CloudDNS API client. +type Client struct { + baseURL *url.URL + httpClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(ctx context.Context, clientID, clientSecret string) *Client { + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + baseURL: baseURL, + httpClient: createOAuthClient(ctx, clientID, clientSecret), + } +} + +// GetAllZones gets all zones. +// https://pitstop.manageengine.com/portal/en/kb/articles/manageengine-clouddns-rest-api-documentation#GET_All +func (c *Client) GetAllZones(ctx context.Context) ([]Zone, error) { + endpoint := c.baseURL.JoinPath("dns", "domain") + + req, err := newRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var results []Zone + + err = c.do(req, &results) + if err != nil { + return nil, err + } + + return results, nil +} + +// GetAllZoneRecords gets all "zone records" for a zone. +// https://pitstop.manageengine.com/portal/en/kb/articles/manageengine-clouddns-rest-api-documentation#GET_All_9 +func (c *Client) GetAllZoneRecords(ctx context.Context, zoneID int) ([]ZoneRecord, error) { + endpoint := c.baseURL.JoinPath("dns", "domain", strconv.Itoa(zoneID), "records", "SPF_TXT") + + req, err := newRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var results []ZoneRecord + + err = c.do(req, &results) + if err != nil { + return nil, err + } + + return results, nil +} + +// DeleteZoneRecord deletes a "zone record". +// https://pitstop.manageengine.com/portal/en/kb/articles/manageengine-clouddns-rest-api-documentation#DEL_Delete_10 +func (c *Client) DeleteZoneRecord(ctx context.Context, zoneID int, domainID int) error { + endpoint := c.baseURL.JoinPath("dns", "domain", strconv.Itoa(zoneID), "records", "SPF_TXT", strconv.Itoa(domainID)) + + req, err := newRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + var results APIResponse + + return c.do(req, &results) +} + +// CreateZoneRecord creates a "zone record". +// https://pitstop.manageengine.com/portal/en/kb/articles/manageengine-clouddns-rest-api-documentation#POST_Create_10 +func (c *Client) CreateZoneRecord(ctx context.Context, zoneID int, record ZoneRecord) error { + endpoint := c.baseURL.JoinPath("dns", "domain", strconv.Itoa(zoneID), "records", "SPF_TXT", "/") + + req, err := newRequest(ctx, http.MethodPost, endpoint, []ZoneRecord{record}) + if err != nil { + return err + } + + var results APIResponse + + return c.do(req, &results) +} + +// UpdateZoneRecord update an existing "zone record". +// https://pitstop.manageengine.com/portal/en/kb/articles/manageengine-clouddns-rest-api-documentation#PUT_Update_10 +func (c *Client) UpdateZoneRecord(ctx context.Context, record ZoneRecord) error { + if record.SpfTxtDomainID == 0 { + return errors.New("SpfTxtDomainID is empty") + } + if record.ZoneID == 0 { + return errors.New("ZoneID is empty") + } + + endpoint := c.baseURL.JoinPath("dns", "domain", strconv.Itoa(record.ZoneID), "records", "SPF_TXT", strconv.Itoa(record.SpfTxtDomainID), "/") + + req, err := newRequest(ctx, http.MethodPut, endpoint, []ZoneRecord{record}) + if err != nil { + return err + } + + var results APIResponse + + return c.do(req, &results) +} + +func (c *Client) do(req *http.Request, result any) error { + resp, err := c.httpClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + var body io.Reader = http.NoBody + + if payload != nil { + buf := new(bytes.Buffer) + + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + + values := url.Values{} + values.Set("config", buf.String()) + body = strings.NewReader(values.Encode()) + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), body) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return fmt.Errorf("[status code: %d] %w", resp.StatusCode, &errAPI) +} diff --git a/providers/dns/manageengine/internal/client_test.go b/providers/dns/manageengine/internal/client_test.go new file mode 100644 index 000000000..edf046222 --- /dev/null +++ b/providers/dns/manageengine/internal/client_test.go @@ -0,0 +1,262 @@ +package internal + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupTest(t *testing.T, pattern string, status int, filename string) *Client { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { + if filename == "" { + rw.WriteHeader(status) + return + } + + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + defer func() { _ = file.Close() }() + + rw.WriteHeader(status) + _, err = io.Copy(rw, file) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + }) + + client := NewClient(context.Background(), "abc", "secret") + + client.httpClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) + + return client +} + +func TestClient_GetAllZones(t *testing.T) { + client := setupTest(t, "GET /dns/domain", http.StatusOK, "zone_domains_all.json") + + groups, err := client.GetAllZones(context.Background()) + require.NoError(t, err) + + expected := []Zone{ + { + ZoneID: 1, + ZoneName: "test.com.", + ZoneTTL: 500, + ZoneTargeting: true, + Refresh: 43200, + Retry: 3600, + Expiry: 1209600, + Minimum: 180, + Org: 2, + NsID: 1, + Serial: 2022042206, + Nss: []string{"ns11.zns-53.com.", "ns21.zns-53.net.", "ns31.zns-53.com.", "ns41.zns-53.net."}, + }, + { + ZoneID: 2, + ZoneName: "yourdomain.com.", + ZoneTTL: 1000, + Refresh: 43200, + Retry: 3600, + Expiry: 1209600, + Minimum: 180, + Org: 2, + Vanity: true, + NsID: 1, + Serial: 2022040608, + Nss: []string{"ns11.yourdomain.com.", "ns21.yourdomain.net.", "ns31.yourdomain.com.", "ns41.yourdomain.net."}, + }, + { + ZoneID: 20, + ZoneName: "hello45.com.", + ZoneTTL: 3000, + Refresh: 43200, + Retry: 3600, + Expiry: 1209600, + Minimum: 180, + Org: 2, + NsID: 1, + Serial: 2022040711, + Nss: []string{"ns11.zns-53.com.", "ns21.zns-53.net.", "ns31.zns-53.com.", "ns41.zns-53.net."}, + }, + { + ZoneID: 22, + ZoneName: "zohoaccl.com.", + ZoneTTL: 300, + ZoneTargeting: true, + Refresh: 43200, + Retry: 3600, + Expiry: 1209600, + Minimum: 180, + Org: 2, + NsID: 1, + Serial: 2022042206, + Nss: []string{"ns11.zns-53.com.", "ns21.zns-53.net.", "ns31.zns-53.com.", "ns41.zns-53.net."}, + }, + { + ZoneID: 23, + ZoneName: "zohocal.com.", + ZoneTTL: 300, + ZoneTargeting: true, + Refresh: 43200, + Retry: 3600, + Expiry: 1209600, + Minimum: 180, + Org: 2, + NsID: 1, + Serial: 2022041310, + Nss: []string{"ns11.zns-53.com.", "ns21.zns-53.net.", "ns31.zns-53.com.", "ns41.zns-53.net."}, + }, + } + + assert.Equal(t, expected, groups) +} + +func TestClient_GetAllZones_error(t *testing.T) { + client := setupTest(t, "GET /dns/domain", http.StatusUnauthorized, "error.json") + + _, err := client.GetAllZones(context.Background()) + require.Error(t, err) + + require.EqualError(t, err, "[status code: 401] Authentication credentials were not provided.") +} + +func TestClient_GetAllZoneRecords(t *testing.T) { + client := setupTest(t, "GET /dns/domain/4/records/SPF_TXT", http.StatusOK, "zone_records_all.json") + + groups, err := client.GetAllZoneRecords(context.Background(), 4) + require.NoError(t, err) + + expected := []ZoneRecord{ + { + ZoneID: 4, + SpfTxtDomainID: 6, + DomainName: "spftest.example.com.", + DomainTTL: 300, + DomainLocationID: 1, + RecordType: "SPF", + Records: []Record{{ + ID: 1, + Values: []string{"necwcltpwxbz-noelget3jush-vop2xxvapot3eyq_0"}, + DomainID: 6, + }}, + }, + { + ZoneID: 4, + SpfTxtDomainID: 13, + DomainName: "txt.example.com.", + DomainTTL: 300, + DomainLocationID: 1, + RecordType: "TXT", + Records: []Record{{ + ID: 1, + Values: []string{"v=spf1include:transmail.netinclude:example.com~all", "c-68e3oc4trm8w7piplscg7vgojmtkjrnrabr4king8"}, + DomainID: 13, + }}, + }, + } + + assert.Equal(t, expected, groups) +} + +func TestClient_GetAllZoneRecords_error(t *testing.T) { + client := setupTest(t, "GET /dns/domain/4/records/SPF_TXT", http.StatusUnauthorized, "error.json") + + _, err := client.GetAllZoneRecords(context.Background(), 4) + require.Error(t, err) + + require.EqualError(t, err, "[status code: 401] Authentication credentials were not provided.") +} + +func TestClient_DeleteZoneRecord(t *testing.T) { + client := setupTest(t, "DELETE /dns/domain/4/records/SPF_TXT/6", http.StatusOK, "zone_record_delete.json") + + err := client.DeleteZoneRecord(context.Background(), 4, 6) + require.NoError(t, err) +} + +func TestClient_DeleteZoneRecord_error(t *testing.T) { + client := setupTest(t, "DELETE /dns/domain/4/records/SPF_TXT/6", http.StatusUnauthorized, "error.json") + + err := client.DeleteZoneRecord(context.Background(), 4, 6) + require.Error(t, err) + + require.EqualError(t, err, "[status code: 401] Authentication credentials were not provided.") +} + +func TestClient_CreateZoneRecord(t *testing.T) { + client := setupTest(t, "POST /dns/domain/4/records/SPF_TXT/", http.StatusOK, "zone_record_create.json") + + record := ZoneRecord{} + + err := client.CreateZoneRecord(context.Background(), 4, record) + require.NoError(t, err) +} + +func TestClient_CreateZoneRecord_error(t *testing.T) { + client := setupTest(t, "POST /dns/domain/4/records/SPF_TXT/", http.StatusUnauthorized, "error.json") + + record := ZoneRecord{} + + err := client.CreateZoneRecord(context.Background(), 4, record) + require.Error(t, err) + + require.EqualError(t, err, "[status code: 401] Authentication credentials were not provided.") +} + +func TestClient_CreateZoneRecord_error_bad_request(t *testing.T) { + client := setupTest(t, "POST /dns/domain/4/records/SPF_TXT/", http.StatusBadRequest, "error_bad_request.json") + + record := ZoneRecord{} + + err := client.CreateZoneRecord(context.Background(), 4, record) + require.Error(t, err) + + require.EqualError(t, err, "[status code: 400] Invalid record format, Record should be in list.") +} + +func TestClient_UpdateZoneRecord(t *testing.T) { + client := setupTest(t, "PUT /dns/domain/4/records/SPF_TXT/6/", http.StatusOK, "zone_record_update.json") + + record := ZoneRecord{ + SpfTxtDomainID: 6, + ZoneID: 4, + } + + err := client.UpdateZoneRecord(context.Background(), record) + require.NoError(t, err) +} + +func TestClient_UpdateZoneRecord_error(t *testing.T) { + client := setupTest(t, "PUT /dns/domain/4/records/SPF_TXT/6/", http.StatusUnauthorized, "error.json") + + record := ZoneRecord{ + SpfTxtDomainID: 6, + ZoneID: 4, + } + + err := client.UpdateZoneRecord(context.Background(), record) + require.Error(t, err) + + require.EqualError(t, err, "[status code: 401] Authentication credentials were not provided.") +} diff --git a/providers/dns/manageengine/internal/fixtures/error.json b/providers/dns/manageengine/internal/fixtures/error.json new file mode 100644 index 000000000..5cd198670 --- /dev/null +++ b/providers/dns/manageengine/internal/fixtures/error.json @@ -0,0 +1,3 @@ +{ + "detail": "Authentication credentials were not provided." +} diff --git a/providers/dns/manageengine/internal/fixtures/error_bad_request.json b/providers/dns/manageengine/internal/fixtures/error_bad_request.json new file mode 100644 index 000000000..944cef6c0 --- /dev/null +++ b/providers/dns/manageengine/internal/fixtures/error_bad_request.json @@ -0,0 +1,3 @@ +{ + "error": "Invalid record format, Record should be in list." +} diff --git a/providers/dns/manageengine/internal/fixtures/zone_domains_all.json b/providers/dns/manageengine/internal/fixtures/zone_domains_all.json new file mode 100644 index 000000000..3e37f52a7 --- /dev/null +++ b/providers/dns/manageengine/internal/fixtures/zone_domains_all.json @@ -0,0 +1,146 @@ +[ + { + "zone_id": 1, + "zone_name": "test.com.", + "zone_ttl": 500, + "zone_type": 0, + "zone_targeting": true, + "zone_logging": "{}", + "zone_contact": "mathes.zoho.com", + "refresh": 43200, + "retry": 3600, + "expiry": 1209600, + "minimum": 180, + "org": 2, + "any_query": false, + "dnssec": true, + "vanity": false, + "ns_id": 1, + "serial": 2022042206, + "ns": [ + "ns11.zns-53.com.", + "ns21.zns-53.net.", + "ns31.zns-53.com.", + "ns41.zns-53.net." + ], + "contact_group": [ + "test_contact1", + "test_contact2" + ], + "ds": [ + { + "record_id": 59, + "keyTag": 36938, + "algorithm": 13, + "digestType": 1, + "digest": "e9f03d176455d5d16f826b69f9ecb11f59be35e7", + "domain_id": 30 + }, + { + "record_id": 60, + "keyTag": 36938, + "algorithm": 13, + "digestType": 2, + "digest": "7ea640a8668eafd9d89a9b2e9994f5fcfb1dee0668d1e93ba556aa57ac047f96", + "domain_id": 30 + } + ] + }, + { + "zone_id": 2, + "zone_name": "yourdomain.com.", + "zone_ttl": 1000, + "zone_type": 0, + "zone_targeting": false, + "zone_logging": "{}", + "zone_contact": "contact.yourdomain.com", + "refresh": 43200, + "retry": 3600, + "expiry": 1209600, + "minimum": 180, + "org": 2, + "any_query": false, + "dnssec": false, + "vanity": true, + "vanity_grp": "yourdomain", + "ns_id": 1, + "serial": 2022040608, + "ns": [ + "ns11.yourdomain.com.", + "ns21.yourdomain.net.", + "ns31.yourdomain.com.", + "ns41.yourdomain.net." + ] + }, + { + "zone_id": 20, + "zone_name": "hello45.com.", + "zone_ttl": 3000, + "zone_targeting": false, + "zone_logging": "{}", + "zone_contact": "mathes.zoho.com", + "refresh": 43200, + "retry": 3600, + "expiry": 1209600, + "minimum": 180, + "org": 2, + "any_query": false, + "dnssec": false, + "ns_id": 1, + "serial": 2022040711, + "ns": [ + "ns11.zns-53.com.", + "ns21.zns-53.net.", + "ns31.zns-53.com.", + "ns41.zns-53.net." + ] + }, + { + "zone_id": 22, + "zone_name": "zohoaccl.com.", + "zone_ttl": 300, + "zone_type": 0, + "zone_targeting": true, + "zone_logging": "{}", + "zone_contact": "networkone.zohocorp.com", + "refresh": 43200, + "retry": 3600, + "expiry": 1209600, + "minimum": 180, + "org": 2, + "any_query": false, + "dnssec": false, + "ns_id": 1, + "serial": 2022042206, + "ns": [ + "ns11.zns-53.com.", + "ns21.zns-53.net.", + "ns31.zns-53.com.", + "ns41.zns-53.net." + ] + }, + { + "zone_id": 23, + "zone_name": "zohocal.com.", + "zone_ttl": 300, + "zone_type": 0, + "zone_targeting": true, + "zone_logging": "{}", + "zone_contact": "mathes.zoho.com", + "refresh": 43200, + "retry": 3600, + "expiry": 1209600, + "minimum": 180, + "org": 2, + "any_query": false, + "dnssec": false, + "ns_id": 1, + "serial": 2022041310, + "ns": [ + "ns11.zns-53.com.", + "ns21.zns-53.net.", + "ns31.zns-53.com.", + "ns41.zns-53.net." + ] + } +] diff --git a/providers/dns/manageengine/internal/fixtures/zone_record_create.json b/providers/dns/manageengine/internal/fixtures/zone_record_create.json new file mode 100644 index 000000000..3fd216f2d --- /dev/null +++ b/providers/dns/manageengine/internal/fixtures/zone_record_create.json @@ -0,0 +1,3 @@ +{ + "message": "Record created successfully" +} diff --git a/providers/dns/manageengine/internal/fixtures/zone_record_delete.json b/providers/dns/manageengine/internal/fixtures/zone_record_delete.json new file mode 100644 index 000000000..c657d84ea --- /dev/null +++ b/providers/dns/manageengine/internal/fixtures/zone_record_delete.json @@ -0,0 +1,3 @@ +{ + "message": "Record deleted successfully" +} diff --git a/providers/dns/manageengine/internal/fixtures/zone_record_update.json b/providers/dns/manageengine/internal/fixtures/zone_record_update.json new file mode 100644 index 000000000..178c1fb0f --- /dev/null +++ b/providers/dns/manageengine/internal/fixtures/zone_record_update.json @@ -0,0 +1,3 @@ +{ + "message": "Record updated successfully" +} diff --git a/providers/dns/manageengine/internal/fixtures/zone_records_all.json b/providers/dns/manageengine/internal/fixtures/zone_records_all.json new file mode 100644 index 000000000..ae08a4c7e --- /dev/null +++ b/providers/dns/manageengine/internal/fixtures/zone_records_all.json @@ -0,0 +1,40 @@ +[ + { + "spf_txt_domain_id": 6, + "zone_id": 4, + "domain_name": "spftest.example.com.", + "domain_ttl": 300, + "domain_location_id": 1, + "record_type": "SPF", + "records": [ + { + "record_id": 1, + "value": [ + "necwcltpwxbz-noelget3jush-vop2xxvapot3eyq_0" + ], + "disabled": false, + "domain_id": 6 + } + ] + }, + { + "spf_txt_domain_id": 13, + "zone_id": 4, + "domain_name": "txt.example.com.", + "domain_ttl": 300, + "domain_maxhost": 1, + "domain_location_id": 1, + "record_type": "TXT", + "records": [ + { + "record_id": 1, + "value": [ + "v=spf1include:transmail.netinclude:example.com~all", + "c-68e3oc4trm8w7piplscg7vgojmtkjrnrabr4king8" + ], + "disabled": false, + "domain_id": 13 + } + ] + } +] diff --git a/providers/dns/manageengine/internal/identity.go b/providers/dns/manageengine/internal/identity.go new file mode 100644 index 000000000..66a659188 --- /dev/null +++ b/providers/dns/manageengine/internal/identity.go @@ -0,0 +1,20 @@ +package internal + +import ( + "context" + "net/http" + + "golang.org/x/oauth2/clientcredentials" +) + +const defaultAuthURL = "https://clouddns.manageengine.com/oauth2/token/" + +func createOAuthClient(ctx context.Context, clientID, clientSecret string) *http.Client { + config := &clientcredentials.Config{ + TokenURL: defaultAuthURL, + ClientID: clientID, + ClientSecret: clientSecret, + } + + return config.Client(ctx) +} diff --git a/providers/dns/manageengine/internal/types.go b/providers/dns/manageengine/internal/types.go new file mode 100644 index 000000000..7a039f67f --- /dev/null +++ b/providers/dns/manageengine/internal/types.go @@ -0,0 +1,63 @@ +package internal + +import ( + "strings" +) + +type APIError struct { + Message string `json:"error"` + Detail string `json:"detail"` +} + +func (a *APIError) Error() string { + var msg []string + + if a.Message != "" { + msg = append(msg, a.Message) + } + + if a.Detail != "" { + msg = append(msg, a.Detail) + } + + return strings.Join(msg, " ") +} + +type APIResponse struct { + Message string `json:"message,omitempty"` +} + +type ZoneRecord struct { + ZoneID int `json:"zone_id,omitempty"` + SpfTxtDomainID int `json:"spf_txt_domain_id,omitempty"` + DomainName string `json:"domain_name,omitempty"` + DomainTTL int `json:"domain_ttl,omitempty"` + DomainLocationID int `json:"domain_location_id,omitempty"` + RecordType string `json:"record_type,omitempty"` + Records []Record `json:"records"` +} + +type Record struct { + ID int `json:"record_id,omitempty"` + Values []string `json:"value,omitempty"` + Disabled bool `json:"disabled,omitempty"` + DomainID int `json:"domain_id,omitempty"` +} + +type Zone struct { + ZoneID int `json:"zone_id"` + ZoneName string `json:"zone_name"` + ZoneTTL int `json:"zone_ttl"` + ZoneType int `json:"zone_type,omitempty"` + ZoneTargeting bool `json:"zone_targeting"` + Refresh int `json:"refresh"` + Retry int `json:"retry"` + Expiry int `json:"expiry"` + Minimum int `json:"minimum"` + Org int `json:"org"` + AnyQuery bool `json:"any_query"` + Vanity bool `json:"vanity,omitempty"` + NsID int `json:"ns_id"` + Serial int `json:"serial"` + Nss []string `json:"ns"` +} diff --git a/providers/dns/manageengine/manageengine.go b/providers/dns/manageengine/manageengine.go new file mode 100644 index 000000000..f26ae33b5 --- /dev/null +++ b/providers/dns/manageengine/manageengine.go @@ -0,0 +1,262 @@ +// Package manageengine implements a DNS provider for solving the DNS-01 challenge using ManageEngine CloudDNS. +package manageengine + +import ( + "context" + "errors" + "fmt" + "slices" + "strings" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/manageengine/internal" +) + +// Environment variables names. +const ( + envNamespace = "MANAGEENGINE_" + + EnvClientID = envNamespace + "CLIENT_ID" + EnvClientSecret = envNamespace + "CLIENT_SECRET" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" +) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + ClientID string + ClientSecret string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int +} + +// 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), + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for ManageEngine CloudDNS. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvClientID, EnvClientSecret) + if err != nil { + return nil, fmt.Errorf("manageengine: %w", err) + } + + config := NewDefaultConfig() + config.ClientID = values[EnvClientID] + config.ClientSecret = values[EnvClientSecret] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for ManageEngine CloudDNS. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("manageengine: the configuration of the DNS provider is nil") + } + + if config.ClientID == "" || config.ClientSecret == "" { + return nil, errors.New("manageengine: credentials missing") + } + + client := internal.NewClient(context.Background(), config.ClientID, config.ClientSecret) + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("manageengine: could not find zone for domain %q: %w", domain, err) + } + + zoneID, err := d.findZoneID(ctx, authZone) + if err != nil { + return fmt.Errorf("manageengine: find zone ID: %w", err) + } + + zoneRecord, err := d.findZoneRecord(ctx, zoneID, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("manageengine: find zone record: %w", err) + } + + // Update the existing zone record. + if zoneRecord != nil { + for _, record := range zoneRecord.Records { + if slices.Contains(record.Values, info.Value) { + continue + } + + zr := internal.ZoneRecord{ + ZoneID: zoneID, + SpfTxtDomainID: zoneRecord.SpfTxtDomainID, + DomainName: info.EffectiveFQDN, + DomainTTL: d.config.TTL, + RecordType: "TXT", + Records: []internal.Record{{ + Values: append(record.Values, info.Value), + DomainID: zoneRecord.SpfTxtDomainID, + }}, + } + + // Update the zone record. + err = d.client.UpdateZoneRecord(ctx, zr) + if err != nil { + return fmt.Errorf("manageengine: update zone record: %w", err) + } + + return nil + } + + return errors.New("manageengine: zone already contains the TXT record value") + } + + // Create a new zone record. + record := internal.ZoneRecord{ + ZoneID: zoneID, + DomainName: info.EffectiveFQDN, + DomainTTL: d.config.TTL, + RecordType: "TXT", + Records: []internal.Record{{ + Values: []string{info.Value}, + }}, + } + + err = d.client.CreateZoneRecord(ctx, zoneID, record) + if err != nil { + return fmt.Errorf("manageengine: create zone record: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("manageengine: could not find zone for domain %q: %w", domain, err) + } + + zoneID, err := d.findZoneID(ctx, authZone) + if err != nil { + return fmt.Errorf("manageengine: find zone ID: %w", err) + } + + zoneRecord, err := d.findZoneRecord(ctx, zoneID, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("manageengine: find zone record: %w", err) + } + + for _, record := range zoneRecord.Records { + if !slices.Contains(record.Values, info.Value) { + continue + } + + // Delete the zone record. + if len(record.Values) <= 1 { + err = d.client.DeleteZoneRecord(ctx, zoneID, zoneRecord.SpfTxtDomainID) + if err != nil { + return fmt.Errorf("manageengine: delete zone record: %w", err) + } + + return nil + } + + // Update the zone record. + var values []string + for _, value := range record.Values { + if value != info.Value { + values = append(values, value) + } + } + + zr := internal.ZoneRecord{ + ZoneID: zoneID, + SpfTxtDomainID: zoneRecord.SpfTxtDomainID, + DomainName: info.EffectiveFQDN, + DomainTTL: d.config.TTL, + RecordType: "TXT", + Records: []internal.Record{{ + Values: values, + DomainID: zoneRecord.SpfTxtDomainID, + }}, + } + + err = d.client.UpdateZoneRecord(ctx, zr) + if err != nil { + return fmt.Errorf("manageengine: create zone record: %w", err) + } + + return nil + } + + 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 +} + +func (d *DNSProvider) findZoneID(ctx context.Context, authZone string) (int, error) { + zones, err := d.client.GetAllZones(ctx) + if err != nil { + return 0, fmt.Errorf("get all zone groups: %w", err) + } + + for _, zone := range zones { + if strings.EqualFold(zone.ZoneName, authZone) { + return zone.ZoneID, nil + } + } + + return 0, fmt.Errorf("zone not found %s", authZone) +} + +func (d *DNSProvider) findZoneRecord(ctx context.Context, zoneID int, fqdn string) (*internal.ZoneRecord, error) { + zoneRecords, err := d.client.GetAllZoneRecords(ctx, zoneID) + if err != nil { + return nil, fmt.Errorf("get all zone records: %w", err) + } + + for _, zoneRecord := range zoneRecords { + if !strings.EqualFold(zoneRecord.DomainName, fqdn) { + continue + } + + if strings.EqualFold(zoneRecord.RecordType, "TXT") { + return &zoneRecord, nil + } + } + + return nil, nil +} diff --git a/providers/dns/manageengine/manageengine.toml b/providers/dns/manageengine/manageengine.toml new file mode 100644 index 000000000..dea92b3e6 --- /dev/null +++ b/providers/dns/manageengine/manageengine.toml @@ -0,0 +1,24 @@ +Name = "ManageEngine CloudDNS" +Description = '''''' +URL = "https://clouddns.manageengine.com" +Code = "manageengine" +Since = "v4.21.0" + +Example = ''' +MANAGEENGINE_CLIENT_ID="xxx" \ +MANAGEENGINE_CLIENT_SECRET="yyy" \ +lego --email you@example.com --dns manageengine -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + MANAGEENGINE_CLIENT_ID = "Client ID" + MANAGEENGINE_CLIENT_SECRET = "Client Secret" + [Configuration.Additional] + MANAGEENGINE_POLLING_INTERVAL = "Time between DNS propagation check" + MANAGEENGINE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" + MANAGEENGINE_TTL = "The TTL of the TXT record used for the DNS challenge" + MANAGEENGINE_HTTP_TIMEOUT = "API request timeout" + +[Links] + API = "https://pitstop.manageengine.com/portal/en/kb/articles/manageengine-clouddns-rest-api-documentation" diff --git a/providers/dns/manageengine/manageengine_test.go b/providers/dns/manageengine/manageengine_test.go new file mode 100644 index 000000000..624459be9 --- /dev/null +++ b/providers/dns/manageengine/manageengine_test.go @@ -0,0 +1,143 @@ +package manageengine + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvClientID, EnvClientSecret).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvClientID: "abc", + EnvClientSecret: "secret", + }, + }, + { + desc: "missing client ID", + envVars: map[string]string{ + EnvClientID: "", + EnvClientSecret: "secret", + }, + expected: "manageengine: some credentials information are missing: MANAGEENGINE_CLIENT_ID", + }, + { + desc: "missing client secret", + envVars: map[string]string{ + EnvClientID: "abc", + EnvClientSecret: "", + }, + expected: "manageengine: some credentials information are missing: MANAGEENGINE_CLIENT_SECRET", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "manageengine: some credentials information are missing: MANAGEENGINE_CLIENT_ID,MANAGEENGINE_CLIENT_SECRET", + }, + } + + 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 + clientID string + clientSecret string + expected string + }{ + { + desc: "success", + clientID: "abc", + clientSecret: "secret", + }, + { + desc: "missing client ID", + clientSecret: "secret", + expected: "manageengine: credentials missing", + }, + { + desc: "missing client secret", + clientID: "abc", + expected: "manageengine: credentials missing", + }, + { + desc: "missing credentials", + expected: "manageengine: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.ClientID = test.clientID + config.ClientSecret = test.clientSecret + + 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 a60b48b70..053c3c4e7 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -86,6 +86,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/loopia" "github.com/go-acme/lego/v4/providers/dns/luadns" "github.com/go-acme/lego/v4/providers/dns/mailinabox" + "github.com/go-acme/lego/v4/providers/dns/manageengine" "github.com/go-acme/lego/v4/providers/dns/metaname" "github.com/go-acme/lego/v4/providers/dns/mijnhost" "github.com/go-acme/lego/v4/providers/dns/mittwald" @@ -315,6 +316,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return luadns.NewDNSProvider() case "mailinabox": return mailinabox.NewDNSProvider() + case "manageengine": + return manageengine.NewDNSProvider() case "metaname": return metaname.NewDNSProvider() case "mijnhost": From bcc17b1bf8b2caad3b07efa52df7bd40866aac5c Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 20 Dec 2024 15:07:58 +0100 Subject: [PATCH 022/298] chore: update dependencies (#2383) --- go.mod | 72 ++++++++++++++--------------- go.sum | 144 ++++++++++++++++++++++++++++----------------------------- 2 files changed, 108 insertions(+), 108 deletions(-) diff --git a/go.mod b/go.mod index 591a21681..fcd88001a 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/go-acme/lego/v4 go 1.22.0 require ( - cloud.google.com/go/compute/metadata v0.5.2 + cloud.google.com/go/compute/metadata v0.6.0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 @@ -16,17 +16,17 @@ require ( github.com/BurntSushi/toml v1.4.0 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 - github.com/aliyun/alibaba-cloud-sdk-go v1.63.66 - github.com/aws/aws-sdk-go-v2 v1.32.6 - github.com/aws/aws-sdk-go-v2/config v1.28.6 - github.com/aws/aws-sdk-go-v2/credentials v1.17.47 - github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.7 - github.com/aws/aws-sdk-go-v2/service/route53 v1.46.3 - github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 + github.com/aliyun/alibaba-cloud-sdk-go v1.63.72 + github.com/aws/aws-sdk-go-v2 v1.32.7 + github.com/aws/aws-sdk-go-v2/config v1.28.7 + github.com/aws/aws-sdk-go-v2/credentials v1.17.48 + github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8 + github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4 + github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 github.com/cenkalti/backoff/v4 v4.3.0 github.com/civo/civogo v0.3.11 - github.com/cloudflare/cloudflare-go v0.111.0 + github.com/cloudflare/cloudflare-go v0.112.0 github.com/cpu/goacmedns v0.1.1 github.com/dnsimple/dnsimple-go v1.7.0 github.com/exoscale/egoscale/v3 v3.1.7 @@ -37,11 +37,11 @@ require ( github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/hashicorp/go-retryablehttp v0.7.7 github.com/hashicorp/go-version v1.7.0 - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.126 + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df github.com/infobloxopen/infoblox-go-client v1.1.1 github.com/labbsr0x/bindman-dns-webhook v1.0.2 - github.com/linode/linodego v1.43.0 + github.com/linode/linodego v1.44.0 github.com/liquidweb/liquidweb-go v1.6.4 github.com/mattn/go-isatty v0.0.20 github.com/miekg/dns v1.1.62 @@ -58,41 +58,41 @@ require ( github.com/nrdcg/nodion v0.1.0 github.com/nrdcg/porkbun v0.4.0 github.com/nzdjb/go-metaname v1.0.0 - github.com/oracle/oci-go-sdk/v65 v65.80.0 + github.com/oracle/oci-go-sdk/v65 v65.81.1 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/sacloud/iaas-api-go v1.14.0 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 github.com/selectel/domains-go v1.1.0 github.com/selectel/go-selvpcclient/v3 v3.2.1 github.com/softlayer/softlayer-go v1.1.7 github.com/stretchr/testify v1.10.0 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1058 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1058 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 github.com/transip/gotransip/v6 v6.26.0 github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec github.com/urfave/cli/v2 v2.27.5 github.com/vinyldns/go-vinyldns v0.9.16 - github.com/volcengine/volc-sdk-golang v1.0.188 + github.com/volcengine/volc-sdk-golang v1.0.189 github.com/vultr/govultr/v3 v3.9.1 - github.com/yandex-cloud/go-genproto v0.0.0-20241206133605-07e4a676108b - github.com/yandex-cloud/go-sdk v0.0.0-20241206142255-6c3760d17eea + github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c + github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 golang.org/x/crypto v0.31.0 - golang.org/x/net v0.32.0 + golang.org/x/net v0.33.0 golang.org/x/oauth2 v0.24.0 golang.org/x/text v0.21.0 golang.org/x/time v0.8.0 - google.golang.org/api v0.211.0 - gopkg.in/ns1/ns1-go.v2 v2.12.2 + google.golang.org/api v0.214.0 + gopkg.in/ns1/ns1-go.v2 v2.13.0 gopkg.in/yaml.v2 v2.4.0 software.sslmate.com/src/go-pkcs12 v0.5.0 ) require ( - cloud.google.com/go/auth v0.12.1 // indirect + cloud.google.com/go/auth v0.13.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect @@ -104,17 +104,17 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect github.com/aws/smithy-go v1.22.1 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect @@ -123,7 +123,7 @@ require ( github.com/dimchansky/utfbom v1.1.1 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-errors/errors v1.0.1 // indirect @@ -132,8 +132,8 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.16.0 // indirect - github.com/go-resty/resty/v2 v2.15.3 // indirect - github.com/goccy/go-json v0.10.3 // indirect + github.com/go-resty/resty/v2 v2.16.2 // indirect + github.com/goccy/go-json v0.10.4 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect @@ -201,7 +201,7 @@ require ( golang.org/x/tools v0.28.0 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/grpc v1.67.1 // indirect google.golang.org/protobuf v1.35.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 097f9c4e6..c5e736459 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,8 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/auth v0.12.1 h1:n2Bj25BUMM0nvE9D2XLTiImanwZhO3DkfWSYS/SAJP4= -cloud.google.com/go/auth v0.12.1/go.mod h1:BFMu+TNpF3DmvfBO9ClqTR/SiqVIm7LukKF9mbendF4= +cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= +cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -23,8 +23,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= -cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -113,8 +113,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/aliyun/alibaba-cloud-sdk-go v1.63.66 h1:DnbaZyCaDJfCeo/0+qiI6XLIHu29NMa6iVXOJdcu6f4= -github.com/aliyun/alibaba-cloud-sdk-go v1.63.66/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= +github.com/aliyun/alibaba-cloud-sdk-go v1.63.72 h1:HvFZUzEbNvfe8F2Mg0wBGv90bPhWDxgVtDHR5zoBOU0= +github.com/aliyun/alibaba-cloud-sdk-go v1.63.72/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -124,45 +124,45 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.32.6 h1:7BokKRgRPuGmKkFMhEg/jSul+tB9VvXhcViILtfG8b4= -github.com/aws/aws-sdk-go-v2 v1.32.6/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= +github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw= +github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= -github.com/aws/aws-sdk-go-v2/config v1.28.6 h1:D89IKtGrs/I3QXOLNTH93NJYtDhm8SYa9Q5CsPShmyo= -github.com/aws/aws-sdk-go-v2/config v1.28.6/go.mod h1:GDzxJ5wyyFSCoLkS+UhGB0dArhb9mI+Co4dHtoTxbko= -github.com/aws/aws-sdk-go-v2/credentials v1.17.47 h1:48bA+3/fCdi2yAwVt+3COvmatZ6jUDNkDTIsqDiMUdw= -github.com/aws/aws-sdk-go-v2/credentials v1.17.47/go.mod h1:+KdckOejLW3Ks3b0E3b5rHsr2f9yuORBum0WPnE5o5w= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 h1:AmoU1pziydclFT/xRV+xXE/Vb8fttJCLRPv8oAkprc0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21/go.mod h1:AjUdLYe4Tgs6kpH4Bv7uMZo7pottoyHMn4eTcIcneaY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 h1:s/fF4+yDQDoElYhfIVvSNyeCydfbuTKzhxSXDXCPasU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25/go.mod h1:IgPfDv5jqFIzQSNbUEMoitNooSMXjRSDkhXv8jiROvU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 h1:ZntTCl5EsYnhN/IygQEUugpdwbhdkom9uHcbCftiGgA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25/go.mod h1:DBdPrgeocww+CSl1C8cEV8PN1mHMBhuCDLpXezyvWkE= +github.com/aws/aws-sdk-go-v2/config v1.28.7 h1:GduUnoTXlhkgnxTD93g1nv4tVPILbdNQOzav+Wpg7AE= +github.com/aws/aws-sdk-go-v2/config v1.28.7/go.mod h1:vZGX6GVkIE8uECSUHB6MWAUsd4ZcG2Yq/dMa4refR3M= +github.com/aws/aws-sdk-go-v2/credentials v1.17.48 h1:IYdLD1qTJ0zanRavulofmqut4afs45mOWEI+MzZtTfQ= +github.com/aws/aws-sdk-go-v2/credentials v1.17.48/go.mod h1:tOscxHN3CGmuX9idQ3+qbkzrjVIx32lqDSU1/0d/qXs= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 h1:kqOrpojG71DxJm/KDPO+Z/y1phm1JlC8/iT+5XRmAn8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22/go.mod h1:NtSFajXVVL8TA2QNngagVZmUtXciyrHOt7xgz4faS/M= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 h1:r67ps7oHCYnflpgDy2LZU0MAQtQbYIOqNNnqGO6xQkE= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25/go.mod h1:GrGY+Q4fIokYLtjCVB/aFfCVL6hhGUFl8inD18fDalE= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 h1:GeNJsIFHB+WW5ap2Tec4K6dzcVTsRbsT1Lra46Hv9ME= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26/go.mod h1:zfgMpwHDXX2WGoG84xG2H+ZlPTkJUU4YUvx2svLQYWo= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 h1:HCpPsWqmYQieU7SS6E9HXfdAMSud0pteVXieJmcpIRI= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6/go.mod h1:ngUiVRCco++u+soRRVBIvBZxSMMvOVMXA4PJ36JLfSw= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 h1:50+XsN70RS7dwJ2CkVNXzj7U2L1HKP8nqTd3XWEXBN4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6/go.mod h1:WqgLmwY7so32kG01zD8CPTJWVWM+TzJoOVHwTg4aPug= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 h1:BbGDtTi0T1DYlmjBiCr/le3wzhA37O8QTC5/Ab8+EXk= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6/go.mod h1:hLMJt7Q8ePgViKupeymbqI0la+t9/iYFBjxQCFwuAwI= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.7 h1:pVO3tnwny+c+XIfNkmrReAkNd4Gyy7TVvro1ZTfzY4g= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.7/go.mod h1:yveTbfkp9hhabgl3aXbd2/AvWCgJRi0O+mhm3REyvE8= -github.com/aws/aws-sdk-go-v2/service/route53 v1.46.3 h1:pDBrvz7CMK381q5U+nPqtSQZZid5z1XH8lsI6kHNcSY= -github.com/aws/aws-sdk-go-v2/service/route53 v1.46.3/go.mod h1:rDMeB13C/RS0/zw68RQD4LLiWChf5tZBKjEQmjtHa/c= -github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 h1:nyuzXooUNJexRT0Oy0UQY6AhOzxPxhtt4DcBIHyCnmw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0/go.mod h1:sT/iQz8JK3u/5gZkT+Hmr7GzVZehUMkRZpOaAwYXeGY= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 h1:rLnYAfXQ3YAccocshIH5mzNNwZBkBo+bP6EhIxak6Hw= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.7/go.mod h1:ZHtuQJ6t9A/+YDuxOLnbryAmITtr8UysSny3qcyvJTc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 h1:JnhTZR3PiYDNKlXy50/pNeix9aGMo6lLpXwJ1mw8MD4= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6/go.mod h1:URronUEGfXZN1VpdktPSD1EkAL9mfrV+2F4sjH38qOY= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 h1:s4074ZO1Hk8qv65GqNXqDjmkf4HSQqJukaLuuW0TpDA= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.2/go.mod h1:mVggCnIWoM09jP71Wh+ea7+5gAp53q+49wDFs1SW5z8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7 h1:tB4tNw83KcajNAzaIMhkhVI2Nt8fAZd5A5ro113FEMY= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7/go.mod h1:lvpyBGkZ3tZ9iSsUIcC2EWp+0ywa7aK3BLT+FwZi+mQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 h1:8eUsivBQzZHqe/3FE+cqwfH+0p5Jo8PFM/QYQSmeZ+M= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7/go.mod h1:kLPQvGUmxn/fqiCrDeohwG33bq2pQpGeY62yRO6Nrh0= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7 h1:Hi0KGbrnr57bEHWM0bJ1QcBzxLrL/k2DHvGYhb8+W1w= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7/go.mod h1:wKNgWgExdjjrm4qvfbTorkvocEstaoDl4WCvGfeCy9c= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8 h1:+lmJoqxuUoPlSfGk5JYQQivd9YFjUvRZR6RPY+Wcx48= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8/go.mod h1:Gg8/myP4+rgRi4+j9gQdbOEnMtwMAUUIeXo+nKCFVj8= +github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4 h1:0jMtawybbfpFEIMy4wvfyW2Z4YLr7mnuzT0fhR67Nrc= +github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4/go.mod h1:xlMODgumb0Pp8bzfpojqelDrf8SL9rb5ovwmwKJl+oU= +github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1 h1:aOVVZJgWbaH+EJYPvEgkNhCEbXXvH7+oML36oaPK3zE= +github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1/go.mod h1:r+xl5yzMk9083rMR+sJ5TYj9Tihvf/l1oxzZXDgGj2Q= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 h1:CvuUmnXI7ebaUAhbJcDy9YQx8wHR69eZ9I7q5hszt/g= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.8/go.mod h1:XDeGv1opzwm8ubxddF0cgqkZWsyOtw4lr6dxwmb6YQg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 h1:F2rBfNAL5UyswqoeWv9zs74N/NanhK16ydHW1pahX6E= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7/go.mod h1:JfyQ0g2JG8+Krq0EuZNnRwX0mU0HrwY/tG6JNfcqh4k= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 h1:Xgv/hyNgvLda/M9l9qxXc4UFSgppnRczLxlMs5Ae/QY= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.3/go.mod h1:5Gn+d+VaaRgsjewpMvGazt0WfcFO+Md4wLOuBfGR9Bc= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= @@ -198,8 +198,8 @@ github.com/civo/civogo v0.3.11 h1:mON/fyrV946Sbk6paRtOSGsN+asCgCmHCgArf5xmGxM= github.com/civo/civogo v0.3.11/go.mod h1:7+GeeFwc4AYTULaEshpT2vIcl3Qq8HPoxA17viX3l6g= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.111.0 h1:bFgl5OyR7iaV9DkTaoI2jU8X4rXDzEaFDaPfMTp+Ewo= -github.com/cloudflare/cloudflare-go v0.111.0/go.mod h1:w5c4Vm00JjZM+W0mPi6QOC+eWLncGQPURtgDck3z5xU= +github.com/cloudflare/cloudflare-go v0.112.0 h1:caFwqXdGJCl3rjVMgbPEn8iCYAg9JsRYV3dIVQE5d7g= +github.com/cloudflare/cloudflare-go v0.112.0/go.mod h1:QB55kuJ5ZTeLNFcLJePfMuBilhu/LDKpLBmKFQIoSZ0= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= @@ -262,8 +262,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -298,8 +298,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/go-resty/resty/v2 v2.15.3 h1:bqff+hcqAflpiF591hhJzNdkRsFhlB96CYfBwSFvql8= -github.com/go-resty/resty/v2 v2.15.3/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= +github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg= +github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -309,8 +309,8 @@ github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= -github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= +github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= @@ -475,8 +475,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.126 h1:+Lt/FRG1SfmdS7hxpGDdNdtFgs7kiVyDC3/BXsa3RAw= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.126/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 h1:kQ2Agpfy7Ze1ajn9xCQG9G6T7XIbqv+FBDS/U98W9Mk= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -549,8 +549,8 @@ github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk= github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/linode/linodego v1.43.0 h1:sGeBB3caZt7vKBoPS5p4AVzmlG4JoqQOdigIibx3egk= -github.com/linode/linodego v1.43.0/go.mod h1:n4TMFu1UVNala+icHqrTEFFaicYSF74cSAUG5zkTwfA= +github.com/linode/linodego v1.44.0 h1:JZLLWzCAx3CmHSV9NmCoXisuqKtrmPhfY9MrgvaHMUY= +github.com/linode/linodego v1.44.0/go.mod h1:umdoNOmtbqAdGQbmQnPFZ2YS4US+/mU/1bA7MjoKAvg= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= @@ -678,8 +678,8 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= -github.com/oracle/oci-go-sdk/v65 v65.80.0 h1:Rr7QLMozd2DfDBKo6AB3DzLYQxAwuOG118+K5AAD5E8= -github.com/oracle/oci-go-sdk/v65 v65.80.0/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0= +github.com/oracle/oci-go-sdk/v65 v65.81.1 h1:JYc47bk8n/MUchA2KHu1ggsCQzlJZQLJ+tTKfOho00E= +github.com/oracle/oci-go-sdk/v65 v65.81.1/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0= github.com/ovh/go-ovh v1.6.0 h1:ixLOwxQdzYDx296sXcgS35TOPEahJkpjMGtzPadCjQI= github.com/ovh/go-ovh v1.6.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -760,8 +760,8 @@ github.com/sacloud/api-client-go v0.2.10 h1:+rv3jDohD+pkdYwOTBiB+jZsM0xK3AxadXRz github.com/sacloud/api-client-go v0.2.10/go.mod h1:Jj3CTy2+O4bcMedVDXlbHuqqche85HEPuVXoQFhLaRc= github.com/sacloud/go-http v0.1.8 h1:ynreWA/vnM8G2ksbMlmefBHsXURKPz49qlPRqQ9IQdw= github.com/sacloud/go-http v0.1.8/go.mod h1:7TL7TN1fnPKHsMifIqURDkGujnKViCgEz5Ei/LQdFK8= -github.com/sacloud/iaas-api-go v1.12.0 h1:kqXFn3HzCiawlX6hVJb1GVqcSJqcmiGHB4Zp14sxiI8= -github.com/sacloud/iaas-api-go v1.12.0/go.mod h1:SZLXeWOdXk3WReIS557sbU1gkOgrE4rseIBQV1B3b7o= +github.com/sacloud/iaas-api-go v1.14.0 h1:xjkFWqdo4ilTrKPNNYBNWR/CZ/kVRsJrdAHAad6J/AQ= +github.com/sacloud/iaas-api-go v1.14.0/go.mod h1:C8os2Mnj0TOmMdSllwhaDWKMVG2ysFnpe69kyA4M3V0= github.com/sacloud/packages-go v0.0.10 h1:UiQGjy8LretewkRhsuna1TBM9Vz/l9FoYpQx+D+AOck= github.com/sacloud/packages-go v0.0.10/go.mod h1:f8QITBh9z4IZc4yE9j21Q8b0sXEMwRlRmhhjWeDVTYs= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= @@ -845,10 +845,10 @@ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1058 h1:VVv5rEFtGbxEB23V3gJO5pFHEXGlOh9duEWEtNm+w6c= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1058/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1058 h1:3pviSO0EEt1arn54TaCcL9QG45YJxtddubJjGFoqK7Q= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1058/go.mod h1:bVNaLGg19+7pJ9Y4nIIR7w2LQ/3PQMRLBA7jcs+cggU= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065 h1:krcqtAmexnHHBm/4ge4tr2b1cn/a7JGBESVGoZYXQAE= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 h1:aEFtLD1ceyeljQXB1S2BjN0zjTkf0X3XmpuxFIiC29w= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065/go.mod h1:HWvwy09hFSMXrj9SMvVRWV4U7rZO3l+WuogyNuxiT3M= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -866,8 +866,8 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= -github.com/volcengine/volc-sdk-golang v1.0.188 h1:s90SUVGPUX2oAVeM/FgzQeSCXZ80XCXsuXliT3s0CtI= -github.com/volcengine/volc-sdk-golang v1.0.188/go.mod h1:u0VtPvlXWpXDTmc9IHkaW1q+5Jjwus4oAqRhNMDRInE= +github.com/volcengine/volc-sdk-golang v1.0.189 h1:VMDTHWYXakXJtZqPYn0As/h4eB0c4imvyru6mIp+o60= +github.com/volcengine/volc-sdk-golang v1.0.189/go.mod h1:u0VtPvlXWpXDTmc9IHkaW1q+5Jjwus4oAqRhNMDRInE= github.com/vultr/govultr/v3 v3.9.1 h1:uxSIb8Miel7tqTs3ee+z3t+JelZikwqBBsZzCOPBy/8= github.com/vultr/govultr/v3 v3.9.1/go.mod h1:Rd8ebpXm7jxH3MDmhnEs+zrlYW212ouhx+HeUMfHm2o= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -881,10 +881,10 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yandex-cloud/go-genproto v0.0.0-20241206133605-07e4a676108b h1:+xsB23dmxN3hBSGZLAiyLsUADnqr6ASOiZJmLd8++nk= -github.com/yandex-cloud/go-genproto v0.0.0-20241206133605-07e4a676108b/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= -github.com/yandex-cloud/go-sdk v0.0.0-20241206142255-6c3760d17eea h1:XvnMWpD249l3rhJjDWEAGOQmYZ3Rw0XjEwREDzm9wDs= -github.com/yandex-cloud/go-sdk v0.0.0-20241206142255-6c3760d17eea/go.mod h1:6JH4ZTrHlyTtKwf1VoEGfbHl+or8NFdOyxwYzID0UdI= +github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c h1:Rnr+lDYXVkP+3eT8/d68iq4G/UeIhyCQk+HKa8toTvg= +github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 h1:qmpz0Kvr9GAng8LAhRcKIpY71CEAcL3EBkftVlsP5Cw= +github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134/go.mod h1:KgZCJrxdhdw/sKhTQ/M3S9WOLri2PCnBlc4C3s+PfKY= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1050,8 +1050,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1270,8 +1270,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.211.0 h1:IUpLjq09jxBSV1lACO33CGY3jsRcbctfGzhj+ZSE/Bg= -google.golang.org/api v0.211.0/go.mod h1:XOloB4MXFH4UTlQSGuNUxw0UT74qdENK8d6JNsXKLi0= +google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA= +google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1314,8 +1314,8 @@ google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOs google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4= google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ= google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 h1:IfdSdTcLFy4lqUQrQJLkLt1PB+AsqVz6lwkWPzWEz10= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1369,8 +1369,8 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/ns1/ns1-go.v2 v2.12.2 h1:SPM5BTTMJ1zVBhMMiiPFdF7l6Y3fq5o7bKM7jDqsUfM= -gopkg.in/ns1/ns1-go.v2 v2.12.2/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= +gopkg.in/ns1/ns1-go.v2 v2.13.0 h1:I5NNqI9Bi1SGK92TVkOvLTwux5LNrix/99H2datVh48= +gopkg.in/ns1/ns1-go.v2 v2.13.0/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= From 258fb88ec9d716515180cb59247c1c8fdc49c970 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 20 Dec 2024 15:11:10 +0100 Subject: [PATCH 023/298] Prepare release v4.21.0 --- CHANGELOG.md | 21 +++++++++++++++++++ acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5be646f6..bdd07ac56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## [v4.21.0](https://github.com/go-acme/lego/releases/tag/v4.21.0) (2024-12-20) + +### Added + +- **[dnsprovider]** Add DNS provider for Rainyun/雨云 +- **[dnsprovider]** Add DNS provider for West.cn/西部数码 +- **[dnsprovider]** Add DNS provider for ManageEngine CloudDNS +- **[cli]** feat: add --force-cert-domains flag to renew + +### Fixed + +- **[cli]** create client only when needed +- **[cli]** clone the transport with tls-skip-verify +- **[cli]** use retryable client for ACME server calls +- **[dnsprovider]** bunny: fix zone detection +- **[dnsprovider]** inwx: delete only the TXT record related to the DNS challenge +- **[dnsprovider]** infomaniak: increase default propagation timeout +- **[dnsprovider]** dnsmadeeasy: use default transport +- **[dnsprovider]** netcup: increase default propagation values +- **[dnsprovider]** otc: use default transport + ## [v4.20.4](https://github.com/go-acme/lego/releases/tag/v4.20.4) (2024-11-21) Publish the Snap to the Snapcraft stable channel. diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 6755fe77a..a1e8c68ab 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.20.4" + ourUserAgent = "xenolf-acme/4.21.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index d449e84cf..c23e0d038 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.20.4+dev-detach" +const defaultVersion = "v4.21.0+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 987dfcad9..30b7f6929 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.20.4" + ourUserAgent = "goacme-lego/4.21.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From ee7a9e4fa04f25ce4a67e449485638c2163be4cb Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 20 Dec 2024 15:11:30 +0100 Subject: [PATCH 024/298] Detach v4.21.0 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index a1e8c68ab..e2d5700ff 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index c23e0d038..8705ebbdf 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.21.0+dev-release" +const defaultVersion = "v4.21.0+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 30b7f6929..572fd5012 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From 5f53d3e87d19999993ae81c69a25e43db42937ce Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 2 Jan 2025 23:59:02 +0100 Subject: [PATCH 025/298] chore: update linter (#2387) --- .github/workflows/pr.yml | 2 +- .golangci.yml | 2 ++ challenge/dns01/dns_challenge_manual_test.go | 5 +++-- platform/config/env/env_test.go | 10 ++++++---- providers/dns/designate/designate_test.go | 4 ++-- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index d7404a6b8..891e261ed 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: stable - GOLANGCI_LINT_VERSION: v1.62.0 + GOLANGCI_LINT_VERSION: v1.63.3 HUGO_VERSION: 0.131.0 CGO_ENABLED: 0 LEGO_E2E_TESTS: CI diff --git a/.golangci.yml b/.golangci.yml index 68fd32a68..9467cf549 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -133,6 +133,8 @@ linters-settings: errorf: true sprintf1: true strconcat: false + usetesting: + os-setenv: false # we already have a test "framework" to handle env vars run: timeout: 10m diff --git a/challenge/dns01/dns_challenge_manual_test.go b/challenge/dns01/dns_challenge_manual_test.go index cfc728aca..26a508d1c 100644 --- a/challenge/dns01/dns_challenge_manual_test.go +++ b/challenge/dns01/dns_challenge_manual_test.go @@ -30,9 +30,10 @@ func TestDNSProviderManual(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - file, err := os.CreateTemp("", "lego_test") + file, err := os.CreateTemp(t.TempDir(), "lego_test") require.NoError(t, err) - defer func() { _ = os.Remove(file.Name()) }() + + t.Cleanup(func() { _ = file.Close() }) _, err = file.WriteString(test.input) require.NoError(t, err) diff --git a/platform/config/env/env_test.go b/platform/config/env/env_test.go index 4a3d0a04c..d7c51a552 100644 --- a/platform/config/env/env_test.go +++ b/platform/config/env/env_test.go @@ -367,9 +367,10 @@ func TestGetOrFile_ReadsFiles(t *testing.T) { err = os.Unsetenv(varEnvName) require.NoError(t, err) - file, err := os.CreateTemp("", "lego") + file, err := os.CreateTemp(t.TempDir(), "lego") require.NoError(t, err) - defer os.Remove(file.Name()) + + t.Cleanup(func() { _ = file.Close() }) err = os.WriteFile(file.Name(), []byte("lego_file\n"), 0o644) require.NoError(t, err) @@ -392,9 +393,10 @@ func TestGetOrFile_PrefersEnvVars(t *testing.T) { err = os.Unsetenv(varEnvName) require.NoError(t, err) - file, err := os.CreateTemp("", "lego") + file, err := os.CreateTemp(t.TempDir(), "lego") require.NoError(t, err) - defer os.Remove(file.Name()) + + t.Cleanup(func() { _ = file.Close() }) err = os.WriteFile(file.Name(), []byte("lego_file"), 0o644) require.NoError(t, err) diff --git a/providers/dns/designate/designate_test.go b/providers/dns/designate/designate_test.go index 881faeef1..1045baa95 100644 --- a/providers/dns/designate/designate_test.go +++ b/providers/dns/designate/designate_test.go @@ -265,10 +265,10 @@ func TestNewDNSProviderConfig(t *testing.T) { func createCloudsYaml(t *testing.T, cloudName string, cloud clientconfig.Cloud) string { t.Helper() - file, err := os.CreateTemp("", "lego_test") + file, err := os.CreateTemp(t.TempDir(), "lego_test") require.NoError(t, err) - t.Cleanup(func() { _ = os.RemoveAll(file.Name()) }) + t.Cleanup(func() { _ = file.Close() }) clouds := clientconfig.Clouds{ Clouds: map[string]clientconfig.Cloud{ From b83c1d5f648a4e01adf247908ab2b5818b938dfb Mon Sep 17 00:00:00 2001 From: bossm8 <91630231+bossm8@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:22:00 +0100 Subject: [PATCH 026/298] feat: add hook-timeout to run and renew commands (#2389) Co-authored-by: Fernandez Ludovic --- cmd/cmd_renew.go | 10 ++++++++-- cmd/cmd_run.go | 8 +++++++- cmd/hook.go | 4 ++-- docs/data/zz_cli_help.toml | 2 ++ 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index c4c680234..ca297fae7 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -25,6 +25,7 @@ const ( flgARIWaitToRenewDuration = "ari-wait-to-renew-duration" flgReuseKey = "reuse-key" flgRenewHook = "renew-hook" + flgRenewHookTimeout = "renew-hook-timeout" flgNoRandomSleep = "no-random-sleep" flgForceCertDomains = "force-cert-domains" ) @@ -109,6 +110,11 @@ func createRenew() *cli.Command { Name: flgRenewHook, Usage: "Define a hook. The hook is executed only when the certificates are effectively renewed.", }, + &cli.DurationFlag{ + Name: flgRenewHookTimeout, + Usage: "Define the timeout for the hook execution.", + Value: 2 * time.Minute, + }, &cli.BoolFlag{ Name: flgNoRandomSleep, Usage: "Do not add a random sleep before the renewal." + @@ -254,7 +260,7 @@ func renewForDomains(ctx *cli.Context, account *Account, keyType certcrypto.KeyT addPathToMetadata(meta, domain, certRes, certsStorage) - return launchHook(ctx.String(flgRenewHook), meta) + return launchHook(ctx.String(flgRenewHook), ctx.Duration(flgRenewHookTimeout), meta) } func renewForCSR(ctx *cli.Context, account *Account, keyType certcrypto.KeyType, certsStorage *CertificatesStorage, bundle bool, meta map[string]string) error { @@ -337,7 +343,7 @@ func renewForCSR(ctx *cli.Context, account *Account, keyType certcrypto.KeyType, addPathToMetadata(meta, domain, certRes, certsStorage) - return launchHook(ctx.String(flgRenewHook), meta) + return launchHook(ctx.String(flgRenewHook), ctx.Duration(flgRenewHookTimeout), meta) } func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool { diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index f2cec5655..f6d7b70c3 100644 --- a/cmd/cmd_run.go +++ b/cmd/cmd_run.go @@ -23,6 +23,7 @@ const ( flgPreferredChain = "preferred-chain" flgAlwaysDeactivateAuthorizations = "always-deactivate-authorizations" flgRunHook = "run-hook" + flgRunHookTimeout = "run-hook-timeout" ) func createRun() *cli.Command { @@ -75,6 +76,11 @@ func createRun() *cli.Command { Name: flgRunHook, Usage: "Define a hook. The hook is executed when the certificates are effectively created.", }, + &cli.DurationFlag{ + Name: flgRunHookTimeout, + Usage: "Define the timeout for the hook execution.", + Value: 2 * time.Minute, + }, }, } } @@ -129,7 +135,7 @@ func run(ctx *cli.Context) error { addPathToMetadata(meta, cert.Domain, cert, certsStorage) - return launchHook(ctx.String(flgRunHook), meta) + return launchHook(ctx.String(flgRunHook), ctx.Duration(flgRunHookTimeout), meta) } func handleTOS(ctx *cli.Context, client *lego.Client) bool { diff --git a/cmd/hook.go b/cmd/hook.go index 0b0ca4038..514650ecd 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -10,12 +10,12 @@ import ( "time" ) -func launchHook(hook string, meta map[string]string) error { +func launchHook(hook string, timeout time.Duration, meta map[string]string) error { if hook == "" { return nil } - ctxCmd, cancel := context.WithTimeout(context.Background(), 120*time.Second) + ctxCmd, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() parts := strings.Fields(hook) diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 84615c54d..f6674e5e5 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -74,6 +74,7 @@ OPTIONS: --preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. --always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful. --run-hook value Define a hook. The hook is executed when the certificates are effectively created. + --run-hook-timeout value Define the timeout for the hook execution. (default: 2m0s) --help, -h show help """ @@ -98,6 +99,7 @@ OPTIONS: --preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. --always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful. --renew-hook value Define a hook. The hook is executed only when the certificates are effectively renewed. + --renew-hook-timeout value Define the timeout for the hook execution. (default: 2m0s) --no-random-sleep Do not add a random sleep before the renewal. We do not recommend using this flag if you are doing your renewals in an automated way. (default: false) --force-cert-domains Check and ensure that the cert's domain list matches those passed in the domains argument. (default: false) --help, -h show help From c2b88e19da2c9641cb2cc44953700845bb8287d7 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 9 Jan 2025 22:12:05 +0100 Subject: [PATCH 027/298] acme-dns: HTTP storage (#2393) --- .golangci.yml | 13 +- cmd/zz_gen_cmd_dnshelp.go | 1 + docs/content/dns/zz_gen_acme-dns.md | 9 +- go.mod | 2 +- go.sum | 4 +- providers/dns/acmedns/acmedns.go | 66 ++++++-- providers/dns/acmedns/acmedns.toml | 9 +- providers/dns/acmedns/acmedns_test.go | 140 +---------------- .../dns/acmedns/internal/fixtures/error.json | 3 + .../acmedns/internal/fixtures/fetch-all.json | 16 ++ .../dns/acmedns/internal/fixtures/fetch.json | 7 + .../dns/acmedns/internal/http_storage.go | 139 +++++++++++++++++ .../dns/acmedns/internal/http_storage_test.go | 139 +++++++++++++++++ providers/dns/acmedns/internal/readme.md | 70 +++++++++ providers/dns/acmedns/mock_test.go | 143 ++++++++++++++++++ 15 files changed, 591 insertions(+), 170 deletions(-) create mode 100644 providers/dns/acmedns/internal/fixtures/error.json create mode 100644 providers/dns/acmedns/internal/fixtures/fetch-all.json create mode 100644 providers/dns/acmedns/internal/fixtures/fetch.json create mode 100644 providers/dns/acmedns/internal/http_storage.go create mode 100644 providers/dns/acmedns/internal/http_storage_test.go create mode 100644 providers/dns/acmedns/internal/readme.md create mode 100644 providers/dns/acmedns/mock_test.go diff --git a/.golangci.yml b/.golangci.yml index 9467cf549..9beeea718 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -165,9 +165,6 @@ issues: text: 'Error return value of `fmt.Fprintln` is not checked' linters: - errcheck - - path: providers/dns/dns_providers.go - linters: - - gocyclo - path: certcrypto/crypto.go text: '(tlsFeatureExtensionOID|ocspMustStapleFeature) is a global variable' linters: @@ -220,7 +217,7 @@ issues: text: 'testCases is a global variable' linters: - gochecknoglobals - - path: providers/dns/acmedns/acmedns_test.go + - path: providers/dns/acmedns/mock_test.go text: 'egTestAccount is a global variable' linters: - gochecknoglobals @@ -228,10 +225,6 @@ issues: text: 'memcachedHosts is a global variable' linters: - gochecknoglobals - - path: cmd/zz_gen_cmd_dnshelp.go - linters: - - gocyclo - - funlen - path: providers/dns/checkdomain/internal/types.go text: '`payed` is a misspelling of `paid`' linters: @@ -259,10 +252,6 @@ issues: text: 'cyclomatic complexity 13 of func `\(\*DNSProvider\)\.CleanUp` is high' linters: - gocyclo - - path: providers/dns/servercow/internal/types.go - text: 'the methods of "Value" use pointer receiver and non-pointer receiver.' - linters: - - recvcheck # Those elements have been replaced by non-exposed structures. - path: providers/dns/linode/linode_test.go diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index e5ae3b46d..e529ce234 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -176,6 +176,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Credentials:`) ew.writeln(` - "ACME_DNS_API_BASE": The ACME-DNS API address`) + ew.writeln(` - "ACME_DNS_STORAGE_BASE_URL": The ACME-DNS JSON account data server.`) ew.writeln(` - "ACME_DNS_STORAGE_PATH": The ACME-DNS JSON account data file. A per-domain account will be registered/persisted to this file and used for TXT updates.`) ew.writeln() diff --git a/docs/content/dns/zz_gen_acme-dns.md b/docs/content/dns/zz_gen_acme-dns.md index 0d57146ff..be901c512 100644 --- a/docs/content/dns/zz_gen_acme-dns.md +++ b/docs/content/dns/zz_gen_acme-dns.md @@ -29,6 +29,12 @@ Here is an example bash command using the Joohoi's ACME-DNS provider: ACME_DNS_API_BASE=http://10.0.0.8:4443 \ ACME_DNS_STORAGE_PATH=/root/.lego-acme-dns-accounts.json \ lego --email you@example.com --dns "acme-dns" -d '*.example.com' -d example.com run + +# or + +ACME_DNS_API_BASE=http://10.0.0.8:4443 \ +ACME_DNS_STORAGE_BASE_URL=http://10.10.10.10:80 \ +lego --email you@example.com --dns "acme-dns" -d '*.example.com' -d example.com run ``` @@ -39,6 +45,7 @@ lego --email you@example.com --dns "acme-dns" -d '*.example.com' -d example.com | Environment Variable Name | Description | |-----------------------|-------------| | `ACME_DNS_API_BASE` | The ACME-DNS API address | +| `ACME_DNS_STORAGE_BASE_URL` | The ACME-DNS JSON account data server. | | `ACME_DNS_STORAGE_PATH` | The ACME-DNS JSON account data file. A per-domain account will be registered/persisted to this file and used for TXT updates. | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. @@ -52,7 +59,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). ## More information - [API documentation](https://github.com/joohoi/acme-dns#api) -- [Go client](https://github.com/cpu/goacmedns) +- [Go client](https://github.com/nrdcg/goacmedns) diff --git a/go.mod b/go.mod index fcd88001a..171e71e5b 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,6 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 github.com/civo/civogo v0.3.11 github.com/cloudflare/cloudflare-go v0.112.0 - github.com/cpu/goacmedns v0.1.1 github.com/dnsimple/dnsimple-go v1.7.0 github.com/exoscale/egoscale/v3 v3.1.7 github.com/go-jose/go-jose/v4 v4.0.4 @@ -52,6 +51,7 @@ require ( github.com/nrdcg/desec v0.10.0 github.com/nrdcg/dnspod-go v0.4.0 github.com/nrdcg/freemyip v0.3.0 + github.com/nrdcg/goacmedns v0.2.0 github.com/nrdcg/goinwx v0.10.0 github.com/nrdcg/mailinabox v0.2.0 github.com/nrdcg/namesilo v0.2.1 diff --git a/go.sum b/go.sum index c5e736459..c7de23dd5 100644 --- a/go.sum +++ b/go.sum @@ -209,8 +209,6 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4= -github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= @@ -638,6 +636,8 @@ github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc= github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM= +github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0= +github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg= github.com/nrdcg/goinwx v0.10.0 h1:6W630bjDxQD6OuXKqrFRYVpTt0G/9GXXm3CeOrN0zJM= github.com/nrdcg/goinwx v0.10.0/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4= github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk= diff --git a/providers/dns/acmedns/acmedns.go b/providers/dns/acmedns/acmedns.go index 7ba7f08d0..8aabaa14c 100644 --- a/providers/dns/acmedns/acmedns.go +++ b/providers/dns/acmedns/acmedns.go @@ -3,13 +3,16 @@ package acmedns import ( + "context" "errors" "fmt" - "github.com/cpu/goacmedns" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/acmedns/internal" + "github.com/nrdcg/goacmedns" + "github.com/nrdcg/goacmedns/storage" ) const ( @@ -19,9 +22,14 @@ const ( // EnvAPIBase is the environment variable name for the ACME-DNS API address. // (e.g. https://acmedns.your-domain.com). EnvAPIBase = envNamespace + "API_BASE" + // EnvStoragePath is the environment variable name for the ACME-DNS JSON account data file. // A per-domain account will be registered/persisted to this file and used for TXT updates. EnvStoragePath = envNamespace + "STORAGE_PATH" + + // EnvStorageBaseURL is the environment variable name for the ACME-DNS JSON account data. + // The URL to the storage server. + EnvStorageBaseURL = envNamespace + "STORAGE_BASE_URL" ) var _ challenge.Provider = (*DNSProvider)(nil) @@ -31,10 +39,10 @@ var _ challenge.Provider = (*DNSProvider)(nil) type acmeDNSClient interface { // UpdateTXTRecord updates the provided account's TXT record // to the given value or returns an error. - UpdateTXTRecord(account goacmedns.Account, value string) error + UpdateTXTRecord(ctx context.Context, account goacmedns.Account, value string) error // RegisterAccount registers and returns a new account // with the given allowFrom restriction or returns an error. - RegisterAccount(allowFrom []string) (goacmedns.Account, error) + RegisterAccount(ctx context.Context, allowFrom []string) (goacmedns.Account, error) } // DNSProvider implements the challenge.Provider interface. @@ -46,17 +54,41 @@ type DNSProvider struct { // NewDNSProvider creates an ACME-DNS provider using file based account storage. // Its configuration is loaded from the environment by reading EnvAPIBase and EnvStoragePath. func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIBase, EnvStoragePath) + values, err := env.Get(EnvAPIBase) if err != nil { return nil, fmt.Errorf("acme-dns: %w", err) } - client := goacmedns.NewClient(values[EnvAPIBase]) - storage := goacmedns.NewFileStorage(values[EnvStoragePath], 0o600) - return NewDNSProviderClient(client, storage) + storagePath := env.GetOrFile(EnvStoragePath) + storageBaseURL := env.GetOrFile(EnvStorageBaseURL) + + if storagePath == "" && storageBaseURL == "" { + return nil, fmt.Errorf("acme-dns: %s or %s environment variables not set", EnvStoragePath, EnvStorageBaseURL) + } + + if storagePath != "" && storageBaseURL != "" { + return nil, fmt.Errorf("acme-dns: %s or %s environment variables cannot be used at the same time", EnvStoragePath, EnvStorageBaseURL) + } + + var st goacmedns.Storage + if storagePath != "" { + st = storage.NewFile(values[EnvStoragePath], 0o600) + } else { + st, err = internal.NewHTTPStorage(storageBaseURL) + if err != nil { + return nil, fmt.Errorf("acme-dns: new HTTP storage: %w", err) + } + } + + client, err := goacmedns.NewClient(values[EnvAPIBase]) + if err != nil { + return nil, fmt.Errorf("acme-dns: %w", err) + } + + return NewDNSProviderClient(client, st) } -// NewDNSProviderClient creates an ACME-DNS DNSProvider with the given acmeDNSClient and goacmedns.Storage. +// NewDNSProviderClient creates an ACME-DNS DNSProvider with the given acmeDNSClient and [goacmedns.Storage]. func NewDNSProviderClient(client acmeDNSClient, storage goacmedns.Storage) (*DNSProvider, error) { if client == nil { return nil, errors.New("ACME-DNS Client must be not nil") @@ -105,16 +137,18 @@ func (e ErrCNAMERequired) Error() string { // one will be created and registered with the ACME DNS server and an ErrCNAMERequired error is returned. // This will halt issuance and indicate to the user that a one-time manual setup is required for the domain. func (d *DNSProvider) Present(domain, _, keyAuth string) error { + ctx := context.Background() + // Compute the challenge response FQDN and TXT value for the domain based on the keyAuth. info := dns01.GetChallengeInfo(domain, keyAuth) // Check if credentials were previously saved for this domain. - account, err := d.storage.Fetch(domain) + account, err := d.storage.Fetch(ctx, domain) if err != nil { - if errors.Is(err, goacmedns.ErrDomainNotFound) { + if errors.Is(err, storage.ErrDomainNotFound) { // The account did not exist. // Create a new one and return an error indicating the required one-time manual CNAME setup. - return d.register(domain, info.FQDN) + return d.register(ctx, domain, info.FQDN) } // Errors other than goacmedns.ErrDomainNotFound are unexpected. @@ -122,7 +156,7 @@ func (d *DNSProvider) Present(domain, _, keyAuth string) error { } // Update the acme-dns TXT record. - return d.client.UpdateTXTRecord(account, info.Value) + return d.client.UpdateTXTRecord(ctx, account, info.Value) } // CleanUp removes the record matching the specified parameters. It is not @@ -137,19 +171,19 @@ func (d *DNSProvider) CleanUp(_, _, _ string) error { // If account creation works as expected a ErrCNAMERequired error is returned describing // the one-time manual CNAME setup required to complete setup of the ACME-DNS hook for the domain. // If any other error occurs it is returned as-is. -func (d *DNSProvider) register(domain, fqdn string) error { +func (d *DNSProvider) register(ctx context.Context, domain, fqdn string) error { // TODO(@cpu): Read CIDR whitelists from the environment - newAcct, err := d.client.RegisterAccount(nil) + newAcct, err := d.client.RegisterAccount(ctx, nil) if err != nil { return err } // Store the new account in the storage and call save to persist the data. - err = d.storage.Put(domain, newAcct) + err = d.storage.Put(ctx, domain, newAcct) if err != nil { return err } - err = d.storage.Save() + err = d.storage.Save(ctx) if err != nil { return err } diff --git a/providers/dns/acmedns/acmedns.toml b/providers/dns/acmedns/acmedns.toml index f4632411b..12d690414 100644 --- a/providers/dns/acmedns/acmedns.toml +++ b/providers/dns/acmedns/acmedns.toml @@ -9,13 +9,20 @@ Example = ''' ACME_DNS_API_BASE=http://10.0.0.8:4443 \ ACME_DNS_STORAGE_PATH=/root/.lego-acme-dns-accounts.json \ lego --email you@example.com --dns "acme-dns" -d '*.example.com' -d example.com run + +# or + +ACME_DNS_API_BASE=http://10.0.0.8:4443 \ +ACME_DNS_STORAGE_BASE_URL=http://10.10.10.10:80 \ +lego --email you@example.com --dns "acme-dns" -d '*.example.com' -d example.com run ''' [Configuration] [Configuration.Credentials] ACME_DNS_API_BASE = "The ACME-DNS API address" ACME_DNS_STORAGE_PATH = "The ACME-DNS JSON account data file. A per-domain account will be registered/persisted to this file and used for TXT updates." + ACME_DNS_STORAGE_BASE_URL = "The ACME-DNS JSON account data server." [Links] API = "https://github.com/joohoi/acme-dns#api" - GoClient = "https://github.com/cpu/goacmedns" + GoClient = "https://github.com/nrdcg/goacmedns" diff --git a/providers/dns/acmedns/acmedns_test.go b/providers/dns/acmedns/acmedns_test.go index 68e8f7406..e21a10522 100644 --- a/providers/dns/acmedns/acmedns_test.go +++ b/providers/dns/acmedns/acmedns_test.go @@ -1,21 +1,14 @@ package acmedns import ( - "errors" + "context" "testing" - "github.com/cpu/goacmedns" + "github.com/nrdcg/goacmedns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -var ( - // errorClientErr is used by the Client mocks that return an error. - errorClientErr = errors.New("errorClient always errors") - // errorStorageErr is used by the Storage mocks that return an error. - errorStorageErr = errors.New("errorStorage always errors") -) - const ( // Fixed test data for unit tests. egDomain = "example.com" @@ -23,133 +16,6 @@ const ( egKeyAuth = "⚷" ) -var egTestAccount = goacmedns.Account{ - FullDomain: "acme-dns." + egDomain, - SubDomain: "random-looking-junk." + egDomain, - Username: "spooky.mulder", - Password: "trustno1", -} - -// mockClient is a mock implementing the acmeDNSClient interface that always -// returns a fixed goacmedns.Account from calls to Register. -type mockClient struct { - mockAccount goacmedns.Account -} - -// UpdateTXTRecord does nothing. -func (c mockClient) UpdateTXTRecord(_ goacmedns.Account, _ string) error { - return nil -} - -// RegisterAccount returns c.mockAccount and no errors. -func (c mockClient) RegisterAccount(_ []string) (goacmedns.Account, error) { - return c.mockAccount, nil -} - -// mockUpdateClient is a mock implementing the acmeDNSClient interface that -// tracks the calls to UpdateTXTRecord in the records map. -type mockUpdateClient struct { - mockClient - records map[goacmedns.Account]string -} - -// UpdateTXTRecord saves a record value to c.records for the given acct. -func (c mockUpdateClient) UpdateTXTRecord(acct goacmedns.Account, value string) error { - c.records[acct] = value - return nil -} - -// errorUpdateClient is a mock implementing the acmeDNSClient interface that always -// returns errors from errorUpdateClient. -type errorUpdateClient struct { - mockClient -} - -// UpdateTXTRecord always returns an error. -func (c errorUpdateClient) UpdateTXTRecord(_ goacmedns.Account, _ string) error { - return errorClientErr -} - -// errorRegisterClient is a mock implementing the acmeDNSClient interface that always -// returns errors from RegisterAccount. -type errorRegisterClient struct { - mockClient -} - -// RegisterAccount always returns an error. -func (c errorRegisterClient) RegisterAccount(_ []string) (goacmedns.Account, error) { - return goacmedns.Account{}, errorClientErr -} - -// mockStorage is a mock implementing the goacmedns.Storage interface that -// returns static account data and ignores Save. -type mockStorage struct { - accounts map[string]goacmedns.Account -} - -// Save does nothing. -func (m mockStorage) Save() error { - return nil -} - -// Put stores an account for the given domain in m.accounts. -func (m mockStorage) Put(domain string, acct goacmedns.Account) error { - m.accounts[domain] = acct - return nil -} - -// Fetch retrieves an account for the given domain from m.accounts or returns -// goacmedns.ErrDomainNotFound. -func (m mockStorage) Fetch(domain string) (goacmedns.Account, error) { - if acct, ok := m.accounts[domain]; ok { - return acct, nil - } - return goacmedns.Account{}, goacmedns.ErrDomainNotFound -} - -// FetchAll returns all of m.accounts. -func (m mockStorage) FetchAll() map[string]goacmedns.Account { - return m.accounts -} - -// errorPutStorage is a mock implementing the goacmedns.Storage interface that -// always returns errors from Put. -type errorPutStorage struct { - mockStorage -} - -// Put always errors. -func (e errorPutStorage) Put(_ string, _ goacmedns.Account) error { - return errorStorageErr -} - -// errorSaveStorage is a mock implementing the goacmedns.Storage interface that -// always returns errors from Save. -type errorSaveStorage struct { - mockStorage -} - -// Save always errors. -func (e errorSaveStorage) Save() error { - return errorStorageErr -} - -// errorFetchStorage is a mock implementing the goacmedns.Storage interface that -// always returns errors from Fetch. -type errorFetchStorage struct { - mockStorage -} - -// Fetch always errors. -func (e errorFetchStorage) Fetch(_ string) (goacmedns.Account, error) { - return goacmedns.Account{}, errorStorageErr -} - -// FetchAll is a nop for errorFetchStorage. -func (e errorFetchStorage) FetchAll() map[string]goacmedns.Account { - return nil -} - // TestPresent tests that the ACME-DNS Present function for updating a DNS-01 // challenge response TXT record works as expected. func TestPresent(t *testing.T) { @@ -277,7 +143,7 @@ func TestRegister(t *testing.T) { } // Call register for the example domain/fqdn. - err = dp.register(egDomain, egFQDN) + err = dp.register(context.Background(), egDomain, egFQDN) if test.ExpectedError != nil { assert.Equal(t, test.ExpectedError, err) } else { diff --git a/providers/dns/acmedns/internal/fixtures/error.json b/providers/dns/acmedns/internal/fixtures/error.json new file mode 100644 index 000000000..d1b2ba3be --- /dev/null +++ b/providers/dns/acmedns/internal/fixtures/error.json @@ -0,0 +1,3 @@ +{ + "message": "There is an error" +} diff --git a/providers/dns/acmedns/internal/fixtures/fetch-all.json b/providers/dns/acmedns/internal/fixtures/fetch-all.json new file mode 100644 index 000000000..9ea557b38 --- /dev/null +++ b/providers/dns/acmedns/internal/fixtures/fetch-all.json @@ -0,0 +1,16 @@ +{ + "a": { + "fulldomain": "foo.example.com", + "subdomain": "foo", + "username": "user", + "password": "secret", + "server_url": "https://example.com" + }, + "b": { + "fulldomain": "bar.example.com", + "subdomain": "bar", + "username": "user", + "password": "secret", + "server_url": "https://example.com" + } +} diff --git a/providers/dns/acmedns/internal/fixtures/fetch.json b/providers/dns/acmedns/internal/fixtures/fetch.json new file mode 100644 index 000000000..d29cebc5b --- /dev/null +++ b/providers/dns/acmedns/internal/fixtures/fetch.json @@ -0,0 +1,7 @@ +{ + "fulldomain": "foo.example.com", + "subdomain": "foo", + "username": "user", + "password": "secret", + "server_url": "https://example.com" +} diff --git a/providers/dns/acmedns/internal/http_storage.go b/providers/dns/acmedns/internal/http_storage.go new file mode 100644 index 000000000..1a1e8e2ee --- /dev/null +++ b/providers/dns/acmedns/internal/http_storage.go @@ -0,0 +1,139 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/nrdcg/goacmedns" + "github.com/nrdcg/goacmedns/storage" +) + +var _ goacmedns.Storage = (*HTTPStorage)(nil) + +// HTTPStorage is an implementation of [acmedns.Storage] over HTTP. +type HTTPStorage struct { + client *http.Client + baseURL *url.URL +} + +// NewHTTPStorage created a new [HTTPStorage]. +func NewHTTPStorage(baseURL string) (*HTTPStorage, error) { + endpoint, err := url.Parse(baseURL) + if err != nil { + return nil, err + } + + return &HTTPStorage{ + client: &http.Client{Timeout: 2 * time.Minute}, + baseURL: endpoint, + }, nil +} + +func (s *HTTPStorage) Save(_ context.Context) error { + return nil +} + +func (s *HTTPStorage) Put(ctx context.Context, domain string, account goacmedns.Account) error { + req, err := newJSONRequest(ctx, http.MethodPost, s.baseURL.JoinPath(domain), account) + if err != nil { + return fmt.Errorf("unable to create request: %w", err) + } + + return s.do(req, nil) +} + +func (s *HTTPStorage) Fetch(ctx context.Context, domain string) (goacmedns.Account, error) { + req, err := newJSONRequest(ctx, http.MethodGet, s.baseURL.JoinPath(domain), nil) + if err != nil { + return goacmedns.Account{}, fmt.Errorf("unable to create request: %w", err) + } + + var account goacmedns.Account + + err = s.do(req, &account) + if err != nil { + return goacmedns.Account{}, err + } + + return account, nil +} + +func (s *HTTPStorage) FetchAll(ctx context.Context) (map[string]goacmedns.Account, error) { + req, err := newJSONRequest(ctx, http.MethodGet, s.baseURL, nil) + if err != nil { + return nil, err + } + + var mapping map[string]goacmedns.Account + + err = s.do(req, &mapping) + if err != nil { + return nil, err + } + + return mapping, nil +} + +func (s *HTTPStorage) do(req *http.Request, result any) error { + resp, err := s.client.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode == http.StatusNotFound { + return storage.ErrDomainNotFound + } + + if resp.StatusCode/100 != 2 { + return errutils.NewUnexpectedResponseStatusCodeError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} diff --git a/providers/dns/acmedns/internal/http_storage_test.go b/providers/dns/acmedns/internal/http_storage_test.go new file mode 100644 index 000000000..a628f9a6d --- /dev/null +++ b/providers/dns/acmedns/internal/http_storage_test.go @@ -0,0 +1,139 @@ +package internal + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/nrdcg/goacmedns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupTest(t *testing.T, pattern, filename string, statusCode int) *HTTPStorage { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { + if filename == "" { + rw.WriteHeader(statusCode) + return + } + + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + defer func() { _ = file.Close() }() + + rw.WriteHeader(statusCode) + _, err = io.Copy(rw, file) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + }) + + storage, err := NewHTTPStorage(server.URL) + require.NoError(t, err) + + storage.client = server.Client() + + return storage +} + +func TestHTTPStorage_Fetch(t *testing.T) { + storage := setupTest(t, "GET /example.com", "fetch.json", http.StatusOK) + + account, err := storage.Fetch(context.Background(), "example.com") + require.NoError(t, err) + + expected := goacmedns.Account{ + FullDomain: "foo.example.com", + SubDomain: "foo", + Username: "user", + Password: "secret", + ServerURL: "https://example.com", + } + + assert.Equal(t, expected, account) +} + +func TestHTTPStorage_Fetch_error(t *testing.T) { + storage := setupTest(t, "GET /example.com", "error.json", http.StatusInternalServerError) + + _, err := storage.Fetch(context.Background(), "example.com") + require.Error(t, err) +} + +func TestHTTPStorage_FetchAll(t *testing.T) { + storage := setupTest(t, "GET /", "fetch-all.json", http.StatusOK) + + account, err := storage.FetchAll(context.Background()) + require.NoError(t, err) + + expected := map[string]goacmedns.Account{ + "a": { + FullDomain: "foo.example.com", + SubDomain: "foo", + Username: "user", + Password: "secret", + ServerURL: "https://example.com", + }, + "b": { + FullDomain: "bar.example.com", + SubDomain: "bar", + Username: "user", + Password: "secret", + ServerURL: "https://example.com", + }, + } + + assert.Equal(t, expected, account) +} + +func TestHTTPStorage_FetchAll_error(t *testing.T) { + storage := setupTest(t, "GET /", "error.json", http.StatusInternalServerError) + + _, err := storage.FetchAll(context.Background()) + require.Error(t, err) +} + +func TestHTTPStorage_Put(t *testing.T) { + storage := setupTest(t, "POST /example.com", "", http.StatusOK) + + account := goacmedns.Account{ + FullDomain: "foo.example.com", + SubDomain: "foo", + Username: "user", + Password: "secret", + ServerURL: "https://example.com", + } + + err := storage.Put(context.Background(), "example.com", account) + require.NoError(t, err) +} + +func TestHTTPStorage_Put_error(t *testing.T) { + storage := setupTest(t, "POST /example.com", "error.json", http.StatusInternalServerError) + + account := goacmedns.Account{ + FullDomain: "foo.example.com", + SubDomain: "foo", + Username: "user", + Password: "secret", + ServerURL: "https://example.com", + } + + err := storage.Put(context.Background(), "example.com", account) + require.Error(t, err) +} diff --git a/providers/dns/acmedns/internal/readme.md b/providers/dns/acmedns/internal/readme.md new file mode 100644 index 000000000..bccdc5388 --- /dev/null +++ b/providers/dns/acmedns/internal/readme.md @@ -0,0 +1,70 @@ +# HTTP Storage + +## Fetch + +### Request + +Endpoint: `GET /` + +### Response + +Response status code 200. + +Response body (account): + +```json +{ + "fulldomain": "foo.example.com", + "subdomain": "foo", + "username": "user", + "password": "secret", + "server_url": "https://example.com" +} +``` + +## Fetch All + +### Request + +Endpoint: `GET ` + +### Response + +Response status code 200. + +Response body (domain/account mapping): + +```json +{ + "foo.example.com": { + "fulldomain": "foo.example.com", + "subdomain": "foo", + "username": "user", + "password": "secret", + "server_url": "https://example.com" + }, + "bar.example.com": { + "fulldomain": "bar.example.com", + "subdomain": "bar", + "username": "user", + "password": "secret", + "server_url": "https://example.com" + } +} +``` + +## Put + +### Request + +Endpoint: `POST /` + +### Response + +Response status code 200. + +No expected body. + +## Save + +No dedicated endpoint. diff --git a/providers/dns/acmedns/mock_test.go b/providers/dns/acmedns/mock_test.go new file mode 100644 index 000000000..0d188ecc4 --- /dev/null +++ b/providers/dns/acmedns/mock_test.go @@ -0,0 +1,143 @@ +package acmedns + +import ( + "context" + "errors" + + "github.com/nrdcg/goacmedns" + "github.com/nrdcg/goacmedns/storage" +) + +var ( + // errorClientErr is used by the Client mocks that return an error. + errorClientErr = errors.New("errorClient always errors") + // errorStorageErr is used by the Storage mocks that return an error. + errorStorageErr = errors.New("errorStorage always errors") +) + +var egTestAccount = goacmedns.Account{ + FullDomain: "acme-dns." + egDomain, + SubDomain: "random-looking-junk." + egDomain, + Username: "spooky.mulder", + Password: "trustno1", +} + +// mockClient is a mock implementing the acmeDNSClient interface that always +// returns a fixed goacmedns.Account from calls to Register. +type mockClient struct { + mockAccount goacmedns.Account +} + +// UpdateTXTRecord does nothing. +func (c mockClient) UpdateTXTRecord(_ context.Context, _ goacmedns.Account, _ string) error { + return nil +} + +// RegisterAccount returns c.mockAccount and no errors. +func (c mockClient) RegisterAccount(_ context.Context, _ []string) (goacmedns.Account, error) { + return c.mockAccount, nil +} + +// mockUpdateClient is a mock implementing the acmeDNSClient interface that +// tracks the calls to UpdateTXTRecord in the records map. +type mockUpdateClient struct { + mockClient + records map[goacmedns.Account]string +} + +// UpdateTXTRecord saves a record value to c.records for the given acct. +func (c mockUpdateClient) UpdateTXTRecord(_ context.Context, acct goacmedns.Account, value string) error { + c.records[acct] = value + return nil +} + +// errorUpdateClient is a mock implementing the acmeDNSClient interface that always +// returns errors from errorUpdateClient. +type errorUpdateClient struct { + mockClient +} + +// UpdateTXTRecord always returns an error. +func (c errorUpdateClient) UpdateTXTRecord(_ context.Context, _ goacmedns.Account, _ string) error { + return errorClientErr +} + +// errorRegisterClient is a mock implementing the acmeDNSClient interface that always +// returns errors from RegisterAccount. +type errorRegisterClient struct { + mockClient +} + +// RegisterAccount always returns an error. +func (c errorRegisterClient) RegisterAccount(_ context.Context, _ []string) (goacmedns.Account, error) { + return goacmedns.Account{}, errorClientErr +} + +// mockStorage is a mock implementing the goacmedns.Storage interface that +// returns static account data and ignores Save. +type mockStorage struct { + accounts map[string]goacmedns.Account +} + +// Save does nothing. +func (m mockStorage) Save(_ context.Context) error { + return nil +} + +// Put stores an account for the given domain in m.accounts. +func (m mockStorage) Put(_ context.Context, domain string, acct goacmedns.Account) error { + m.accounts[domain] = acct + return nil +} + +// Fetch retrieves an account for the given domain from m.accounts or returns +// goacmedns.ErrDomainNotFound. +func (m mockStorage) Fetch(_ context.Context, domain string) (goacmedns.Account, error) { + if acct, ok := m.accounts[domain]; ok { + return acct, nil + } + return goacmedns.Account{}, storage.ErrDomainNotFound +} + +// FetchAll returns all of m.accounts. +func (m mockStorage) FetchAll(_ context.Context) (map[string]goacmedns.Account, error) { + return m.accounts, nil +} + +// errorPutStorage is a mock implementing the goacmedns.Storage interface that +// always returns errors from Put. +type errorPutStorage struct { + mockStorage +} + +// Put always errors. +func (e errorPutStorage) Put(_ context.Context, _ string, _ goacmedns.Account) error { + return errorStorageErr +} + +// errorSaveStorage is a mock implementing the goacmedns.Storage interface that +// always returns errors from Save. +type errorSaveStorage struct { + mockStorage +} + +// Save always errors. +func (e errorSaveStorage) Save(_ context.Context) error { + return errorStorageErr +} + +// errorFetchStorage is a mock implementing the goacmedns.Storage interface that +// always returns errors from Fetch. +type errorFetchStorage struct { + mockStorage +} + +// Fetch always errors. +func (e errorFetchStorage) Fetch(_ context.Context, _ string) (goacmedns.Account, error) { + return goacmedns.Account{}, errorStorageErr +} + +// FetchAll is a nop for errorFetchStorage. +func (e errorFetchStorage) FetchAll(_ context.Context) (map[string]goacmedns.Account, error) { + return nil, nil +} From 4c65680b7a9b3926ae7ef445623b2daddf642be2 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 10 Jan 2025 16:17:25 +0100 Subject: [PATCH 028/298] netcup: remove TTL option (#2396) --- cmd/zz_gen_cmd_dnshelp.go | 1 - docs/content/dns/zz_gen_netcup.md | 1 - providers/dns/netcup/internal/client_test.go | 3 --- providers/dns/netcup/internal/types.go | 1 - providers/dns/netcup/netcup.go | 10 ++++++---- providers/dns/netcup/netcup.toml | 1 - 6 files changed, 6 insertions(+), 11 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index e529ce234..6f6dc8858 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -2088,7 +2088,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(` - "NETCUP_HTTP_TIMEOUT": API request timeout`) ew.writeln(` - "NETCUP_POLLING_INTERVAL": Time between DNS propagation check`) ew.writeln(` - "NETCUP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "NETCUP_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/netcup`) diff --git a/docs/content/dns/zz_gen_netcup.md b/docs/content/dns/zz_gen_netcup.md index e1973c814..cd19c45c6 100644 --- a/docs/content/dns/zz_gen_netcup.md +++ b/docs/content/dns/zz_gen_netcup.md @@ -54,7 +54,6 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | `NETCUP_HTTP_TIMEOUT` | API request timeout | | `NETCUP_POLLING_INTERVAL` | Time between DNS propagation check | | `NETCUP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `NETCUP_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" %}}). diff --git a/providers/dns/netcup/internal/client_test.go b/providers/dns/netcup/internal/client_test.go index 0e028e881..da70e65e0 100644 --- a/providers/dns/netcup/internal/client_test.go +++ b/providers/dns/netcup/internal/client_test.go @@ -202,7 +202,6 @@ func TestClient_GetDNSRecords(t *testing.T) { Destination: "bGVnbzE=", DeleteRecord: false, State: "yes", - TTL: 300, }, { ID: 2, Hostname: "example2.com", @@ -211,7 +210,6 @@ func TestClient_GetDNSRecords(t *testing.T) { Destination: "bGVnbw==", DeleteRecord: false, State: "yes", - TTL: 300, }} records, err := client.GetDNSRecords(context.Background(), "example.com") @@ -363,7 +361,6 @@ func TestClient_UpdateDNSRecord_Live(t *testing.T) { RecordType: "TXT", Destination: "asdf5678", DeleteRecord: false, - TTL: 120, } // test diff --git a/providers/dns/netcup/internal/types.go b/providers/dns/netcup/internal/types.go index 55212f909..e4cc5ec14 100644 --- a/providers/dns/netcup/internal/types.go +++ b/providers/dns/netcup/internal/types.go @@ -72,7 +72,6 @@ type DNSRecord struct { Destination string `json:"destination"` DeleteRecord bool `json:"deleterecord,omitempty"` State string `json:"state,omitempty"` - TTL int `json:"ttl,omitempty"` } // ResponseMsg as specified in netcup WSDL. diff --git a/providers/dns/netcup/netcup.go b/providers/dns/netcup/netcup.go index b0ef4a2bf..4f4500e90 100644 --- a/providers/dns/netcup/netcup.go +++ b/providers/dns/netcup/netcup.go @@ -24,7 +24,9 @@ const ( EnvAPIKey = envNamespace + "API_KEY" EnvAPIPassword = envNamespace + "API_PASSWORD" - EnvTTL = envNamespace + "TTL" + // Deprecated: the TTL is not configurable on record. + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" @@ -37,16 +39,17 @@ type Config struct { Key string Password string Customer string - TTL int PropagationTimeout time.Duration PollingInterval time.Duration HTTPClient *http.Client + + // Deprecated: the TTL is not configurable on record. + TTL int } // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 15*time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 30*time.Second), HTTPClient: &http.Client{ @@ -120,7 +123,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Hostname: hostname, RecordType: "TXT", Destination: info.Value, - TTL: d.config.TTL, } zone = dns01.UnFqdn(zone) diff --git a/providers/dns/netcup/netcup.toml b/providers/dns/netcup/netcup.toml index 0954d07d6..67c52cf04 100644 --- a/providers/dns/netcup/netcup.toml +++ b/providers/dns/netcup/netcup.toml @@ -19,7 +19,6 @@ lego --email you@example.com --dns netcup -d '*.example.com' -d example.com run [Configuration.Additional] NETCUP_POLLING_INTERVAL = "Time between DNS propagation check" NETCUP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - NETCUP_TTL = "The TTL of the TXT record used for the DNS challenge" NETCUP_HTTP_TIMEOUT = "API request timeout" [Links] From 5f69695771b1feefba8683c9281c16c54f7a49f3 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 11 Jan 2025 15:35:31 +0100 Subject: [PATCH 029/298] docs: improve units and default values (#2397) --- .gitattributes | 1 + cmd/zz_gen_cmd_dnshelp.go | 1104 ++++++++--------- docs/content/dns/zz_gen_alidns.md | 8 +- docs/content/dns/zz_gen_allinkl.md | 6 +- docs/content/dns/zz_gen_arvancloud.md | 8 +- docs/content/dns/zz_gen_auroradns.md | 6 +- docs/content/dns/zz_gen_autodns.md | 8 +- docs/content/dns/zz_gen_azure.md | 6 +- docs/content/dns/zz_gen_azuredns.md | 6 +- docs/content/dns/zz_gen_bindman.md | 6 +- docs/content/dns/zz_gen_bluecat.md | 8 +- docs/content/dns/zz_gen_brandit.md | 8 +- docs/content/dns/zz_gen_bunny.md | 6 +- docs/content/dns/zz_gen_checkdomain.md | 8 +- docs/content/dns/zz_gen_civo.md | 6 +- docs/content/dns/zz_gen_clouddns.md | 8 +- docs/content/dns/zz_gen_cloudflare.md | 8 +- docs/content/dns/zz_gen_cloudns.md | 8 +- docs/content/dns/zz_gen_cloudru.md | 10 +- docs/content/dns/zz_gen_cloudxns.md | 8 +- docs/content/dns/zz_gen_conoha.md | 10 +- docs/content/dns/zz_gen_constellix.md | 8 +- docs/content/dns/zz_gen_corenetworks.md | 10 +- docs/content/dns/zz_gen_cpanel.md | 9 +- docs/content/dns/zz_gen_derak.md | 8 +- docs/content/dns/zz_gen_desec.md | 8 +- docs/content/dns/zz_gen_designate.md | 6 +- docs/content/dns/zz_gen_digitalocean.md | 8 +- docs/content/dns/zz_gen_directadmin.md | 8 +- docs/content/dns/zz_gen_dnshomede.md | 8 +- docs/content/dns/zz_gen_dnsimple.md | 6 +- docs/content/dns/zz_gen_dnsmadeeasy.md | 8 +- docs/content/dns/zz_gen_dnspod.md | 8 +- docs/content/dns/zz_gen_dode.md | 10 +- docs/content/dns/zz_gen_domeneshop.md | 6 +- docs/content/dns/zz_gen_dreamhost.md | 7 +- docs/content/dns/zz_gen_duckdns.md | 10 +- docs/content/dns/zz_gen_dyn.md | 8 +- docs/content/dns/zz_gen_dynu.md | 8 +- docs/content/dns/zz_gen_easydns.md | 10 +- docs/content/dns/zz_gen_edgedns.md | 6 +- docs/content/dns/zz_gen_efficientip.md | 7 +- docs/content/dns/zz_gen_epik.md | 8 +- docs/content/dns/zz_gen_exec.md | 10 +- docs/content/dns/zz_gen_exoscale.md | 8 +- docs/content/dns/zz_gen_freemyip.md | 10 +- docs/content/dns/zz_gen_gandi.md | 8 +- docs/content/dns/zz_gen_gandiv5.md | 8 +- docs/content/dns/zz_gen_gcloud.md | 6 +- docs/content/dns/zz_gen_gcore.md | 8 +- docs/content/dns/zz_gen_glesys.md | 8 +- docs/content/dns/zz_gen_godaddy.md | 8 +- docs/content/dns/zz_gen_googledomains.md | 6 +- docs/content/dns/zz_gen_hetzner.md | 8 +- docs/content/dns/zz_gen_hostingde.md | 8 +- docs/content/dns/zz_gen_hosttech.md | 8 +- docs/content/dns/zz_gen_httpnet.md | 8 +- docs/content/dns/zz_gen_httpreq.md | 6 +- docs/content/dns/zz_gen_huaweicloud.md | 8 +- docs/content/dns/zz_gen_hurricane.md | 8 +- docs/content/dns/zz_gen_hyperone.md | 7 +- docs/content/dns/zz_gen_ibmcloud.md | 8 +- docs/content/dns/zz_gen_iij.md | 6 +- docs/content/dns/zz_gen_iijdpf.md | 6 +- docs/content/dns/zz_gen_infoblox.md | 16 +- docs/content/dns/zz_gen_infomaniak.md | 8 +- docs/content/dns/zz_gen_internetbs.md | 8 +- docs/content/dns/zz_gen_inwx.md | 6 +- docs/content/dns/zz_gen_ionos.md | 8 +- docs/content/dns/zz_gen_ipv64.md | 7 +- docs/content/dns/zz_gen_iwantmyname.md | 8 +- docs/content/dns/zz_gen_joker.md | 10 +- docs/content/dns/zz_gen_liara.md | 8 +- docs/content/dns/zz_gen_lightsail.md | 4 +- docs/content/dns/zz_gen_limacity.md | 10 +- docs/content/dns/zz_gen_linode.md | 8 +- docs/content/dns/zz_gen_liquidweb.md | 8 +- docs/content/dns/zz_gen_loopia.md | 8 +- docs/content/dns/zz_gen_luadns.md | 8 +- docs/content/dns/zz_gen_mailinabox.md | 4 +- docs/content/dns/zz_gen_manageengine.md | 7 +- docs/content/dns/zz_gen_metaname.md | 6 +- docs/content/dns/zz_gen_mijnhost.md | 10 +- docs/content/dns/zz_gen_mittwald.md | 10 +- docs/content/dns/zz_gen_mydnsjp.md | 7 +- docs/content/dns/zz_gen_mythicbeasts.md | 8 +- docs/content/dns/zz_gen_namecheap.md | 8 +- docs/content/dns/zz_gen_namedotcom.md | 8 +- docs/content/dns/zz_gen_namesilo.md | 6 +- docs/content/dns/zz_gen_nearlyfreespeech.md | 10 +- docs/content/dns/zz_gen_netcup.md | 6 +- docs/content/dns/zz_gen_netlify.md | 8 +- docs/content/dns/zz_gen_nicmanager.md | 8 +- docs/content/dns/zz_gen_nifcloud.md | 8 +- docs/content/dns/zz_gen_njalla.md | 8 +- docs/content/dns/zz_gen_nodion.md | 8 +- docs/content/dns/zz_gen_ns1.md | 8 +- docs/content/dns/zz_gen_oraclecloud.md | 7 +- docs/content/dns/zz_gen_otc.md | 10 +- docs/content/dns/zz_gen_ovh.md | 8 +- docs/content/dns/zz_gen_pdns.md | 8 +- docs/content/dns/zz_gen_plesk.md | 8 +- docs/content/dns/zz_gen_porkbun.md | 8 +- docs/content/dns/zz_gen_rackspace.md | 8 +- docs/content/dns/zz_gen_rainyun.md | 8 +- docs/content/dns/zz_gen_rcodezero.md | 8 +- docs/content/dns/zz_gen_regfish.md | 8 +- docs/content/dns/zz_gen_regru.md | 8 +- docs/content/dns/zz_gen_rfc2136.md | 10 +- docs/content/dns/zz_gen_rimuhosting.md | 8 +- docs/content/dns/zz_gen_route53.md | 6 +- docs/content/dns/zz_gen_safedns.md | 8 +- docs/content/dns/zz_gen_sakuracloud.md | 8 +- docs/content/dns/zz_gen_scaleway.md | 6 +- docs/content/dns/zz_gen_selectel.md | 8 +- docs/content/dns/zz_gen_selectelv2.md | 8 +- docs/content/dns/zz_gen_selfhostde.md | 8 +- docs/content/dns/zz_gen_servercow.md | 8 +- docs/content/dns/zz_gen_shellrent.md | 8 +- docs/content/dns/zz_gen_simply.md | 8 +- docs/content/dns/zz_gen_sonic.md | 10 +- docs/content/dns/zz_gen_stackpath.md | 6 +- docs/content/dns/zz_gen_technitium.md | 8 +- docs/content/dns/zz_gen_tencentcloud.md | 8 +- docs/content/dns/zz_gen_timewebcloud.md | 6 +- docs/content/dns/zz_gen_transip.md | 6 +- docs/content/dns/zz_gen_ultradns.md | 6 +- docs/content/dns/zz_gen_variomedia.md | 10 +- docs/content/dns/zz_gen_vegadns.md | 6 +- docs/content/dns/zz_gen_vercel.md | 8 +- docs/content/dns/zz_gen_versio.md | 10 +- docs/content/dns/zz_gen_vinyldns.md | 6 +- docs/content/dns/zz_gen_vkcloud.md | 6 +- docs/content/dns/zz_gen_volcengine.md | 8 +- docs/content/dns/zz_gen_vscale.md | 8 +- docs/content/dns/zz_gen_vultr.md | 8 +- docs/content/dns/zz_gen_webnames.md | 7 +- docs/content/dns/zz_gen_websupport.md | 10 +- docs/content/dns/zz_gen_wedos.md | 8 +- docs/content/dns/zz_gen_westcn.md | 8 +- docs/content/dns/zz_gen_yandex.md | 8 +- docs/content/dns/zz_gen_yandex360.md | 8 +- docs/content/dns/zz_gen_yandexcloud.md | 6 +- docs/content/dns/zz_gen_zoneee.md | 7 +- docs/content/dns/zz_gen_zonomi.md | 8 +- providers/dns/alidns/alidns.toml | 8 +- providers/dns/allinkl/allinkl.toml | 6 +- providers/dns/arvancloud/arvancloud.toml | 8 +- providers/dns/auroradns/auroradns.toml | 6 +- providers/dns/autodns/autodns.toml | 8 +- providers/dns/azure/azure.toml | 6 +- providers/dns/azuredns/azuredns.toml | 6 +- providers/dns/bindman/bindman.toml | 6 +- providers/dns/bluecat/bluecat.toml | 8 +- providers/dns/brandit/brandit.toml | 8 +- providers/dns/bunny/bunny.go | 2 +- providers/dns/bunny/bunny.toml | 6 +- providers/dns/checkdomain/checkdomain.toml | 8 +- providers/dns/civo/civo.toml | 12 +- providers/dns/clouddns/clouddns.toml | 8 +- providers/dns/cloudflare/cloudflare.go | 2 +- providers/dns/cloudflare/cloudflare.toml | 8 +- providers/dns/cloudns/cloudns.toml | 8 +- providers/dns/cloudru/cloudru.toml | 10 +- providers/dns/cloudxns/cloudxns.toml | 8 +- providers/dns/conoha/conoha.toml | 10 +- providers/dns/constellix/constellix.toml | 8 +- providers/dns/corenetworks/corenetworks.toml | 10 +- providers/dns/cpanel/cpanel.toml | 9 +- providers/dns/derak/derak.toml | 8 +- providers/dns/desec/desec.toml | 8 +- providers/dns/designate/designate.toml | 6 +- providers/dns/digitalocean/digitalocean.go | 2 +- providers/dns/digitalocean/digitalocean.toml | 8 +- providers/dns/directadmin/directadmin.toml | 8 +- providers/dns/dnshomede/dnshomede.toml | 8 +- providers/dns/dnsimple/dnsimple.toml | 6 +- providers/dns/dnsmadeeasy/dnsmadeeasy.toml | 8 +- providers/dns/dnspod/dnspod.toml | 8 +- providers/dns/dode/dode.toml | 10 +- providers/dns/domeneshop/domeneshop.toml | 6 +- providers/dns/dreamhost/dreamhost.toml | 7 +- providers/dns/duckdns/duckdns.toml | 10 +- providers/dns/dyn/dyn.toml | 8 +- providers/dns/dynu/dynu.toml | 8 +- providers/dns/easydns/easydns.toml | 10 +- providers/dns/edgedns/edgedns.toml | 6 +- providers/dns/efficientip/efficientip.toml | 7 +- providers/dns/epik/epik.toml | 8 +- providers/dns/exec/exec.toml | 10 +- providers/dns/exoscale/exoscale.toml | 8 +- providers/dns/freemyip/freemyip.toml | 10 +- providers/dns/gandi/gandi.toml | 8 +- providers/dns/gandiv5/gandiv5.toml | 8 +- providers/dns/gcloud/gcloud.toml | 6 +- providers/dns/gcore/gcore.toml | 8 +- providers/dns/glesys/glesys.toml | 8 +- providers/dns/godaddy/godaddy.go | 2 +- providers/dns/godaddy/godaddy.toml | 8 +- providers/dns/googledomains/googledomains.go | 2 +- .../dns/googledomains/googledomains.toml | 6 +- providers/dns/hetzner/hetzner.go | 2 +- providers/dns/hetzner/hetzner.toml | 8 +- providers/dns/hostingde/hostingde.go | 2 +- providers/dns/hostingde/hostingde.toml | 8 +- providers/dns/hosttech/hosttech.toml | 8 +- providers/dns/httpnet/httpnet.go | 2 +- providers/dns/httpnet/httpnet.toml | 8 +- providers/dns/httpreq/httpreq.toml | 6 +- providers/dns/huaweicloud/huaweicloud.toml | 8 +- providers/dns/hurricane/hurricane.toml | 8 +- providers/dns/hyperone/hyperone.toml | 7 +- providers/dns/ibmcloud/ibmcloud.toml | 8 +- providers/dns/iij/iij.toml | 6 +- providers/dns/iijdpf/iijdpf.toml | 6 +- providers/dns/infoblox/infoblox.toml | 16 +- providers/dns/infomaniak/infomaniak.toml | 8 +- providers/dns/internetbs/internetbs.toml | 8 +- providers/dns/inwx/inwx.go | 2 +- providers/dns/inwx/inwx.toml | 6 +- providers/dns/ionos/ionos.toml | 8 +- providers/dns/ipv64/ipv64.toml | 7 +- providers/dns/iwantmyname/iwantmyname.toml | 8 +- providers/dns/joker/joker.toml | 10 +- providers/dns/liara/liara.toml | 8 +- providers/dns/lightsail/lightsail.toml | 4 +- providers/dns/limacity/limacity.toml | 10 +- providers/dns/linode/linode.go | 4 +- providers/dns/linode/linode.toml | 8 +- providers/dns/liquidweb/liquidweb.go | 2 +- providers/dns/liquidweb/liquidweb.toml | 8 +- providers/dns/loopia/loopia.go | 4 +- providers/dns/loopia/loopia.toml | 8 +- providers/dns/luadns/luadns.go | 2 +- providers/dns/luadns/luadns.toml | 8 +- providers/dns/mailinabox/mailinabox.toml | 4 +- providers/dns/manageengine/manageengine.toml | 7 +- providers/dns/metaname/metaname.toml | 6 +- providers/dns/mijnhost/mijnhost.toml | 10 +- providers/dns/mittwald/mittwald.toml | 10 +- providers/dns/mydnsjp/mydnsjp.go | 2 +- providers/dns/mydnsjp/mydnsjp.toml | 7 +- providers/dns/mythicbeasts/mythicbeasts.toml | 8 +- providers/dns/namecheap/namecheap.go | 4 +- providers/dns/namecheap/namecheap.toml | 8 +- providers/dns/namedotcom/namedotcom.toml | 8 +- providers/dns/namesilo/namesilo.toml | 6 +- .../nearlyfreespeech/nearlyfreespeech.toml | 10 +- providers/dns/netcup/netcup.toml | 6 +- providers/dns/netlify/netlify.toml | 8 +- providers/dns/nicmanager/nicmanager.toml | 8 +- providers/dns/nifcloud/nifcloud.toml | 8 +- providers/dns/njalla/njalla.toml | 8 +- providers/dns/nodion/nodion.toml | 8 +- providers/dns/ns1/ns1.toml | 8 +- providers/dns/oraclecloud/oraclecloud.go | 2 +- providers/dns/oraclecloud/oraclecloud.toml | 7 +- providers/dns/otc/otc.toml | 10 +- providers/dns/ovh/ovh.toml | 8 +- providers/dns/pdns/pdns.toml | 8 +- providers/dns/plesk/plesk.toml | 8 +- providers/dns/porkbun/porkbun.toml | 8 +- providers/dns/rackspace/rackspace.toml | 8 +- providers/dns/rainyun/rainyun.toml | 8 +- providers/dns/rcodezero/rcodezero.go | 2 +- providers/dns/rcodezero/rcodezero.toml | 8 +- providers/dns/regfish/regfish.toml | 8 +- providers/dns/regru/regru.toml | 8 +- providers/dns/rfc2136/rfc2136.go | 4 +- providers/dns/rfc2136/rfc2136.toml | 10 +- providers/dns/rimuhosting/rimuhosting.toml | 8 +- providers/dns/route53/route53.toml | 6 +- providers/dns/safedns/safedns.toml | 8 +- providers/dns/sakuracloud/sakuracloud.toml | 8 +- providers/dns/scaleway/scaleway.toml | 6 +- providers/dns/selectel/selectel.go | 2 +- providers/dns/selectel/selectel.toml | 8 +- providers/dns/selectelv2/selectelv2.toml | 8 +- providers/dns/selfhostde/selfhostde.toml | 8 +- providers/dns/servercow/servercow.go | 2 +- providers/dns/servercow/servercow.toml | 8 +- providers/dns/shellrent/shellrent.toml | 8 +- providers/dns/simply/simply.toml | 8 +- providers/dns/sonic/sonic.toml | 10 +- providers/dns/stackpath/stackpath.go | 2 +- providers/dns/stackpath/stackpath.toml | 6 +- providers/dns/technitium/technitium.toml | 8 +- providers/dns/tencentcloud/tencentcloud.toml | 8 +- providers/dns/timewebcloud/timewebcloud.toml | 6 +- providers/dns/transip/transip.toml | 6 +- providers/dns/ultradns/ultradns.go | 2 +- providers/dns/ultradns/ultradns.toml | 6 +- providers/dns/variomedia/variomedia.toml | 10 +- providers/dns/vegadns/vegadns.go | 2 +- providers/dns/vegadns/vegadns.toml | 6 +- providers/dns/vercel/vercel.go | 2 +- providers/dns/vercel/vercel.toml | 8 +- providers/dns/versio/versio.go | 2 +- providers/dns/versio/versio.toml | 10 +- providers/dns/vinyldns/vinyldns.toml | 6 +- providers/dns/vkcloud/vkcloud.toml | 6 +- providers/dns/volcengine/volcengine.go | 2 +- providers/dns/volcengine/volcengine.toml | 8 +- providers/dns/vscale/vscale.go | 2 +- providers/dns/vscale/vscale.toml | 8 +- providers/dns/vultr/vultr.toml | 8 +- providers/dns/webnames/webnames.toml | 7 +- providers/dns/websupport/websupport.toml | 10 +- providers/dns/wedos/wedos.toml | 8 +- providers/dns/westcn/westcn.toml | 8 +- providers/dns/yandex/yandex.toml | 8 +- providers/dns/yandex360/yandex360.toml | 8 +- providers/dns/yandexcloud/yandexcloud.toml | 6 +- providers/dns/zoneee/zoneee.toml | 7 +- providers/dns/zonomi/zonomi.toml | 8 +- 315 files changed, 1692 insertions(+), 1709 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..a91e62484 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +**/zz_gen_*.* linguist-generated diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 6f6dc8858..203cff36f 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -198,10 +198,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "ALICLOUD_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "ALICLOUD_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "ALICLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "ALICLOUD_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "ALICLOUD_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "ALICLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "ALICLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "ALICLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/alidns`) @@ -219,9 +219,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "ALL_INKL_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "ALL_INKL_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "ALL_INKL_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "ALL_INKL_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "ALL_INKL_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "ALL_INKL_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/allinkl`) @@ -238,10 +238,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "ARVANCLOUD_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "ARVANCLOUD_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "ARVANCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "ARVANCLOUD_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "ARVANCLOUD_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "ARVANCLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "ARVANCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "ARVANCLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/arvancloud`) @@ -260,9 +260,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "AURORA_ENDPOINT": API endpoint URL`) - ew.writeln(` - "AURORA_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "AURORA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "AURORA_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "AURORA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "AURORA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "AURORA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/auroradns`) @@ -282,10 +282,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "AUTODNS_CONTEXT": API context (4 for production, 1 for testing. Defaults to 4)`) ew.writeln(` - "AUTODNS_ENDPOINT": API endpoint URL, defaults to https://api.autodns.com/v1/`) - ew.writeln(` - "AUTODNS_HTTP_TIMEOUT": API request timeout, defaults to 30 seconds`) - ew.writeln(` - "AUTODNS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "AUTODNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "AUTODNS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "AUTODNS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "AUTODNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "AUTODNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "AUTODNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/autodns`) @@ -309,10 +309,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "AZURE_METADATA_ENDPOINT": Metadata Service endpoint URL`) - ew.writeln(` - "AZURE_POLLING_INTERVAL": Time between DNS propagation check`) + ew.writeln(` - "AZURE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "AZURE_PRIVATE_ZONE": Set to true to use Azure Private DNS Zones and not public`) - ew.writeln(` - "AZURE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "AZURE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "AZURE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "AZURE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln(` - "AZURE_ZONE_NAME": Zone name to use inside Azure DNS service to add the TXT record in`) ew.writeln() @@ -336,13 +336,13 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(` - "AZURE_AUTH_METHOD": Specify which authentication method to use`) ew.writeln(` - "AZURE_AUTH_MSI_TIMEOUT": Managed Identity timeout duration`) ew.writeln(` - "AZURE_ENVIRONMENT": Azure environment, one of: public, usgovernment, and china`) - ew.writeln(` - "AZURE_POLLING_INTERVAL": Time between DNS propagation check`) + ew.writeln(` - "AZURE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "AZURE_PRIVATE_ZONE": Set to true to use Azure Private DNS Zones and not public`) - ew.writeln(` - "AZURE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "AZURE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln(` - "AZURE_RESOURCE_GROUP": DNS zone resource group`) ew.writeln(` - "AZURE_SERVICEDISCOVERY_FILTER": Advanced ServiceDiscovery filter using Kusto query condition`) ew.writeln(` - "AZURE_SUBSCRIPTION_ID": DNS zone subscription ID`) - ew.writeln(` - "AZURE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "AZURE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln(` - "AZURE_ZONE_NAME": Zone name to use inside Azure DNS service to add the TXT record in`) ew.writeln() @@ -360,9 +360,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "BINDMAN_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "BINDMAN_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "BINDMAN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "BINDMAN_HTTP_TIMEOUT": API request timeout in seconds (Default: 60)`) + ew.writeln(` - "BINDMAN_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "BINDMAN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/bindman`) @@ -383,11 +383,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "BLUECAT_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "BLUECAT_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "BLUECAT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "BLUECAT_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "BLUECAT_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "BLUECAT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln(` - "BLUECAT_SKIP_DEPLOY": Skip deployements`) - ew.writeln(` - "BLUECAT_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "BLUECAT_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/bluecat`) @@ -405,10 +405,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "BRANDIT_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "BRANDIT_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "BRANDIT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "BRANDIT_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "BRANDIT_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "BRANDIT_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "BRANDIT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 600)`) + ew.writeln(` - "BRANDIT_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/brandit`) @@ -425,9 +425,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "BUNNY_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "BUNNY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "BUNNY_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "BUNNY_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "BUNNY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "BUNNY_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/bunny`) @@ -445,10 +445,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "CHECKDOMAIN_ENDPOINT": API endpoint URL, defaults to https://api.checkdomain.de`) - ew.writeln(` - "CHECKDOMAIN_HTTP_TIMEOUT": API request timeout, defaults to 30 seconds`) - ew.writeln(` - "CHECKDOMAIN_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "CHECKDOMAIN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "CHECKDOMAIN_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "CHECKDOMAIN_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "CHECKDOMAIN_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 300)`) + ew.writeln(` - "CHECKDOMAIN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 7)`) + ew.writeln(` - "CHECKDOMAIN_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/checkdomain`) @@ -465,9 +465,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "CIVO_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "CIVO_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "CIVO_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "CIVO_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 30)`) + ew.writeln(` - "CIVO_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) + ew.writeln(` - "CIVO_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/civo`) @@ -486,10 +486,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "CLOUDDNS_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "CLOUDDNS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "CLOUDDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "CLOUDDNS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "CLOUDDNS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "CLOUDDNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 5)`) + ew.writeln(` - "CLOUDDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "CLOUDDNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/clouddns`) @@ -513,10 +513,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "CLOUDFLARE_HTTP_TIMEOUT": API request timeout (in seconds)`) - ew.writeln(` - "CLOUDFLARE_POLLING_INTERVAL": Time between DNS propagation check (in seconds)`) - ew.writeln(` - "CLOUDFLARE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation (in seconds)`) - ew.writeln(` - "CLOUDFLARE_TTL": The TTL of the TXT record used for the DNS challenge (in seconds)`) + ew.writeln(` - "CLOUDFLARE_HTTP_TIMEOUT": API request timeout in seconds (Default: )`) + ew.writeln(` - "CLOUDFLARE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "CLOUDFLARE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "CLOUDFLARE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/cloudflare`) @@ -534,11 +534,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "CLOUDNS_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "CLOUDNS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "CLOUDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "CLOUDNS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "CLOUDNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "CLOUDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 180)`) ew.writeln(` - "CLOUDNS_SUB_AUTH_ID": The API sub user ID`) - ew.writeln(` - "CLOUDNS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "CLOUDNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/cloudns`) @@ -557,11 +557,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "CLOUDRU_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "CLOUDRU_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "CLOUDRU_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "CLOUDRU_SEQUENCE_INTERVAL": Time between sequential requests`) - ew.writeln(` - "CLOUDRU_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "CLOUDRU_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "CLOUDRU_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 5)`) + ew.writeln(` - "CLOUDRU_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) + ew.writeln(` - "CLOUDRU_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 120)`) + ew.writeln(` - "CLOUDRU_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/cloudru`) @@ -579,10 +579,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "CLOUDXNS_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "CLOUDXNS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "CLOUDXNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "CLOUDXNS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "CLOUDXNS_HTTP_TIMEOUT": API request timeout in seconds (Default: )`) + ew.writeln(` - "CLOUDXNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: )`) + ew.writeln(` - "CLOUDXNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: )`) + ew.writeln(` - "CLOUDXNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: )`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/cloudxns`) @@ -601,11 +601,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "CONOHA_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "CONOHA_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "CONOHA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "CONOHA_REGION": The region`) - ew.writeln(` - "CONOHA_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "CONOHA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "CONOHA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "CONOHA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "CONOHA_REGION": The region (Default: tyo1)`) + ew.writeln(` - "CONOHA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/conoha`) @@ -623,10 +623,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "CONSTELLIX_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "CONSTELLIX_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "CONSTELLIX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "CONSTELLIX_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "CONSTELLIX_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "CONSTELLIX_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "CONSTELLIX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "CONSTELLIX_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/constellix`) @@ -644,11 +644,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "CORENETWORKS_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "CORENETWORKS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "CORENETWORKS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "CORENETWORKS_SEQUENCE_INTERVAL": Time between sequential requests`) - ew.writeln(` - "CORENETWORKS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "CORENETWORKS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "CORENETWORKS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "CORENETWORKS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "CORENETWORKS_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) + ew.writeln(` - "CORENETWORKS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/corenetworks`) @@ -667,12 +667,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "CPANEL_HTTP_TIMEOUT": API request timeout`) + ew.writeln(` - "CPANEL_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "CPANEL_MODE": use cpanel API or WHM API (Default: cpanel)`) - ew.writeln(` - "CPANEL_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "CPANEL_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "CPANEL_REGION": The region`) - ew.writeln(` - "CPANEL_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "CPANEL_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "CPANEL_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "CPANEL_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/cpanel`) @@ -689,10 +688,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "DERAK_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "DERAK_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "DERAK_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "DERAK_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "DERAK_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "DERAK_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 5)`) + ew.writeln(` - "DERAK_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "DERAK_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln(` - "DERAK_WEBSITE_ID": Force the zone/website ID`) ew.writeln() @@ -710,10 +709,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "DESEC_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "DESEC_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "DESEC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "DESEC_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "DESEC_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "DESEC_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 4)`) + ew.writeln(` - "DESEC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "DESEC_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/desec`) @@ -738,9 +737,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "DESIGNATE_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "DESIGNATE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "DESIGNATE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "DESIGNATE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "DESIGNATE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 600)`) + ew.writeln(` - "DESIGNATE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 10)`) ew.writeln(` - "DESIGNATE_ZONE_NAME": The zone name to use in the OpenStack Project to manage TXT records.`) ew.writeln(` - "OS_PROJECT_ID": Project ID`) ew.writeln(` - "OS_TENANT_NAME": Tenant name (deprecated see OS_PROJECT_NAME and OS_PROJECT_ID)`) @@ -761,10 +760,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "DO_API_URL": The URL of the API`) - ew.writeln(` - "DO_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "DO_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "DO_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "DO_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "DO_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "DO_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 5)`) + ew.writeln(` - "DO_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "DO_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 30)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/digitalocean`) @@ -783,10 +782,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "DIRECTADMIN_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "DIRECTADMIN_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "DIRECTADMIN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "DIRECTADMIN_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "DIRECTADMIN_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "DIRECTADMIN_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 5)`) + ew.writeln(` - "DIRECTADMIN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "DIRECTADMIN_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 30)`) ew.writeln(` - "DIRECTADMIN_ZONE_NAME": Zone name used to add the TXT record`) ew.writeln() @@ -804,10 +803,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "DNSHOMEDE_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "DNSHOMEDE_POLLING_INTERVAL": Time between DNS propagation checks`) - ew.writeln(` - "DNSHOMEDE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation; defaults to 300s (5 minutes)`) - ew.writeln(` - "DNSHOMEDE_SEQUENCE_INTERVAL": Time between sequential requests`) + ew.writeln(` - "DNSHOMEDE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "DNSHOMEDE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 1200)`) + ew.writeln(` - "DNSHOMEDE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 2)`) + ew.writeln(` - "DNSHOMEDE_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/dnshomede`) @@ -825,9 +824,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "DNSIMPLE_BASE_URL": API endpoint URL`) - ew.writeln(` - "DNSIMPLE_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "DNSIMPLE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "DNSIMPLE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "DNSIMPLE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "DNSIMPLE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "DNSIMPLE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/dnsimple`) @@ -845,11 +844,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "DNSMADEEASY_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "DNSMADEEASY_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "DNSMADEEASY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "DNSMADEEASY_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "DNSMADEEASY_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "DNSMADEEASY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln(` - "DNSMADEEASY_SANDBOX": Activate the sandbox (boolean)`) - ew.writeln(` - "DNSMADEEASY_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "DNSMADEEASY_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/dnsmadeeasy`) @@ -866,10 +865,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "DNSPOD_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "DNSPOD_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "DNSPOD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "DNSPOD_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "DNSPOD_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "DNSPOD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "DNSPOD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "DNSPOD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/dnspod`) @@ -886,11 +885,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "DODE_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "DODE_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "DODE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "DODE_SEQUENCE_INTERVAL": Time between sequential requests`) - ew.writeln(` - "DODE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "DODE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "DODE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "DODE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "DODE_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) + ew.writeln(` - "DODE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/dode`) @@ -908,9 +907,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "DOMENESHOP_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "DOMENESHOP_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "DOMENESHOP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "DOMENESHOP_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "DOMENESHOP_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 20)`) + ew.writeln(` - "DOMENESHOP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/domeneshop`) @@ -927,10 +926,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "DREAMHOST_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "DREAMHOST_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "DREAMHOST_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "DREAMHOST_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "DREAMHOST_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "DREAMHOST_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 60)`) + ew.writeln(` - "DREAMHOST_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 3600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/dreamhost`) @@ -947,11 +945,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "DUCKDNS_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "DUCKDNS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "DUCKDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "DUCKDNS_SEQUENCE_INTERVAL": Time between sequential requests`) - ew.writeln(` - "DUCKDNS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "DUCKDNS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "DUCKDNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "DUCKDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "DUCKDNS_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) + ew.writeln(` - "DUCKDNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/duckdns`) @@ -970,10 +968,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "DYN_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "DYN_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "DYN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "DYN_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "DYN_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "DYN_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "DYN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "DYN_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/dyn`) @@ -990,10 +988,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "DYNU_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "DYNU_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "DYNU_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "DYNU_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "DYNU_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "DYNU_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "DYNU_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 180)`) + ew.writeln(` - "DYNU_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/dynu`) @@ -1012,11 +1010,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "EASYDNS_ENDPOINT": The endpoint URL of the API Server`) - ew.writeln(` - "EASYDNS_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "EASYDNS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "EASYDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "EASYDNS_SEQUENCE_INTERVAL": Time between sequential requests`) - ew.writeln(` - "EASYDNS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "EASYDNS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "EASYDNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "EASYDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "EASYDNS_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) + ew.writeln(` - "EASYDNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/easydns`) @@ -1038,9 +1036,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "AKAMAI_POLLING_INTERVAL": Time between DNS propagation check. Default: 15 seconds`) - ew.writeln(` - "AKAMAI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation. Default: 3 minutes`) - ew.writeln(` - "AKAMAI_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "AKAMAI_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 15)`) + ew.writeln(` - "AKAMAI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 180)`) + ew.writeln(` - "AKAMAI_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/edgedns`) @@ -1060,11 +1058,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "EFFICIENTIP_HTTP_TIMEOUT": API request timeout`) + ew.writeln(` - "EFFICIENTIP_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) ew.writeln(` - "EFFICIENTIP_INSECURE_SKIP_VERIFY": Whether or not to verify EfficientIP API certificate`) - ew.writeln(` - "EFFICIENTIP_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "EFFICIENTIP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "EFFICIENTIP_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "EFFICIENTIP_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "EFFICIENTIP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln(` - "EFFICIENTIP_VIEW_NAME": View name (ex: external)`) ew.writeln() @@ -1082,10 +1079,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "EPIK_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "EPIK_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "EPIK_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "EPIK_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "EPIK_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "EPIK_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "EPIK_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "EPIK_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/epik`) @@ -1114,10 +1111,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "EXOSCALE_ENDPOINT": API endpoint URL`) - ew.writeln(` - "EXOSCALE_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "EXOSCALE_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "EXOSCALE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "EXOSCALE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "EXOSCALE_HTTP_TIMEOUT": API request timeout in seconds (Default: 60)`) + ew.writeln(` - "EXOSCALE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "EXOSCALE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "EXOSCALE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/exoscale`) @@ -1134,11 +1131,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "FREEMYIP_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "FREEMYIP_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "FREEMYIP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "FREEMYIP_SEQUENCE_INTERVAL": Time between sequential requests`) - ew.writeln(` - "FREEMYIP_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "FREEMYIP_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "FREEMYIP_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "FREEMYIP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "FREEMYIP_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) + ew.writeln(` - "FREEMYIP_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/freemyip`) @@ -1155,10 +1152,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "GANDI_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "GANDI_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "GANDI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "GANDI_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "GANDI_HTTP_TIMEOUT": API request timeout in seconds (Default: 60)`) + ew.writeln(` - "GANDI_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 60)`) + ew.writeln(` - "GANDI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 2400)`) + ew.writeln(` - "GANDI_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/gandi`) @@ -1176,10 +1173,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "GANDIV5_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "GANDIV5_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "GANDIV5_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "GANDIV5_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "GANDIV5_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "GANDIV5_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 20)`) + ew.writeln(` - "GANDIV5_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 1200)`) + ew.writeln(` - "GANDIV5_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/gandiv5`) @@ -1200,9 +1197,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "GCE_ALLOW_PRIVATE_ZONE": Allows requested domain to be in private DNS zone, works only with a private ACME server (by default: false)`) - ew.writeln(` - "GCE_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "GCE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "GCE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "GCE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 5)`) + ew.writeln(` - "GCE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 180)`) + ew.writeln(` - "GCE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln(` - "GCE_ZONE_ID": Allows to skip the automatic detection of the zone`) ew.writeln() @@ -1220,10 +1217,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "GCORE_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "GCORE_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "GCORE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "GCORE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "GCORE_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "GCORE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 20)`) + ew.writeln(` - "GCORE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 360)`) + ew.writeln(` - "GCORE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/gcore`) @@ -1241,10 +1238,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "GLESYS_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "GLESYS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "GLESYS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "GLESYS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "GLESYS_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "GLESYS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 20)`) + ew.writeln(` - "GLESYS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 1200)`) + ew.writeln(` - "GLESYS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/glesys`) @@ -1262,10 +1259,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "GODADDY_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "GODADDY_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "GODADDY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "GODADDY_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "GODADDY_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "GODADDY_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "GODADDY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "GODADDY_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/godaddy`) @@ -1282,9 +1279,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "GOOGLE_DOMAINS_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "GOOGLE_DOMAINS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "GOOGLE_DOMAINS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "GOOGLE_DOMAINS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "GOOGLE_DOMAINS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "GOOGLE_DOMAINS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/googledomains`) @@ -1301,10 +1298,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "HETZNER_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "HETZNER_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "HETZNER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "HETZNER_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "HETZNER_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "HETZNER_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "HETZNER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "HETZNER_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/hetzner`) @@ -1321,10 +1318,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "HOSTINGDE_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "HOSTINGDE_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "HOSTINGDE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "HOSTINGDE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "HOSTINGDE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "HOSTINGDE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "HOSTINGDE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "HOSTINGDE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln(` - "HOSTINGDE_ZONE_NAME": Zone name in ACE format`) ew.writeln() @@ -1343,10 +1340,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "HOSTTECH_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "HOSTTECH_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "HOSTTECH_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "HOSTTECH_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "HOSTTECH_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "HOSTTECH_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "HOSTTECH_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "HOSTTECH_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/hosttech`) @@ -1363,10 +1360,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "HTTPNET_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "HTTPNET_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "HTTPNET_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "HTTPNET_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "HTTPNET_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "HTTPNET_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "HTTPNET_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "HTTPNET_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln(` - "HTTPNET_ZONE_NAME": Zone name in ACE format`) ew.writeln() @@ -1385,10 +1382,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "HTTPREQ_HTTP_TIMEOUT": API request timeout`) + ew.writeln(` - "HTTPREQ_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "HTTPREQ_PASSWORD": Basic authentication password`) - ew.writeln(` - "HTTPREQ_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "HTTPREQ_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "HTTPREQ_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "HTTPREQ_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln(` - "HTTPREQ_USERNAME": Basic authentication username`) ew.writeln() @@ -1408,10 +1405,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "HUAWEICLOUD_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "HUAWEICLOUD_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "HUAWEICLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "HUAWEICLOUD_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "HUAWEICLOUD_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "HUAWEICLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "HUAWEICLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "HUAWEICLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/huaweicloud`) @@ -1428,10 +1425,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "HURRICANE_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "HURRICANE_POLLING_INTERVAL": Time between DNS propagation checks`) - ew.writeln(` - "HURRICANE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation; defaults to 300s (5 minutes)`) - ew.writeln(` - "HURRICANE_SEQUENCE_INTERVAL": Time between sequential requests`) + ew.writeln(` - "HURRICANE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "HURRICANE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "HURRICANE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation (Default: 300)`) + ew.writeln(` - "HURRICANE_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/hurricane`) @@ -1445,11 +1442,12 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "HYPERONE_API_URL": Allows to pass custom API Endpoint to be used in the challenge (default https://api.hyperone.com/v2)`) + ew.writeln(` - "HYPERONE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "HYPERONE_LOCATION_ID": Specifies location (region) to be used in API calls. (default pl-waw-1)`) ew.writeln(` - "HYPERONE_PASSPORT_LOCATION": Allows to pass custom passport file location (default ~/.h1/passport.json)`) - ew.writeln(` - "HYPERONE_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "HYPERONE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "HYPERONE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "HYPERONE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 60)`) + ew.writeln(` - "HYPERONE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 2)`) + ew.writeln(` - "HYPERONE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/hyperone`) @@ -1467,10 +1465,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "SOFTLAYER_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "SOFTLAYER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "SOFTLAYER_TIMEOUT": API request timeout`) - ew.writeln(` - "SOFTLAYER_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "SOFTLAYER_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "SOFTLAYER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "SOFTLAYER_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "SOFTLAYER_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/ibmcloud`) @@ -1489,9 +1487,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "IIJ_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "IIJ_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "IIJ_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "IIJ_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 4)`) + ew.writeln(` - "IIJ_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 240)`) + ew.writeln(` - "IIJ_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/iij`) @@ -1510,9 +1508,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "IIJ_DPF_API_ENDPOINT": API endpoint URL, defaults to https://api.dns-platform.jp/dpf/v1`) - ew.writeln(` - "IIJ_DPF_POLLING_INTERVAL": Time between DNS propagation check, defaults to 5 second`) - ew.writeln(` - "IIJ_DPF_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation, defaults to 660 second`) - ew.writeln(` - "IIJ_DPF_TTL": The TTL of the TXT record used for the DNS challenge, default to 300`) + ew.writeln(` - "IIJ_DPF_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 5)`) + ew.writeln(` - "IIJ_DPF_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 660)`) + ew.writeln(` - "IIJ_DPF_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/iijdpf`) @@ -1531,14 +1529,14 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "INFOBLOX_DNS_VIEW": The view for the TXT records, default: External`) - ew.writeln(` - "INFOBLOX_HTTP_TIMEOUT": HTTP request timeout`) - ew.writeln(` - "INFOBLOX_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "INFOBLOX_PORT": The port for the infoblox grid manager, default: 443`) - ew.writeln(` - "INFOBLOX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "INFOBLOX_SSL_VERIFY": Whether or not to verify the TLS certificate, default: true`) - ew.writeln(` - "INFOBLOX_TTL": The TTL of the TXT record used for the DNS challenge`) - ew.writeln(` - "INFOBLOX_WAPI_VERSION": The version of WAPI being used, default: 2.11`) + ew.writeln(` - "INFOBLOX_DNS_VIEW": The view for the TXT records (Default: External)`) + ew.writeln(` - "INFOBLOX_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "INFOBLOX_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "INFOBLOX_PORT": The port for the infoblox grid manager (Default: 443)`) + ew.writeln(` - "INFOBLOX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "INFOBLOX_SSL_VERIFY": Whether or not to verify the TLS certificate (Default: true)`) + ew.writeln(` - "INFOBLOX_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + ew.writeln(` - "INFOBLOX_WAPI_VERSION": The version of WAPI being used (Default: 2.11)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/infoblox`) @@ -1556,10 +1554,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "INFOMANIAK_ENDPOINT": https://api.infomaniak.com`) - ew.writeln(` - "INFOMANIAK_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "INFOMANIAK_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "INFOMANIAK_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "INFOMANIAK_TTL": The TTL of the TXT record used for the DNS challenge in seconds`) + ew.writeln(` - "INFOMANIAK_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "INFOMANIAK_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "INFOMANIAK_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "INFOMANIAK_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/infomaniak`) @@ -1577,10 +1575,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "INTERNET_BS_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "INTERNET_BS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "INTERNET_BS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "INTERNET_BS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "INTERNET_BS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "INTERNET_BS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "INTERNET_BS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "INTERNET_BS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/internetbs`) @@ -1598,11 +1596,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "INWX_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "INWX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation (default 360s)`) + ew.writeln(` - "INWX_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "INWX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 360)`) ew.writeln(` - "INWX_SANDBOX": Activate the sandbox (boolean)`) ew.writeln(` - "INWX_SHARED_SECRET": shared secret related to 2FA`) - ew.writeln(` - "INWX_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "INWX_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/inwx`) @@ -1619,10 +1617,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "IONOS_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "IONOS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "IONOS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "IONOS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "IONOS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "IONOS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "IONOS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "IONOS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/ionos`) @@ -1639,10 +1637,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "IPV64_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "IPV64_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "IPV64_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "IPV64_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "IPV64_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "IPV64_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "IPV64_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/ipv64`) @@ -1660,10 +1657,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "IWANTMYNAME_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "IWANTMYNAME_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "IWANTMYNAME_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "IWANTMYNAME_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "IWANTMYNAME_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "IWANTMYNAME_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "IWANTMYNAME_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "IWANTMYNAME_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/iwantmyname`) @@ -1683,11 +1680,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "JOKER_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "JOKER_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "JOKER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "JOKER_SEQUENCE_INTERVAL": Time between sequential requests (only with 'SVC' mode)`) - ew.writeln(` - "JOKER_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "JOKER_HTTP_TIMEOUT": API request timeout in seconds (Default: 60)`) + ew.writeln(` - "JOKER_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "JOKER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "JOKER_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60), only with 'SVC' mode`) + ew.writeln(` - "JOKER_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/joker`) @@ -1704,10 +1701,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "LIARA_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "LIARA_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "LIARA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "LIARA_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "LIARA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "LIARA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "LIARA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "LIARA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/liara`) @@ -1727,8 +1724,8 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "AWS_SHARED_CREDENTIALS_FILE": Managed by the AWS client. Shared credentials file.`) - ew.writeln(` - "LIGHTSAIL_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "LIGHTSAIL_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "LIGHTSAIL_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "LIGHTSAIL_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/lightsail`) @@ -1745,11 +1742,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "LIMACITY_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "LIMACITY_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "LIMACITY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "LIMACITY_SEQUENCE_INTERVAL": Time between sequential requests`) - ew.writeln(` - "LIMACITY_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "LIMACITY_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "LIMACITY_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 80)`) + ew.writeln(` - "LIMACITY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 480)`) + ew.writeln(` - "LIMACITY_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 90)`) + ew.writeln(` - "LIMACITY_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/limacity`) @@ -1766,10 +1763,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "LINODE_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "LINODE_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "LINODE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "LINODE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "LINODE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "LINODE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 15)`) + ew.writeln(` - "LINODE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "LINODE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/linode`) @@ -1787,10 +1784,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "LWAPI_HTTP_TIMEOUT": Maximum waiting time for the DNS records to be created (not verified)`) - ew.writeln(` - "LWAPI_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "LWAPI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "LWAPI_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "LWAPI_HTTP_TIMEOUT": API request timeout in seconds (Default: 60)`) + ew.writeln(` - "LWAPI_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "LWAPI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "LWAPI_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln(` - "LWAPI_URL": Liquid Web API endpoint`) ew.writeln(` - "LWAPI_ZONE": DNS Zone`) @@ -1811,10 +1808,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "LOOPIA_API_URL": API endpoint. Ex: https://api.loopia.se/RPCSERV or https://api.loopia.rs/RPCSERV`) - ew.writeln(` - "LOOPIA_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "LOOPIA_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "LOOPIA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "LOOPIA_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "LOOPIA_HTTP_TIMEOUT": API request timeout in seconds (Default: 60)`) + ew.writeln(` - "LOOPIA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2400)`) + ew.writeln(` - "LOOPIA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "LOOPIA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/loopia`) @@ -1832,10 +1829,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "LUADNS_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "LUADNS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "LUADNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "LUADNS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "LUADNS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "LUADNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "LUADNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "LUADNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/luadns`) @@ -1854,8 +1851,8 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "MAILINABOX_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "MAILINABOX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "MAILINABOX_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 4)`) + ew.writeln(` - "MAILINABOX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/mailinabox`) @@ -1873,10 +1870,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "MANAGEENGINE_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "MANAGEENGINE_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "MANAGEENGINE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "MANAGEENGINE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "MANAGEENGINE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "MANAGEENGINE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "MANAGEENGINE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/manageengine`) @@ -1894,9 +1890,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "METANAME_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "METANAME_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "METANAME_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "METANAME_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "METANAME_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "METANAME_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/metaname`) @@ -1913,11 +1909,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "MIJNHOST_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "MIJNHOST_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "MIJNHOST_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "MIJNHOST_SEQUENCE_INTERVAL": Time between sequential requests`) - ew.writeln(` - "MIJNHOST_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "MIJNHOST_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "MIJNHOST_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "MIJNHOST_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "MIJNHOST_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) + ew.writeln(` - "MIJNHOST_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/mijnhost`) @@ -1934,11 +1930,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "MITTWALD_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "MITTWALD_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "MITTWALD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "MITTWALD_SEQUENCE_INTERVAL": Time between sequential requests`) - ew.writeln(` - "MITTWALD_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "MITTWALD_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "MITTWALD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "MITTWALD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "MITTWALD_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 120)`) + ew.writeln(` - "MITTWALD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/mittwald`) @@ -1956,10 +1952,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "MYDNSJP_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "MYDNSJP_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "MYDNSJP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "MYDNSJP_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "MYDNSJP_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "MYDNSJP_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "MYDNSJP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/mydnsjp`) @@ -1979,10 +1974,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "MYTHICBEASTS_API_ENDPOINT": The endpoint for the API (must implement v2)`) ew.writeln(` - "MYTHICBEASTS_AUTH_API_ENDPOINT": The endpoint for Mythic Beasts' Authentication`) - 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(` - "MYTHICBEASTS_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "MYTHICBEASTS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "MYTHICBEASTS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "MYTHICBEASTS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/mythicbeasts`) @@ -2000,11 +1995,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "NAMECHEAP_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "NAMECHEAP_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "NAMECHEAP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "NAMECHEAP_HTTP_TIMEOUT": API request timeout in seconds (Default: 60)`) + ew.writeln(` - "NAMECHEAP_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 15)`) + ew.writeln(` - "NAMECHEAP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 3600)`) ew.writeln(` - "NAMECHEAP_SANDBOX": Activate the sandbox (boolean)`) - ew.writeln(` - "NAMECHEAP_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "NAMECHEAP_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/namecheap`) @@ -2022,10 +2017,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "NAMECOM_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "NAMECOM_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "NAMECOM_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "NAMECOM_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "NAMECOM_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "NAMECOM_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 20)`) + ew.writeln(` - "NAMECOM_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 900)`) + ew.writeln(` - "NAMECOM_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/namedotcom`) @@ -2042,9 +2037,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "NAMESILO_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "NAMESILO_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation, it is better to set larger than 15m`) - ew.writeln(` - "NAMESILO_TTL": The TTL of the TXT record used for the DNS challenge, should be in [3600, 2592000]`) + ew.writeln(` - "NAMESILO_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "NAMESILO_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60), it is better to set larger than 15 minutes`) + ew.writeln(` - "NAMESILO_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600), should be in [3600, 2592000]`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/namesilo`) @@ -2062,11 +2057,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "NEARLYFREESPEECH_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "NEARLYFREESPEECH_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "NEARLYFREESPEECH_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "NEARLYFREESPEECH_SEQUENCE_INTERVAL": Time between sequential requests`) - ew.writeln(` - "NEARLYFREESPEECH_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "NEARLYFREESPEECH_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "NEARLYFREESPEECH_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "NEARLYFREESPEECH_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "NEARLYFREESPEECH_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) + ew.writeln(` - "NEARLYFREESPEECH_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/nearlyfreespeech`) @@ -2085,9 +2080,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "NETCUP_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "NETCUP_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "NETCUP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "NETCUP_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "NETCUP_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 30)`) + ew.writeln(` - "NETCUP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 900)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/netcup`) @@ -2104,10 +2099,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "NETLIFY_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "NETLIFY_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "NETLIFY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "NETLIFY_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "NETLIFY_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "NETLIFY_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "NETLIFY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "NETLIFY_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/netlify`) @@ -2129,10 +2124,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "NICMANAGER_API_MODE": mode: 'anycast' or 'zone' (default: 'anycast')`) ew.writeln(` - "NICMANAGER_API_OTP": TOTP Secret (optional)`) - ew.writeln(` - "NICMANAGER_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "NICMANAGER_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "NICMANAGER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "NICMANAGER_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "NICMANAGER_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "NICMANAGER_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "NICMANAGER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) + ew.writeln(` - "NICMANAGER_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 900)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/nicmanager`) @@ -2150,10 +2145,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "NIFCLOUD_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "NIFCLOUD_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "NIFCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "NIFCLOUD_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "NIFCLOUD_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "NIFCLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "NIFCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "NIFCLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/nifcloud`) @@ -2170,10 +2165,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "NJALLA_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "NJALLA_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "NJALLA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "NJALLA_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "NJALLA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "NJALLA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "NJALLA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "NJALLA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/njalla`) @@ -2190,10 +2185,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "NODION_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "NODION_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "NODION_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "NODION_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "NODION_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "NODION_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "NODION_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "NODION_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/nodion`) @@ -2210,10 +2205,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "NS1_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "NS1_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "NS1_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "NS1_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "NS1_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "NS1_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "NS1_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "NS1_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/ns1`) @@ -2236,9 +2231,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "OCI_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "OCI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "OCI_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "OCI_HTTP_TIMEOUT": API request timeout in seconds (Default: 60)`) + ew.writeln(` - "OCI_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "OCI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "OCI_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/oraclecloud`) @@ -2259,11 +2255,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "OTC_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "OTC_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "OTC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "OTC_SEQUENCE_INTERVAL": Time between sequential requests`) - ew.writeln(` - "OTC_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "OTC_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "OTC_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "OTC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "OTC_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) + ew.writeln(` - "OTC_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/otc`) @@ -2286,10 +2282,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "OVH_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "OVH_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "OVH_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "OVH_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "OVH_HTTP_TIMEOUT": API request timeout in seconds (Default: 180)`) + ew.writeln(` - "OVH_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "OVH_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "OVH_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/ovh`) @@ -2308,11 +2304,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "PDNS_API_VERSION": Skip API version autodetection and use the provided version number.`) - ew.writeln(` - "PDNS_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "PDNS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "PDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "PDNS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "PDNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "PDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln(` - "PDNS_SERVER_NAME": Name of the server in the URL, 'localhost' by default`) - ew.writeln(` - "PDNS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "PDNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/pdns`) @@ -2331,10 +2327,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "PLESK_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "PLESK_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "PLESK_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "PLESK_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "PLESK_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "PLESK_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "PLESK_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "PLESK_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/plesk`) @@ -2352,10 +2348,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "PORKBUN_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "PORKBUN_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "PORKBUN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "PORKBUN_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "PORKBUN_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "PORKBUN_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "PORKBUN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 600)`) + ew.writeln(` - "PORKBUN_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/porkbun`) @@ -2373,10 +2369,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "RACKSPACE_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "RACKSPACE_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "RACKSPACE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "RACKSPACE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "RACKSPACE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "RACKSPACE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 3)`) + ew.writeln(` - "RACKSPACE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "RACKSPACE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/rackspace`) @@ -2393,10 +2389,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "RAINYUN_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "RAINYUN_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "RAINYUN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "RAINYUN_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "RAINYUN_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "RAINYUN_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "RAINYUN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "RAINYUN_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/rainyun`) @@ -2413,10 +2409,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "RCODEZERO_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "RCODEZERO_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "RCODEZERO_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "RCODEZERO_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "RCODEZERO_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "RCODEZERO_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "RCODEZERO_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 240)`) + ew.writeln(` - "RCODEZERO_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/rcodezero`) @@ -2433,10 +2429,10 @@ func displayDNSHelp(w io.Writer, name string) error { 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(` - "REGFISH_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "REGFISH_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "REGFISH_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "REGFISH_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/regfish`) @@ -2454,12 +2450,12 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "REGRU_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "REGRU_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "REGRU_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "REGRU_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "REGRU_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "REGRU_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln(` - "REGRU_TLS_CERT": authentication certificate`) ew.writeln(` - "REGRU_TLS_KEY": authentication private key`) - ew.writeln(` - "REGRU_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "REGRU_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/regru`) @@ -2479,12 +2475,12 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "RFC2136_DNS_TIMEOUT": API request timeout`) - ew.writeln(` - "RFC2136_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "RFC2136_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "RFC2136_SEQUENCE_INTERVAL": Time between sequential requests`) + ew.writeln(` - "RFC2136_DNS_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "RFC2136_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "RFC2136_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "RFC2136_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) ew.writeln(` - "RFC2136_TSIG_FILE": Path to a key file generated by tsig-keygen`) - ew.writeln(` - "RFC2136_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "RFC2136_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/rfc2136`) @@ -2501,10 +2497,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "RIMUHOSTING_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "RIMUHOSTING_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "RIMUHOSTING_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "RIMUHOSTING_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "RIMUHOSTING_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "RIMUHOSTING_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "RIMUHOSTING_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "RIMUHOSTING_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/rimuhosting`) @@ -2530,10 +2526,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "AWS_MAX_RETRIES": The number of maximum returns the service will use to make an individual API request`) - ew.writeln(` - "AWS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "AWS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "AWS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 4)`) + ew.writeln(` - "AWS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln(` - "AWS_SHARED_CREDENTIALS_FILE": Managed by the AWS client. Shared credentials file.`) - ew.writeln(` - "AWS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "AWS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 10)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/route53`) @@ -2550,10 +2546,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "SAFEDNS_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "SAFEDNS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "SAFEDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "SAFEDNS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "SAFEDNS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "SAFEDNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "SAFEDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "SAFEDNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/safedns`) @@ -2571,10 +2567,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "SAKURACLOUD_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "SAKURACLOUD_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "SAKURACLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "SAKURACLOUD_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "SAKURACLOUD_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "SAKURACLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "SAKURACLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "SAKURACLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/sakuracloud`) @@ -2593,9 +2589,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "SCW_ACCESS_KEY": Access key`) - ew.writeln(` - "SCW_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "SCW_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "SCW_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "SCW_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "SCW_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "SCW_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/scaleway`) @@ -2613,10 +2609,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "SELECTEL_BASE_URL": API endpoint URL`) - ew.writeln(` - "SELECTEL_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "SELECTEL_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "SELECTEL_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "SELECTEL_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "SELECTEL_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "SELECTEL_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "SELECTEL_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "SELECTEL_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/selectel`) @@ -2637,10 +2633,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "SELECTELV2_BASE_URL": API endpoint URL`) - ew.writeln(` - "SELECTELV2_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "SELECTELV2_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "SELECTELV2_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "SELECTELV2_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "SELECTELV2_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "SELECTELV2_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 5)`) + ew.writeln(` - "SELECTELV2_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "SELECTELV2_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/selectelv2`) @@ -2659,10 +2655,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "SELFHOSTDE_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "SELFHOSTDE_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "SELFHOSTDE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "SELFHOSTDE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "SELFHOSTDE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "SELFHOSTDE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 30)`) + ew.writeln(` - "SELFHOSTDE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 240)`) + ew.writeln(` - "SELFHOSTDE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/selfhostde`) @@ -2680,10 +2676,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "SERVERCOW_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "SERVERCOW_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "SERVERCOW_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "SERVERCOW_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "SERVERCOW_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "SERVERCOW_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "SERVERCOW_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "SERVERCOW_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/servercow`) @@ -2701,10 +2697,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "SHELLRENT_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "SHELLRENT_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "SHELLRENT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "SHELLRENT_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "SHELLRENT_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "SHELLRENT_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "SHELLRENT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) + ew.writeln(` - "SHELLRENT_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/shellrent`) @@ -2722,10 +2718,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "SIMPLY_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "SIMPLY_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "SIMPLY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "SIMPLY_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "SIMPLY_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "SIMPLY_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "SIMPLY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) + ew.writeln(` - "SIMPLY_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/simply`) @@ -2743,11 +2739,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "SONIC_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "SONIC_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "SONIC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "SONIC_SEQUENCE_INTERVAL": Time between sequential requests`) - ew.writeln(` - "SONIC_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "SONIC_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "SONIC_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "SONIC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "SONIC_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) + ew.writeln(` - "SONIC_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/sonic`) @@ -2766,9 +2762,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "STACKPATH_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "STACKPATH_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "STACKPATH_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "STACKPATH_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "STACKPATH_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "STACKPATH_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/stackpath`) @@ -2786,10 +2782,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "TECHNITIUM_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "TECHNITIUM_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "TECHNITIUM_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "TECHNITIUM_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "TECHNITIUM_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "TECHNITIUM_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "TECHNITIUM_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "TECHNITIUM_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/technitium`) @@ -2807,12 +2803,12 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "TENCENTCLOUD_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "TENCENTCLOUD_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "TENCENTCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "TENCENTCLOUD_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "TENCENTCLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "TENCENTCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln(` - "TENCENTCLOUD_REGION": Region`) ew.writeln(` - "TENCENTCLOUD_SESSION_TOKEN": Access Key token`) - ew.writeln(` - "TENCENTCLOUD_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "TENCENTCLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/tencentcloud`) @@ -2829,9 +2825,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "TIMEWEBCLOUD_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "TIMEWEBCLOUD_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "TIMEWEBCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "TIMEWEBCLOUD_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "TIMEWEBCLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "TIMEWEBCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/timewebcloud`) @@ -2849,9 +2845,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "TRANSIP_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "TRANSIP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "TRANSIP_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "TRANSIP_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "TRANSIP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 600)`) + ew.writeln(` - "TRANSIP_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 10)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/transip`) @@ -2870,9 +2866,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "ULTRADNS_ENDPOINT": API endpoint URL, defaults to https://api.ultradns.com/`) - ew.writeln(` - "ULTRADNS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "ULTRADNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "ULTRADNS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "ULTRADNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 4)`) + ew.writeln(` - "ULTRADNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "ULTRADNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/ultradns`) @@ -2889,11 +2885,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "VARIOMEDIA_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "VARIOMEDIA_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "VARIOMEDIA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "VARIOMEDIA_SEQUENCE_INTERVAL": Time between sequential requests`) - ew.writeln(` - "VARIOMEDIA_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "VARIOMEDIA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "VARIOMEDIA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "VARIOMEDIA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "VARIOMEDIA_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) + ew.writeln(` - "VARIOMEDIA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/variomedia`) @@ -2912,9 +2908,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "VEGADNS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "VEGADNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "VEGADNS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "VEGADNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 60)`) + ew.writeln(` - "VEGADNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 720)`) + ew.writeln(` - "VEGADNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 10)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/vegadns`) @@ -2931,11 +2927,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "VERCEL_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "VERCEL_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "VERCEL_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "VERCEL_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "VERCEL_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 5)`) + ew.writeln(` - "VERCEL_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln(` - "VERCEL_TEAM_ID": Team ID (ex: team_xxxxxxxxxxxxxxxxxxxxxxxx)`) - ew.writeln(` - "VERCEL_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "VERCEL_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/vercel`) @@ -2954,11 +2950,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "VERSIO_ENDPOINT": The endpoint URL of the API Server`) - ew.writeln(` - "VERSIO_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "VERSIO_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "VERSIO_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "VERSIO_SEQUENCE_INTERVAL": Time between sequential requests, default 60s`) - ew.writeln(` - "VERSIO_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "VERSIO_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "VERSIO_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 5)`) + ew.writeln(` - "VERSIO_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "VERSIO_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) + ew.writeln(` - "VERSIO_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/versio`) @@ -2977,9 +2973,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "VINYLDNS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "VINYLDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "VINYLDNS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "VINYLDNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 4)`) + ew.writeln(` - "VINYLDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "VINYLDNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 30)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/vinyldns`) @@ -3001,9 +2997,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(` - "VK_CLOUD_DNS_ENDPOINT": URL of DNS API. Defaults to https://mcs.mail.ru/public-dns but can be changed for usage with private clouds`) ew.writeln(` - "VK_CLOUD_DOMAIN_NAME": Openstack users domain name. Defaults to 'users' but can be changed for usage with private clouds`) ew.writeln(` - "VK_CLOUD_IDENTITY_ENDPOINT": URL of OpenStack Auth API, Defaults to https://infra.mail.ru:35357/v3/ but can be changed for usage with private clouds`) - ew.writeln(` - "VK_CLOUD_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "VK_CLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "VK_CLOUD_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "VK_CLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "VK_CLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "VK_CLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/vkcloud`) @@ -3022,12 +3018,12 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "VOLC_HOST": API host`) - ew.writeln(` - "VOLC_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "VOLC_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "VOLC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) + ew.writeln(` - "VOLC_HTTP_TIMEOUT": API request timeout in seconds (Default: 15)`) + ew.writeln(` - "VOLC_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "VOLC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 240)`) ew.writeln(` - "VOLC_REGION": Region`) ew.writeln(` - "VOLC_SCHEME": API scheme`) - ew.writeln(` - "VOLC_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "VOLC_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/volcengine`) @@ -3045,10 +3041,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "VSCALE_BASE_URL": API endpoint URL`) - ew.writeln(` - "VSCALE_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "VSCALE_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "VSCALE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "VSCALE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "VSCALE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "VSCALE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "VSCALE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "VSCALE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/vscale`) @@ -3065,10 +3061,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "VULTR_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "VULTR_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "VULTR_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "VULTR_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "VULTR_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "VULTR_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "VULTR_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "VULTR_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/vultr`) @@ -3085,10 +3081,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "WEBNAMES_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "WEBNAMES_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "WEBNAMES_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "WEBNAMES_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "WEBNAMES_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "WEBNAMES_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "WEBNAMES_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/webnames`) @@ -3106,11 +3101,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "WEBSUPPORT_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "WEBSUPPORT_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "WEBSUPPORT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "WEBSUPPORT_SEQUENCE_INTERVAL": Time between sequential requests`) - ew.writeln(` - "WEBSUPPORT_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "WEBSUPPORT_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "WEBSUPPORT_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "WEBSUPPORT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "WEBSUPPORT_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) + ew.writeln(` - "WEBSUPPORT_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/websupport`) @@ -3128,10 +3123,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "WEDOS_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "WEDOS_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "WEDOS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "WEDOS_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "WEDOS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "WEDOS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "WEDOS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 600)`) + ew.writeln(` - "WEDOS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/wedos`) @@ -3149,10 +3144,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "WESTCN_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "WESTCN_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "WESTCN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "WESTCN_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "WESTCN_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "WESTCN_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "WESTCN_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "WESTCN_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/westcn`) @@ -3169,10 +3164,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "YANDEX_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "YANDEX_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "YANDEX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "YANDEX_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "YANDEX_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "YANDEX_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "YANDEX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "YANDEX_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 21600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/yandex`) @@ -3190,10 +3185,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "YANDEX360_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "YANDEX360_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "YANDEX360_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "YANDEX360_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "YANDEX360_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "YANDEX360_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "YANDEX360_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "YANDEX360_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 21600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/yandex360`) @@ -3211,9 +3206,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "YANDEX_CLOUD_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "YANDEX_CLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "YANDEX_CLOUD_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "YANDEX_CLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "YANDEX_CLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "YANDEX_CLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/yandexcloud`) @@ -3232,10 +3227,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "ZONEEE_ENDPOINT": API endpoint URL`) - ew.writeln(` - "ZONEEE_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "ZONEEE_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "ZONEEE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "ZONEEE_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "ZONEEE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "ZONEEE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 5)`) + ew.writeln(` - "ZONEEE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/zoneee`) @@ -3252,10 +3246,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "ZONOMI_HTTP_TIMEOUT": API request timeout`) - ew.writeln(` - "ZONOMI_POLLING_INTERVAL": Time between DNS propagation check`) - ew.writeln(` - "ZONOMI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation`) - ew.writeln(` - "ZONOMI_TTL": The TTL of the TXT record used for the DNS challenge`) + ew.writeln(` - "ZONOMI_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "ZONOMI_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "ZONOMI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "ZONOMI_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/zonomi`) diff --git a/docs/content/dns/zz_gen_alidns.md b/docs/content/dns/zz_gen_alidns.md index d822ecea6..bb55ba4fc 100644 --- a/docs/content/dns/zz_gen_alidns.md +++ b/docs/content/dns/zz_gen_alidns.md @@ -57,10 +57,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `ALICLOUD_HTTP_TIMEOUT` | API request timeout | -| `ALICLOUD_POLLING_INTERVAL` | Time between DNS propagation check | -| `ALICLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `ALICLOUD_TTL` | The TTL of the TXT record used for the DNS challenge | +| `ALICLOUD_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `ALICLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `ALICLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `ALICLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_allinkl.md b/docs/content/dns/zz_gen_allinkl.md index 08e354f87..2415c812f 100644 --- a/docs/content/dns/zz_gen_allinkl.md +++ b/docs/content/dns/zz_gen_allinkl.md @@ -49,9 +49,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `ALL_INKL_HTTP_TIMEOUT` | API request timeout | -| `ALL_INKL_POLLING_INTERVAL` | Time between DNS propagation check | -| `ALL_INKL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `ALL_INKL_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `ALL_INKL_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `ALL_INKL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_arvancloud.md b/docs/content/dns/zz_gen_arvancloud.md index ff03f22e1..b9fa1af8d 100644 --- a/docs/content/dns/zz_gen_arvancloud.md +++ b/docs/content/dns/zz_gen_arvancloud.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `ARVANCLOUD_HTTP_TIMEOUT` | API request timeout | -| `ARVANCLOUD_POLLING_INTERVAL` | Time between DNS propagation check | -| `ARVANCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `ARVANCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge | +| `ARVANCLOUD_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `ARVANCLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `ARVANCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `ARVANCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_auroradns.md b/docs/content/dns/zz_gen_auroradns.md index d3fa5a1df..9fffe34bc 100644 --- a/docs/content/dns/zz_gen_auroradns.md +++ b/docs/content/dns/zz_gen_auroradns.md @@ -50,9 +50,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `AURORA_ENDPOINT` | API endpoint URL | -| `AURORA_POLLING_INTERVAL` | Time between DNS propagation check | -| `AURORA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `AURORA_TTL` | The TTL of the TXT record used for the DNS challenge | +| `AURORA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `AURORA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `AURORA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_autodns.md b/docs/content/dns/zz_gen_autodns.md index 584f21770..73f41b980 100644 --- a/docs/content/dns/zz_gen_autodns.md +++ b/docs/content/dns/zz_gen_autodns.md @@ -51,10 +51,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). |--------------------------------|-------------| | `AUTODNS_CONTEXT` | API context (4 for production, 1 for testing. Defaults to 4) | | `AUTODNS_ENDPOINT` | API endpoint URL, defaults to https://api.autodns.com/v1/ | -| `AUTODNS_HTTP_TIMEOUT` | API request timeout, defaults to 30 seconds | -| `AUTODNS_POLLING_INTERVAL` | Time between DNS propagation check | -| `AUTODNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `AUTODNS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `AUTODNS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `AUTODNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `AUTODNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `AUTODNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_azure.md b/docs/content/dns/zz_gen_azure.md index e1ecd9506..5063c202f 100644 --- a/docs/content/dns/zz_gen_azure.md +++ b/docs/content/dns/zz_gen_azure.md @@ -51,10 +51,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `AZURE_METADATA_ENDPOINT` | Metadata Service endpoint URL | -| `AZURE_POLLING_INTERVAL` | Time between DNS propagation check | +| `AZURE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `AZURE_PRIVATE_ZONE` | Set to true to use Azure Private DNS Zones and not public | -| `AZURE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `AZURE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `AZURE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `AZURE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | | `AZURE_ZONE_NAME` | Zone name to use inside Azure DNS service to add the TXT record in | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/docs/content/dns/zz_gen_azuredns.md b/docs/content/dns/zz_gen_azuredns.md index 4b762e675..f9e7d3844 100644 --- a/docs/content/dns/zz_gen_azuredns.md +++ b/docs/content/dns/zz_gen_azuredns.md @@ -83,13 +83,13 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | `AZURE_AUTH_METHOD` | Specify which authentication method to use | | `AZURE_AUTH_MSI_TIMEOUT` | Managed Identity timeout duration | | `AZURE_ENVIRONMENT` | Azure environment, one of: public, usgovernment, and china | -| `AZURE_POLLING_INTERVAL` | Time between DNS propagation check | +| `AZURE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `AZURE_PRIVATE_ZONE` | Set to true to use Azure Private DNS Zones and not public | -| `AZURE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `AZURE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | | `AZURE_RESOURCE_GROUP` | DNS zone resource group | | `AZURE_SERVICEDISCOVERY_FILTER` | Advanced ServiceDiscovery filter using Kusto query condition | | `AZURE_SUBSCRIPTION_ID` | DNS zone subscription ID | -| `AZURE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `AZURE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | | `AZURE_ZONE_NAME` | Zone name to use inside Azure DNS service to add the TXT record in | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/docs/content/dns/zz_gen_bindman.md b/docs/content/dns/zz_gen_bindman.md index c74273a7f..e12f25b7a 100644 --- a/docs/content/dns/zz_gen_bindman.md +++ b/docs/content/dns/zz_gen_bindman.md @@ -47,9 +47,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `BINDMAN_HTTP_TIMEOUT` | API request timeout | -| `BINDMAN_POLLING_INTERVAL` | Time between DNS propagation check | -| `BINDMAN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `BINDMAN_HTTP_TIMEOUT` | API request timeout in seconds (Default: 60) | +| `BINDMAN_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `BINDMAN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_bluecat.md b/docs/content/dns/zz_gen_bluecat.md index 3b0ebf898..ee45c7f8b 100644 --- a/docs/content/dns/zz_gen_bluecat.md +++ b/docs/content/dns/zz_gen_bluecat.md @@ -56,11 +56,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `BLUECAT_HTTP_TIMEOUT` | API request timeout | -| `BLUECAT_POLLING_INTERVAL` | Time between DNS propagation check | -| `BLUECAT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `BLUECAT_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `BLUECAT_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `BLUECAT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | | `BLUECAT_SKIP_DEPLOY` | Skip deployements | -| `BLUECAT_TTL` | The TTL of the TXT record used for the DNS challenge | +| `BLUECAT_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_brandit.md b/docs/content/dns/zz_gen_brandit.md index c2264f71c..5d1f91214 100644 --- a/docs/content/dns/zz_gen_brandit.md +++ b/docs/content/dns/zz_gen_brandit.md @@ -52,10 +52,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `BRANDIT_HTTP_TIMEOUT` | API request timeout | -| `BRANDIT_POLLING_INTERVAL` | Time between DNS propagation check | -| `BRANDIT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `BRANDIT_TTL` | The TTL of the TXT record used for the DNS challenge | +| `BRANDIT_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `BRANDIT_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `BRANDIT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 600) | +| `BRANDIT_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_bunny.md b/docs/content/dns/zz_gen_bunny.md index f945b9153..7b4db2020 100644 --- a/docs/content/dns/zz_gen_bunny.md +++ b/docs/content/dns/zz_gen_bunny.md @@ -47,9 +47,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `BUNNY_POLLING_INTERVAL` | Time between DNS propagation check | -| `BUNNY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `BUNNY_TTL` | The TTL of the TXT record used for the DNS challenge | +| `BUNNY_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `BUNNY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `BUNNY_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_checkdomain.md b/docs/content/dns/zz_gen_checkdomain.md index 694b8cc67..516d87880 100644 --- a/docs/content/dns/zz_gen_checkdomain.md +++ b/docs/content/dns/zz_gen_checkdomain.md @@ -48,10 +48,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `CHECKDOMAIN_ENDPOINT` | API endpoint URL, defaults to https://api.checkdomain.de | -| `CHECKDOMAIN_HTTP_TIMEOUT` | API request timeout, defaults to 30 seconds | -| `CHECKDOMAIN_POLLING_INTERVAL` | Time between DNS propagation check | -| `CHECKDOMAIN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `CHECKDOMAIN_TTL` | The TTL of the TXT record used for the DNS challenge | +| `CHECKDOMAIN_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `CHECKDOMAIN_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 300) | +| `CHECKDOMAIN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 7) | +| `CHECKDOMAIN_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_civo.md b/docs/content/dns/zz_gen_civo.md index 73f04140d..a2cffe27c 100644 --- a/docs/content/dns/zz_gen_civo.md +++ b/docs/content/dns/zz_gen_civo.md @@ -47,9 +47,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `CIVO_POLLING_INTERVAL` | Time between DNS propagation check | -| `CIVO_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `CIVO_TTL` | The TTL of the TXT record used for the DNS challenge | +| `CIVO_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 30) | +| `CIVO_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | +| `CIVO_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_clouddns.md b/docs/content/dns/zz_gen_clouddns.md index 4754cebca..27a254873 100644 --- a/docs/content/dns/zz_gen_clouddns.md +++ b/docs/content/dns/zz_gen_clouddns.md @@ -51,10 +51,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `CLOUDDNS_HTTP_TIMEOUT` | API request timeout | -| `CLOUDDNS_POLLING_INTERVAL` | Time between DNS propagation check | -| `CLOUDDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `CLOUDDNS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `CLOUDDNS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `CLOUDDNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 5) | +| `CLOUDDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `CLOUDDNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_cloudflare.md b/docs/content/dns/zz_gen_cloudflare.md index 55fbaeae3..68317c632 100644 --- a/docs/content/dns/zz_gen_cloudflare.md +++ b/docs/content/dns/zz_gen_cloudflare.md @@ -60,10 +60,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `CLOUDFLARE_HTTP_TIMEOUT` | API request timeout (in seconds) | -| `CLOUDFLARE_POLLING_INTERVAL` | Time between DNS propagation check (in seconds) | -| `CLOUDFLARE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation (in seconds) | -| `CLOUDFLARE_TTL` | The TTL of the TXT record used for the DNS challenge (in seconds) | +| `CLOUDFLARE_HTTP_TIMEOUT` | API request timeout in seconds (Default: ) | +| `CLOUDFLARE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `CLOUDFLARE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `CLOUDFLARE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_cloudns.md b/docs/content/dns/zz_gen_cloudns.md index f063d835f..01d4b7815 100644 --- a/docs/content/dns/zz_gen_cloudns.md +++ b/docs/content/dns/zz_gen_cloudns.md @@ -49,11 +49,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `CLOUDNS_HTTP_TIMEOUT` | API request timeout | -| `CLOUDNS_POLLING_INTERVAL` | Time between DNS propagation check | -| `CLOUDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `CLOUDNS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `CLOUDNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `CLOUDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 180) | | `CLOUDNS_SUB_AUTH_ID` | The API sub user ID | -| `CLOUDNS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `CLOUDNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_cloudru.md b/docs/content/dns/zz_gen_cloudru.md index b4cb9dcac..52190b031 100644 --- a/docs/content/dns/zz_gen_cloudru.md +++ b/docs/content/dns/zz_gen_cloudru.md @@ -51,11 +51,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `CLOUDRU_HTTP_TIMEOUT` | API request timeout | -| `CLOUDRU_POLLING_INTERVAL` | Time between DNS propagation check | -| `CLOUDRU_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `CLOUDRU_SEQUENCE_INTERVAL` | Time between sequential requests | -| `CLOUDRU_TTL` | The TTL of the TXT record used for the DNS challenge | +| `CLOUDRU_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `CLOUDRU_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 5) | +| `CLOUDRU_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | +| `CLOUDRU_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 120) | +| `CLOUDRU_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_cloudxns.md b/docs/content/dns/zz_gen_cloudxns.md index c63a773e1..0b290b693 100644 --- a/docs/content/dns/zz_gen_cloudxns.md +++ b/docs/content/dns/zz_gen_cloudxns.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `CLOUDXNS_HTTP_TIMEOUT` | API request timeout | -| `CLOUDXNS_POLLING_INTERVAL` | Time between DNS propagation check | -| `CLOUDXNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `CLOUDXNS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `CLOUDXNS_HTTP_TIMEOUT` | API request timeout in seconds (Default: ) | +| `CLOUDXNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: ) | +| `CLOUDXNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: ) | +| `CLOUDXNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: ) | 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" %}}). diff --git a/docs/content/dns/zz_gen_conoha.md b/docs/content/dns/zz_gen_conoha.md index c5de0d20e..bf5c0582d 100644 --- a/docs/content/dns/zz_gen_conoha.md +++ b/docs/content/dns/zz_gen_conoha.md @@ -51,11 +51,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `CONOHA_HTTP_TIMEOUT` | API request timeout | -| `CONOHA_POLLING_INTERVAL` | Time between DNS propagation check | -| `CONOHA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `CONOHA_REGION` | The region | -| `CONOHA_TTL` | The TTL of the TXT record used for the DNS challenge | +| `CONOHA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `CONOHA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `CONOHA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `CONOHA_REGION` | The region (Default: tyo1) | +| `CONOHA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_constellix.md b/docs/content/dns/zz_gen_constellix.md index 69040353d..23628e001 100644 --- a/docs/content/dns/zz_gen_constellix.md +++ b/docs/content/dns/zz_gen_constellix.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `CONSTELLIX_HTTP_TIMEOUT` | API request timeout | -| `CONSTELLIX_POLLING_INTERVAL` | Time between DNS propagation check | -| `CONSTELLIX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `CONSTELLIX_TTL` | The TTL of the TXT record used for the DNS challenge | +| `CONSTELLIX_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `CONSTELLIX_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `CONSTELLIX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `CONSTELLIX_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_corenetworks.md b/docs/content/dns/zz_gen_corenetworks.md index 0b61bbc77..dc756647e 100644 --- a/docs/content/dns/zz_gen_corenetworks.md +++ b/docs/content/dns/zz_gen_corenetworks.md @@ -49,11 +49,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `CORENETWORKS_HTTP_TIMEOUT` | API request timeout | -| `CORENETWORKS_POLLING_INTERVAL` | Time between DNS propagation check | -| `CORENETWORKS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `CORENETWORKS_SEQUENCE_INTERVAL` | Time between sequential requests | -| `CORENETWORKS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `CORENETWORKS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `CORENETWORKS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `CORENETWORKS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `CORENETWORKS_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | +| `CORENETWORKS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_cpanel.md b/docs/content/dns/zz_gen_cpanel.md index 9e939ca59..2cbc5030c 100644 --- a/docs/content/dns/zz_gen_cpanel.md +++ b/docs/content/dns/zz_gen_cpanel.md @@ -61,12 +61,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `CPANEL_HTTP_TIMEOUT` | API request timeout | +| `CPANEL_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `CPANEL_MODE` | use cpanel API or WHM API (Default: cpanel) | -| `CPANEL_POLLING_INTERVAL` | Time between DNS propagation check | -| `CPANEL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `CPANEL_REGION` | The region | -| `CPANEL_TTL` | The TTL of the TXT record used for the DNS challenge | +| `CPANEL_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `CPANEL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `CPANEL_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_derak.md b/docs/content/dns/zz_gen_derak.md index a5daf76db..fedbf4683 100644 --- a/docs/content/dns/zz_gen_derak.md +++ b/docs/content/dns/zz_gen_derak.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `DERAK_HTTP_TIMEOUT` | API request timeout | -| `DERAK_POLLING_INTERVAL` | Time between DNS propagation check | -| `DERAK_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `DERAK_TTL` | The TTL of the TXT record used for the DNS challenge | +| `DERAK_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `DERAK_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 5) | +| `DERAK_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `DERAK_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | | `DERAK_WEBSITE_ID` | Force the zone/website ID | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/docs/content/dns/zz_gen_desec.md b/docs/content/dns/zz_gen_desec.md index 45e5fabc6..977a00e06 100644 --- a/docs/content/dns/zz_gen_desec.md +++ b/docs/content/dns/zz_gen_desec.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `DESEC_HTTP_TIMEOUT` | API request timeout | -| `DESEC_POLLING_INTERVAL` | Time between DNS propagation check | -| `DESEC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `DESEC_TTL` | The TTL of the TXT record used for the DNS challenge | +| `DESEC_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `DESEC_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 4) | +| `DESEC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `DESEC_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_designate.md b/docs/content/dns/zz_gen_designate.md index cbbdfa557..74cd04920 100644 --- a/docs/content/dns/zz_gen_designate.md +++ b/docs/content/dns/zz_gen_designate.md @@ -74,9 +74,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `DESIGNATE_POLLING_INTERVAL` | Time between DNS propagation check | -| `DESIGNATE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `DESIGNATE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `DESIGNATE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `DESIGNATE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 600) | +| `DESIGNATE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 10) | | `DESIGNATE_ZONE_NAME` | The zone name to use in the OpenStack Project to manage TXT records. | | `OS_PROJECT_ID` | Project ID | | `OS_TENANT_NAME` | Tenant name (deprecated see OS_PROJECT_NAME and OS_PROJECT_ID) | diff --git a/docs/content/dns/zz_gen_digitalocean.md b/docs/content/dns/zz_gen_digitalocean.md index 3bf57f59d..24307cfb0 100644 --- a/docs/content/dns/zz_gen_digitalocean.md +++ b/docs/content/dns/zz_gen_digitalocean.md @@ -48,10 +48,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `DO_API_URL` | The URL of the API | -| `DO_HTTP_TIMEOUT` | API request timeout | -| `DO_POLLING_INTERVAL` | Time between DNS propagation check | -| `DO_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `DO_TTL` | The TTL of the TXT record used for the DNS challenge | +| `DO_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `DO_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 5) | +| `DO_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `DO_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 30) | 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" %}}). diff --git a/docs/content/dns/zz_gen_directadmin.md b/docs/content/dns/zz_gen_directadmin.md index 252c69ccf..006cb87d6 100644 --- a/docs/content/dns/zz_gen_directadmin.md +++ b/docs/content/dns/zz_gen_directadmin.md @@ -51,10 +51,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `DIRECTADMIN_HTTP_TIMEOUT` | API request timeout | -| `DIRECTADMIN_POLLING_INTERVAL` | Time between DNS propagation check | -| `DIRECTADMIN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `DIRECTADMIN_TTL` | The TTL of the TXT record used for the DNS challenge | +| `DIRECTADMIN_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `DIRECTADMIN_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 5) | +| `DIRECTADMIN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `DIRECTADMIN_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 30) | | `DIRECTADMIN_ZONE_NAME` | Zone name used to add the TXT record | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/docs/content/dns/zz_gen_dnshomede.md b/docs/content/dns/zz_gen_dnshomede.md index 56825f38d..b865578e6 100644 --- a/docs/content/dns/zz_gen_dnshomede.md +++ b/docs/content/dns/zz_gen_dnshomede.md @@ -50,10 +50,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `DNSHOMEDE_HTTP_TIMEOUT` | API request timeout | -| `DNSHOMEDE_POLLING_INTERVAL` | Time between DNS propagation checks | -| `DNSHOMEDE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation; defaults to 300s (5 minutes) | -| `DNSHOMEDE_SEQUENCE_INTERVAL` | Time between sequential requests | +| `DNSHOMEDE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `DNSHOMEDE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 1200) | +| `DNSHOMEDE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 2) | +| `DNSHOMEDE_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_dnsimple.md b/docs/content/dns/zz_gen_dnsimple.md index 188d7c895..d73122273 100644 --- a/docs/content/dns/zz_gen_dnsimple.md +++ b/docs/content/dns/zz_gen_dnsimple.md @@ -48,9 +48,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `DNSIMPLE_BASE_URL` | API endpoint URL | -| `DNSIMPLE_POLLING_INTERVAL` | Time between DNS propagation check | -| `DNSIMPLE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `DNSIMPLE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `DNSIMPLE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `DNSIMPLE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `DNSIMPLE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_dnsmadeeasy.md b/docs/content/dns/zz_gen_dnsmadeeasy.md index d6f1cb56b..572676fbd 100644 --- a/docs/content/dns/zz_gen_dnsmadeeasy.md +++ b/docs/content/dns/zz_gen_dnsmadeeasy.md @@ -49,11 +49,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `DNSMADEEASY_HTTP_TIMEOUT` | API request timeout | -| `DNSMADEEASY_POLLING_INTERVAL` | Time between DNS propagation check | -| `DNSMADEEASY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `DNSMADEEASY_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `DNSMADEEASY_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `DNSMADEEASY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | | `DNSMADEEASY_SANDBOX` | Activate the sandbox (boolean) | -| `DNSMADEEASY_TTL` | The TTL of the TXT record used for the DNS challenge | +| `DNSMADEEASY_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_dnspod.md b/docs/content/dns/zz_gen_dnspod.md index 2a654d640..b9e906052 100644 --- a/docs/content/dns/zz_gen_dnspod.md +++ b/docs/content/dns/zz_gen_dnspod.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `DNSPOD_HTTP_TIMEOUT` | API request timeout | -| `DNSPOD_POLLING_INTERVAL` | Time between DNS propagation check | -| `DNSPOD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `DNSPOD_TTL` | The TTL of the TXT record used for the DNS challenge | +| `DNSPOD_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `DNSPOD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `DNSPOD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `DNSPOD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_dode.md b/docs/content/dns/zz_gen_dode.md index b73fa70df..240bd5276 100644 --- a/docs/content/dns/zz_gen_dode.md +++ b/docs/content/dns/zz_gen_dode.md @@ -47,11 +47,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `DODE_HTTP_TIMEOUT` | API request timeout | -| `DODE_POLLING_INTERVAL` | Time between DNS propagation check | -| `DODE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `DODE_SEQUENCE_INTERVAL` | Time between sequential requests | -| `DODE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `DODE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `DODE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `DODE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `DODE_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | +| `DODE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_domeneshop.md b/docs/content/dns/zz_gen_domeneshop.md index 24a19a056..a519cfbef 100644 --- a/docs/content/dns/zz_gen_domeneshop.md +++ b/docs/content/dns/zz_gen_domeneshop.md @@ -49,9 +49,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `DOMENESHOP_HTTP_TIMEOUT` | API request timeout | -| `DOMENESHOP_POLLING_INTERVAL` | Time between DNS propagation check | -| `DOMENESHOP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `DOMENESHOP_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `DOMENESHOP_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 20) | +| `DOMENESHOP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_dreamhost.md b/docs/content/dns/zz_gen_dreamhost.md index 9d9663971..e713b8ad2 100644 --- a/docs/content/dns/zz_gen_dreamhost.md +++ b/docs/content/dns/zz_gen_dreamhost.md @@ -47,10 +47,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `DREAMHOST_HTTP_TIMEOUT` | API request timeout | -| `DREAMHOST_POLLING_INTERVAL` | Time between DNS propagation check | -| `DREAMHOST_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `DREAMHOST_TTL` | The TTL of the TXT record used for the DNS challenge | +| `DREAMHOST_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `DREAMHOST_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 60) | +| `DREAMHOST_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 3600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_duckdns.md b/docs/content/dns/zz_gen_duckdns.md index 515097c77..8082075ee 100644 --- a/docs/content/dns/zz_gen_duckdns.md +++ b/docs/content/dns/zz_gen_duckdns.md @@ -47,11 +47,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `DUCKDNS_HTTP_TIMEOUT` | API request timeout | -| `DUCKDNS_POLLING_INTERVAL` | Time between DNS propagation check | -| `DUCKDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `DUCKDNS_SEQUENCE_INTERVAL` | Time between sequential requests | -| `DUCKDNS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `DUCKDNS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `DUCKDNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `DUCKDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `DUCKDNS_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | +| `DUCKDNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_dyn.md b/docs/content/dns/zz_gen_dyn.md index 32f902394..f241ea930 100644 --- a/docs/content/dns/zz_gen_dyn.md +++ b/docs/content/dns/zz_gen_dyn.md @@ -51,10 +51,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `DYN_HTTP_TIMEOUT` | API request timeout | -| `DYN_POLLING_INTERVAL` | Time between DNS propagation check | -| `DYN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `DYN_TTL` | The TTL of the TXT record used for the DNS challenge | +| `DYN_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `DYN_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `DYN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `DYN_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_dynu.md b/docs/content/dns/zz_gen_dynu.md index d59fa23f5..4db76456f 100644 --- a/docs/content/dns/zz_gen_dynu.md +++ b/docs/content/dns/zz_gen_dynu.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `DYNU_HTTP_TIMEOUT` | API request timeout | -| `DYNU_POLLING_INTERVAL` | Time between DNS propagation check | -| `DYNU_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `DYNU_TTL` | The TTL of the TXT record used for the DNS challenge | +| `DYNU_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `DYNU_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `DYNU_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 180) | +| `DYNU_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_easydns.md b/docs/content/dns/zz_gen_easydns.md index f4c44164c..196e6ab7c 100644 --- a/docs/content/dns/zz_gen_easydns.md +++ b/docs/content/dns/zz_gen_easydns.md @@ -50,11 +50,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `EASYDNS_ENDPOINT` | The endpoint URL of the API Server | -| `EASYDNS_HTTP_TIMEOUT` | API request timeout | -| `EASYDNS_POLLING_INTERVAL` | Time between DNS propagation check | -| `EASYDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `EASYDNS_SEQUENCE_INTERVAL` | Time between sequential requests | -| `EASYDNS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `EASYDNS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `EASYDNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `EASYDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `EASYDNS_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | +| `EASYDNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_edgedns.md b/docs/content/dns/zz_gen_edgedns.md index 3ba5fffea..eb94a8a47 100644 --- a/docs/content/dns/zz_gen_edgedns.md +++ b/docs/content/dns/zz_gen_edgedns.md @@ -55,9 +55,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `AKAMAI_POLLING_INTERVAL` | Time between DNS propagation check. Default: 15 seconds | -| `AKAMAI_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation. Default: 3 minutes | -| `AKAMAI_TTL` | The TTL of the TXT record used for the DNS challenge | +| `AKAMAI_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 15) | +| `AKAMAI_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 180) | +| `AKAMAI_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_efficientip.md b/docs/content/dns/zz_gen_efficientip.md index cfdfb9bba..7c151e67a 100644 --- a/docs/content/dns/zz_gen_efficientip.md +++ b/docs/content/dns/zz_gen_efficientip.md @@ -53,11 +53,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `EFFICIENTIP_HTTP_TIMEOUT` | API request timeout | +| `EFFICIENTIP_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | | `EFFICIENTIP_INSECURE_SKIP_VERIFY` | Whether or not to verify EfficientIP API certificate | -| `EFFICIENTIP_POLLING_INTERVAL` | Time between DNS propagation check | -| `EFFICIENTIP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `EFFICIENTIP_TTL` | The TTL of the TXT record used for the DNS challenge | +| `EFFICIENTIP_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `EFFICIENTIP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | | `EFFICIENTIP_VIEW_NAME` | View name (ex: external) | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/docs/content/dns/zz_gen_epik.md b/docs/content/dns/zz_gen_epik.md index 861efb640..50f66e8da 100644 --- a/docs/content/dns/zz_gen_epik.md +++ b/docs/content/dns/zz_gen_epik.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `EPIK_HTTP_TIMEOUT` | API request timeout | -| `EPIK_POLLING_INTERVAL` | Time between DNS propagation check | -| `EPIK_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `EPIK_TTL` | The TTL of the TXT record used for the DNS challenge | +| `EPIK_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `EPIK_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `EPIK_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `EPIK_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_exec.md b/docs/content/dns/zz_gen_exec.md index f2f5f9619..fb2b17e3d 100644 --- a/docs/content/dns/zz_gen_exec.md +++ b/docs/content/dns/zz_gen_exec.md @@ -43,11 +43,11 @@ lego --email you@example.com --dns exec -d '*.example.com' -d example.com run ## Additional Configuration -| Environment Variable Name | Description | -|----------------------------|-------------------------------------------| -| `EXEC_POLLING_INTERVAL` | Time between DNS propagation check. | -| `EXEC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation. | -| `EXEC_SEQUENCE_INTERVAL` | Time between sequential requests. | +| Environment Variable Name | Description | +|----------------------------|--------------------------------------------------------------------| +| `EXEC_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 3). | +| `EXEC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60). | +| `EXEC_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60). | ## Description diff --git a/docs/content/dns/zz_gen_exoscale.md b/docs/content/dns/zz_gen_exoscale.md index ffd3da1e4..5392ff573 100644 --- a/docs/content/dns/zz_gen_exoscale.md +++ b/docs/content/dns/zz_gen_exoscale.md @@ -50,10 +50,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `EXOSCALE_ENDPOINT` | API endpoint URL | -| `EXOSCALE_HTTP_TIMEOUT` | API request timeout | -| `EXOSCALE_POLLING_INTERVAL` | Time between DNS propagation check | -| `EXOSCALE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `EXOSCALE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `EXOSCALE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 60) | +| `EXOSCALE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `EXOSCALE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `EXOSCALE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_freemyip.md b/docs/content/dns/zz_gen_freemyip.md index 421361205..d89e17c27 100644 --- a/docs/content/dns/zz_gen_freemyip.md +++ b/docs/content/dns/zz_gen_freemyip.md @@ -47,11 +47,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `FREEMYIP_HTTP_TIMEOUT` | API request timeout | -| `FREEMYIP_POLLING_INTERVAL` | Time between DNS propagation check | -| `FREEMYIP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `FREEMYIP_SEQUENCE_INTERVAL` | Time between sequential requests | -| `FREEMYIP_TTL` | The TTL of the TXT record used for the DNS challenge | +| `FREEMYIP_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `FREEMYIP_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `FREEMYIP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `FREEMYIP_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | +| `FREEMYIP_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_gandi.md b/docs/content/dns/zz_gen_gandi.md index fa7ae6fe0..961ed6873 100644 --- a/docs/content/dns/zz_gen_gandi.md +++ b/docs/content/dns/zz_gen_gandi.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `GANDI_HTTP_TIMEOUT` | API request timeout | -| `GANDI_POLLING_INTERVAL` | Time between DNS propagation check | -| `GANDI_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `GANDI_TTL` | The TTL of the TXT record used for the DNS challenge | +| `GANDI_HTTP_TIMEOUT` | API request timeout in seconds (Default: 60) | +| `GANDI_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 60) | +| `GANDI_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 2400) | +| `GANDI_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_gandiv5.md b/docs/content/dns/zz_gen_gandiv5.md index c3f0e2d20..773bd3b08 100644 --- a/docs/content/dns/zz_gen_gandiv5.md +++ b/docs/content/dns/zz_gen_gandiv5.md @@ -48,10 +48,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `GANDIV5_HTTP_TIMEOUT` | API request timeout | -| `GANDIV5_POLLING_INTERVAL` | Time between DNS propagation check | -| `GANDIV5_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `GANDIV5_TTL` | The TTL of the TXT record used for the DNS challenge | +| `GANDIV5_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `GANDIV5_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 20) | +| `GANDIV5_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 1200) | +| `GANDIV5_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_gcloud.md b/docs/content/dns/zz_gen_gcloud.md index 556bffe3d..ec830956e 100644 --- a/docs/content/dns/zz_gen_gcloud.md +++ b/docs/content/dns/zz_gen_gcloud.md @@ -52,9 +52,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `GCE_ALLOW_PRIVATE_ZONE` | Allows requested domain to be in private DNS zone, works only with a private ACME server (by default: false) | -| `GCE_POLLING_INTERVAL` | Time between DNS propagation check | -| `GCE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `GCE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `GCE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 5) | +| `GCE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 180) | +| `GCE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | | `GCE_ZONE_ID` | Allows to skip the automatic detection of the zone | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/docs/content/dns/zz_gen_gcore.md b/docs/content/dns/zz_gen_gcore.md index 7dbb3cec8..f2a17c3fb 100644 --- a/docs/content/dns/zz_gen_gcore.md +++ b/docs/content/dns/zz_gen_gcore.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `GCORE_HTTP_TIMEOUT` | API request timeout | -| `GCORE_POLLING_INTERVAL` | Time between DNS propagation check | -| `GCORE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `GCORE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `GCORE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `GCORE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 20) | +| `GCORE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 360) | +| `GCORE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_glesys.md b/docs/content/dns/zz_gen_glesys.md index e49209d85..ff43cfe9a 100644 --- a/docs/content/dns/zz_gen_glesys.md +++ b/docs/content/dns/zz_gen_glesys.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `GLESYS_HTTP_TIMEOUT` | API request timeout | -| `GLESYS_POLLING_INTERVAL` | Time between DNS propagation check | -| `GLESYS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `GLESYS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `GLESYS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `GLESYS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 20) | +| `GLESYS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 1200) | +| `GLESYS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_godaddy.md b/docs/content/dns/zz_gen_godaddy.md index 9852a00d0..c5392a878 100644 --- a/docs/content/dns/zz_gen_godaddy.md +++ b/docs/content/dns/zz_gen_godaddy.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `GODADDY_HTTP_TIMEOUT` | API request timeout | -| `GODADDY_POLLING_INTERVAL` | Time between DNS propagation check | -| `GODADDY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `GODADDY_TTL` | The TTL of the TXT record used for the DNS challenge | +| `GODADDY_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `GODADDY_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `GODADDY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `GODADDY_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_googledomains.md b/docs/content/dns/zz_gen_googledomains.md index a7ccb031e..b1eba25f1 100644 --- a/docs/content/dns/zz_gen_googledomains.md +++ b/docs/content/dns/zz_gen_googledomains.md @@ -47,9 +47,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `GOOGLE_DOMAINS_HTTP_TIMEOUT` | API request timeout | -| `GOOGLE_DOMAINS_POLLING_INTERVAL` | Time between DNS propagation check | -| `GOOGLE_DOMAINS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `GOOGLE_DOMAINS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `GOOGLE_DOMAINS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `GOOGLE_DOMAINS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_hetzner.md b/docs/content/dns/zz_gen_hetzner.md index 1e28e4445..a42175e8d 100644 --- a/docs/content/dns/zz_gen_hetzner.md +++ b/docs/content/dns/zz_gen_hetzner.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `HETZNER_HTTP_TIMEOUT` | API request timeout | -| `HETZNER_POLLING_INTERVAL` | Time between DNS propagation check | -| `HETZNER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `HETZNER_TTL` | The TTL of the TXT record used for the DNS challenge | +| `HETZNER_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `HETZNER_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `HETZNER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `HETZNER_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_hostingde.md b/docs/content/dns/zz_gen_hostingde.md index b2e575c4c..cc86116e1 100644 --- a/docs/content/dns/zz_gen_hostingde.md +++ b/docs/content/dns/zz_gen_hostingde.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `HOSTINGDE_HTTP_TIMEOUT` | API request timeout | -| `HOSTINGDE_POLLING_INTERVAL` | Time between DNS propagation check | -| `HOSTINGDE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `HOSTINGDE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `HOSTINGDE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `HOSTINGDE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `HOSTINGDE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `HOSTINGDE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | | `HOSTINGDE_ZONE_NAME` | Zone name in ACE format | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/docs/content/dns/zz_gen_hosttech.md b/docs/content/dns/zz_gen_hosttech.md index e2881c4fa..4f9f117ba 100644 --- a/docs/content/dns/zz_gen_hosttech.md +++ b/docs/content/dns/zz_gen_hosttech.md @@ -48,10 +48,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `HOSTTECH_HTTP_TIMEOUT` | API request timeout | -| `HOSTTECH_POLLING_INTERVAL` | Time between DNS propagation check | -| `HOSTTECH_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `HOSTTECH_TTL` | The TTL of the TXT record used for the DNS challenge | +| `HOSTTECH_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `HOSTTECH_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `HOSTTECH_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `HOSTTECH_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_httpnet.md b/docs/content/dns/zz_gen_httpnet.md index 8e333992f..06883b3f8 100644 --- a/docs/content/dns/zz_gen_httpnet.md +++ b/docs/content/dns/zz_gen_httpnet.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `HTTPNET_HTTP_TIMEOUT` | API request timeout | -| `HTTPNET_POLLING_INTERVAL` | Time between DNS propagation check | -| `HTTPNET_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `HTTPNET_TTL` | The TTL of the TXT record used for the DNS challenge | +| `HTTPNET_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `HTTPNET_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `HTTPNET_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `HTTPNET_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | | `HTTPNET_ZONE_NAME` | Zone name in ACE format | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/docs/content/dns/zz_gen_httpreq.md b/docs/content/dns/zz_gen_httpreq.md index 81a761d4c..9c6476802 100644 --- a/docs/content/dns/zz_gen_httpreq.md +++ b/docs/content/dns/zz_gen_httpreq.md @@ -48,10 +48,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `HTTPREQ_HTTP_TIMEOUT` | API request timeout | +| `HTTPREQ_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `HTTPREQ_PASSWORD` | Basic authentication password | -| `HTTPREQ_POLLING_INTERVAL` | Time between DNS propagation check | -| `HTTPREQ_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `HTTPREQ_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `HTTPREQ_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | | `HTTPREQ_USERNAME` | Basic authentication username | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/docs/content/dns/zz_gen_huaweicloud.md b/docs/content/dns/zz_gen_huaweicloud.md index d5911eff6..9a37a8878 100644 --- a/docs/content/dns/zz_gen_huaweicloud.md +++ b/docs/content/dns/zz_gen_huaweicloud.md @@ -51,10 +51,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `HUAWEICLOUD_HTTP_TIMEOUT` | API request timeout | -| `HUAWEICLOUD_POLLING_INTERVAL` | Time between DNS propagation check | -| `HUAWEICLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `HUAWEICLOUD_TTL` | The TTL of the TXT record used for the DNS challenge | +| `HUAWEICLOUD_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `HUAWEICLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `HUAWEICLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `HUAWEICLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_hurricane.md b/docs/content/dns/zz_gen_hurricane.md index 385e6501b..da78630d4 100644 --- a/docs/content/dns/zz_gen_hurricane.md +++ b/docs/content/dns/zz_gen_hurricane.md @@ -50,10 +50,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `HURRICANE_HTTP_TIMEOUT` | API request timeout | -| `HURRICANE_POLLING_INTERVAL` | Time between DNS propagation checks | -| `HURRICANE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation; defaults to 300s (5 minutes) | -| `HURRICANE_SEQUENCE_INTERVAL` | Time between sequential requests | +| `HURRICANE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `HURRICANE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `HURRICANE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation (Default: 300) | +| `HURRICANE_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_hyperone.md b/docs/content/dns/zz_gen_hyperone.md index b533de5d5..83dfdb111 100644 --- a/docs/content/dns/zz_gen_hyperone.md +++ b/docs/content/dns/zz_gen_hyperone.md @@ -39,11 +39,12 @@ lego --email you@example.com --dns hyperone -d '*.example.com' -d example.com ru | Environment Variable Name | Description | |--------------------------------|-------------| | `HYPERONE_API_URL` | Allows to pass custom API Endpoint to be used in the challenge (default https://api.hyperone.com/v2) | +| `HYPERONE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `HYPERONE_LOCATION_ID` | Specifies location (region) to be used in API calls. (default pl-waw-1) | | `HYPERONE_PASSPORT_LOCATION` | Allows to pass custom passport file location (default ~/.h1/passport.json) | -| `HYPERONE_POLLING_INTERVAL` | Time between DNS propagation check | -| `HYPERONE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `HYPERONE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `HYPERONE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 60) | +| `HYPERONE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 2) | +| `HYPERONE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_ibmcloud.md b/docs/content/dns/zz_gen_ibmcloud.md index 365377d2b..c2f9f4fe1 100644 --- a/docs/content/dns/zz_gen_ibmcloud.md +++ b/docs/content/dns/zz_gen_ibmcloud.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `SOFTLAYER_POLLING_INTERVAL` | Time between DNS propagation check | -| `SOFTLAYER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `SOFTLAYER_TIMEOUT` | API request timeout | -| `SOFTLAYER_TTL` | The TTL of the TXT record used for the DNS challenge | +| `SOFTLAYER_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `SOFTLAYER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `SOFTLAYER_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `SOFTLAYER_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_iij.md b/docs/content/dns/zz_gen_iij.md index b5e458db2..8c73f58a5 100644 --- a/docs/content/dns/zz_gen_iij.md +++ b/docs/content/dns/zz_gen_iij.md @@ -51,9 +51,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `IIJ_POLLING_INTERVAL` | Time between DNS propagation check | -| `IIJ_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `IIJ_TTL` | The TTL of the TXT record used for the DNS challenge | +| `IIJ_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 4) | +| `IIJ_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 240) | +| `IIJ_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_iijdpf.md b/docs/content/dns/zz_gen_iijdpf.md index b9635ac06..7c694fc32 100644 --- a/docs/content/dns/zz_gen_iijdpf.md +++ b/docs/content/dns/zz_gen_iijdpf.md @@ -50,9 +50,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `IIJ_DPF_API_ENDPOINT` | API endpoint URL, defaults to https://api.dns-platform.jp/dpf/v1 | -| `IIJ_DPF_POLLING_INTERVAL` | Time between DNS propagation check, defaults to 5 second | -| `IIJ_DPF_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation, defaults to 660 second | -| `IIJ_DPF_TTL` | The TTL of the TXT record used for the DNS challenge, default to 300 | +| `IIJ_DPF_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 5) | +| `IIJ_DPF_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 660) | +| `IIJ_DPF_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_infoblox.md b/docs/content/dns/zz_gen_infoblox.md index ba7af4855..f710e2e18 100644 --- a/docs/content/dns/zz_gen_infoblox.md +++ b/docs/content/dns/zz_gen_infoblox.md @@ -51,14 +51,14 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `INFOBLOX_DNS_VIEW` | The view for the TXT records, default: External | -| `INFOBLOX_HTTP_TIMEOUT` | HTTP request timeout | -| `INFOBLOX_POLLING_INTERVAL` | Time between DNS propagation check | -| `INFOBLOX_PORT` | The port for the infoblox grid manager, default: 443 | -| `INFOBLOX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `INFOBLOX_SSL_VERIFY` | Whether or not to verify the TLS certificate, default: true | -| `INFOBLOX_TTL` | The TTL of the TXT record used for the DNS challenge | -| `INFOBLOX_WAPI_VERSION` | The version of WAPI being used, default: 2.11 | +| `INFOBLOX_DNS_VIEW` | The view for the TXT records (Default: External) | +| `INFOBLOX_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `INFOBLOX_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `INFOBLOX_PORT` | The port for the infoblox grid manager (Default: 443) | +| `INFOBLOX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `INFOBLOX_SSL_VERIFY` | Whether or not to verify the TLS certificate (Default: true) | +| `INFOBLOX_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | +| `INFOBLOX_WAPI_VERSION` | The version of WAPI being used (Default: 2.11) | 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" %}}). diff --git a/docs/content/dns/zz_gen_infomaniak.md b/docs/content/dns/zz_gen_infomaniak.md index 4b737d4af..be02d8ee8 100644 --- a/docs/content/dns/zz_gen_infomaniak.md +++ b/docs/content/dns/zz_gen_infomaniak.md @@ -48,10 +48,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `INFOMANIAK_ENDPOINT` | https://api.infomaniak.com | -| `INFOMANIAK_HTTP_TIMEOUT` | API request timeout | -| `INFOMANIAK_POLLING_INTERVAL` | Time between DNS propagation check | -| `INFOMANIAK_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `INFOMANIAK_TTL` | The TTL of the TXT record used for the DNS challenge in seconds | +| `INFOMANIAK_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `INFOMANIAK_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `INFOMANIAK_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `INFOMANIAK_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_internetbs.md b/docs/content/dns/zz_gen_internetbs.md index 3725bcb07..e98fbf4b9 100644 --- a/docs/content/dns/zz_gen_internetbs.md +++ b/docs/content/dns/zz_gen_internetbs.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `INTERNET_BS_HTTP_TIMEOUT` | API request timeout | -| `INTERNET_BS_POLLING_INTERVAL` | Time between DNS propagation check | -| `INTERNET_BS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `INTERNET_BS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `INTERNET_BS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `INTERNET_BS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `INTERNET_BS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `INTERNET_BS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_inwx.md b/docs/content/dns/zz_gen_inwx.md index b51d58c07..a46ff061e 100644 --- a/docs/content/dns/zz_gen_inwx.md +++ b/docs/content/dns/zz_gen_inwx.md @@ -55,11 +55,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `INWX_POLLING_INTERVAL` | Time between DNS propagation check | -| `INWX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation (default 360s) | +| `INWX_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `INWX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 360) | | `INWX_SANDBOX` | Activate the sandbox (boolean) | | `INWX_SHARED_SECRET` | shared secret related to 2FA | -| `INWX_TTL` | The TTL of the TXT record used for the DNS challenge | +| `INWX_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_ionos.md b/docs/content/dns/zz_gen_ionos.md index 54d694da0..c0cb859b7 100644 --- a/docs/content/dns/zz_gen_ionos.md +++ b/docs/content/dns/zz_gen_ionos.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `IONOS_HTTP_TIMEOUT` | API request timeout | -| `IONOS_POLLING_INTERVAL` | Time between DNS propagation check | -| `IONOS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `IONOS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `IONOS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `IONOS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `IONOS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `IONOS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_ipv64.md b/docs/content/dns/zz_gen_ipv64.md index 6d7bcd24c..21327caaf 100644 --- a/docs/content/dns/zz_gen_ipv64.md +++ b/docs/content/dns/zz_gen_ipv64.md @@ -47,10 +47,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `IPV64_HTTP_TIMEOUT` | API request timeout | -| `IPV64_POLLING_INTERVAL` | Time between DNS propagation check | -| `IPV64_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `IPV64_TTL` | The TTL of the TXT record used for the DNS challenge | +| `IPV64_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `IPV64_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `IPV64_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_iwantmyname.md b/docs/content/dns/zz_gen_iwantmyname.md index 8146a36ed..251bf2096 100644 --- a/docs/content/dns/zz_gen_iwantmyname.md +++ b/docs/content/dns/zz_gen_iwantmyname.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `IWANTMYNAME_HTTP_TIMEOUT` | API request timeout | -| `IWANTMYNAME_POLLING_INTERVAL` | Time between DNS propagation check | -| `IWANTMYNAME_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `IWANTMYNAME_TTL` | The TTL of the TXT record used for the DNS challenge | +| `IWANTMYNAME_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `IWANTMYNAME_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `IWANTMYNAME_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `IWANTMYNAME_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_joker.md b/docs/content/dns/zz_gen_joker.md index 2c0a6eafc..c8d55b2f7 100644 --- a/docs/content/dns/zz_gen_joker.md +++ b/docs/content/dns/zz_gen_joker.md @@ -63,11 +63,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `JOKER_HTTP_TIMEOUT` | API request timeout | -| `JOKER_POLLING_INTERVAL` | Time between DNS propagation check | -| `JOKER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `JOKER_SEQUENCE_INTERVAL` | Time between sequential requests (only with 'SVC' mode) | -| `JOKER_TTL` | The TTL of the TXT record used for the DNS challenge | +| `JOKER_HTTP_TIMEOUT` | API request timeout in seconds (Default: 60) | +| `JOKER_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `JOKER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `JOKER_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60), only with 'SVC' mode | +| `JOKER_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_liara.md b/docs/content/dns/zz_gen_liara.md index 23bde4d79..b9776fe35 100644 --- a/docs/content/dns/zz_gen_liara.md +++ b/docs/content/dns/zz_gen_liara.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `LIARA_HTTP_TIMEOUT` | API request timeout | -| `LIARA_POLLING_INTERVAL` | Time between DNS propagation check | -| `LIARA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `LIARA_TTL` | The TTL of the TXT record used for the DNS challenge | +| `LIARA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `LIARA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `LIARA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `LIARA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_lightsail.md b/docs/content/dns/zz_gen_lightsail.md index f2bbaefb7..8e738611b 100644 --- a/docs/content/dns/zz_gen_lightsail.md +++ b/docs/content/dns/zz_gen_lightsail.md @@ -47,8 +47,8 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `AWS_SHARED_CREDENTIALS_FILE` | Managed by the AWS client. Shared credentials file. | -| `LIGHTSAIL_POLLING_INTERVAL` | Time between DNS propagation check | -| `LIGHTSAIL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `LIGHTSAIL_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `LIGHTSAIL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_limacity.md b/docs/content/dns/zz_gen_limacity.md index fdaae55e6..2a01814e5 100644 --- a/docs/content/dns/zz_gen_limacity.md +++ b/docs/content/dns/zz_gen_limacity.md @@ -47,11 +47,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `LIMACITY_HTTP_TIMEOUT` | API request timeout | -| `LIMACITY_POLLING_INTERVAL` | Time between DNS propagation check | -| `LIMACITY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `LIMACITY_SEQUENCE_INTERVAL` | Time between sequential requests | -| `LIMACITY_TTL` | The TTL of the TXT record used for the DNS challenge | +| `LIMACITY_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `LIMACITY_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 80) | +| `LIMACITY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 480) | +| `LIMACITY_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 90) | +| `LIMACITY_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_linode.md b/docs/content/dns/zz_gen_linode.md index 8b97123b2..8c8487541 100644 --- a/docs/content/dns/zz_gen_linode.md +++ b/docs/content/dns/zz_gen_linode.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `LINODE_HTTP_TIMEOUT` | API request timeout | -| `LINODE_POLLING_INTERVAL` | Time between DNS propagation check | -| `LINODE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `LINODE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `LINODE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `LINODE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 15) | +| `LINODE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `LINODE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_liquidweb.md b/docs/content/dns/zz_gen_liquidweb.md index 511ba9c92..9d8fe8c9c 100644 --- a/docs/content/dns/zz_gen_liquidweb.md +++ b/docs/content/dns/zz_gen_liquidweb.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `LWAPI_HTTP_TIMEOUT` | Maximum waiting time for the DNS records to be created (not verified) | -| `LWAPI_POLLING_INTERVAL` | Time between DNS propagation check | -| `LWAPI_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `LWAPI_TTL` | The TTL of the TXT record used for the DNS challenge | +| `LWAPI_HTTP_TIMEOUT` | API request timeout in seconds (Default: 60) | +| `LWAPI_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `LWAPI_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `LWAPI_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | | `LWAPI_URL` | Liquid Web API endpoint | | `LWAPI_ZONE` | DNS Zone | diff --git a/docs/content/dns/zz_gen_loopia.md b/docs/content/dns/zz_gen_loopia.md index 79827d325..3951de8e1 100644 --- a/docs/content/dns/zz_gen_loopia.md +++ b/docs/content/dns/zz_gen_loopia.md @@ -50,10 +50,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `LOOPIA_API_URL` | API endpoint. Ex: https://api.loopia.se/RPCSERV or https://api.loopia.rs/RPCSERV | -| `LOOPIA_HTTP_TIMEOUT` | API request timeout | -| `LOOPIA_POLLING_INTERVAL` | Time between DNS propagation check | -| `LOOPIA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `LOOPIA_TTL` | The TTL of the TXT record used for the DNS challenge | +| `LOOPIA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 60) | +| `LOOPIA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2400) | +| `LOOPIA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `LOOPIA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_luadns.md b/docs/content/dns/zz_gen_luadns.md index 2a6a02dd9..c987cc9bf 100644 --- a/docs/content/dns/zz_gen_luadns.md +++ b/docs/content/dns/zz_gen_luadns.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `LUADNS_HTTP_TIMEOUT` | API request timeout | -| `LUADNS_POLLING_INTERVAL` | Time between DNS propagation check | -| `LUADNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `LUADNS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `LUADNS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `LUADNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `LUADNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `LUADNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_mailinabox.md b/docs/content/dns/zz_gen_mailinabox.md index f3269620f..8b5048c60 100644 --- a/docs/content/dns/zz_gen_mailinabox.md +++ b/docs/content/dns/zz_gen_mailinabox.md @@ -51,8 +51,8 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `MAILINABOX_POLLING_INTERVAL` | Time between DNS propagation check | -| `MAILINABOX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `MAILINABOX_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 4) | +| `MAILINABOX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_manageengine.md b/docs/content/dns/zz_gen_manageengine.md index 32266f2d2..32b3a3aeb 100644 --- a/docs/content/dns/zz_gen_manageengine.md +++ b/docs/content/dns/zz_gen_manageengine.md @@ -49,10 +49,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `MANAGEENGINE_HTTP_TIMEOUT` | API request timeout | -| `MANAGEENGINE_POLLING_INTERVAL` | Time between DNS propagation check | -| `MANAGEENGINE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `MANAGEENGINE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `MANAGEENGINE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `MANAGEENGINE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `MANAGEENGINE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_metaname.md b/docs/content/dns/zz_gen_metaname.md index ea794d4e5..a90d0170b 100644 --- a/docs/content/dns/zz_gen_metaname.md +++ b/docs/content/dns/zz_gen_metaname.md @@ -49,9 +49,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `METANAME_POLLING_INTERVAL` | Time between DNS propagation check | -| `METANAME_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `METANAME_TTL` | The TTL of the TXT record used for the DNS challenge | +| `METANAME_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `METANAME_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `METANAME_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_mijnhost.md b/docs/content/dns/zz_gen_mijnhost.md index 65c1d953d..42abc6558 100644 --- a/docs/content/dns/zz_gen_mijnhost.md +++ b/docs/content/dns/zz_gen_mijnhost.md @@ -47,11 +47,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `MIJNHOST_HTTP_TIMEOUT` | API request timeout | -| `MIJNHOST_POLLING_INTERVAL` | Time between DNS propagation check | -| `MIJNHOST_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `MIJNHOST_SEQUENCE_INTERVAL` | Time between sequential requests | -| `MIJNHOST_TTL` | The TTL of the TXT record used for the DNS challenge | +| `MIJNHOST_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `MIJNHOST_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `MIJNHOST_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `MIJNHOST_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | +| `MIJNHOST_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_mittwald.md b/docs/content/dns/zz_gen_mittwald.md index c1edfe084..943397ee9 100644 --- a/docs/content/dns/zz_gen_mittwald.md +++ b/docs/content/dns/zz_gen_mittwald.md @@ -47,11 +47,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `MITTWALD_HTTP_TIMEOUT` | API request timeout | -| `MITTWALD_POLLING_INTERVAL` | Time between DNS propagation check | -| `MITTWALD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `MITTWALD_SEQUENCE_INTERVAL` | Time between sequential requests | -| `MITTWALD_TTL` | The TTL of the TXT record used for the DNS challenge | +| `MITTWALD_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `MITTWALD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `MITTWALD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `MITTWALD_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 120) | +| `MITTWALD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_mydnsjp.md b/docs/content/dns/zz_gen_mydnsjp.md index 4fc899bf0..5b29266db 100644 --- a/docs/content/dns/zz_gen_mydnsjp.md +++ b/docs/content/dns/zz_gen_mydnsjp.md @@ -49,10 +49,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `MYDNSJP_HTTP_TIMEOUT` | API request timeout | -| `MYDNSJP_POLLING_INTERVAL` | Time between DNS propagation check | -| `MYDNSJP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `MYDNSJP_TTL` | The TTL of the TXT record used for the DNS challenge | +| `MYDNSJP_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `MYDNSJP_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `MYDNSJP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_mythicbeasts.md b/docs/content/dns/zz_gen_mythicbeasts.md index 86e2ae5fd..37feebf8c 100644 --- a/docs/content/dns/zz_gen_mythicbeasts.md +++ b/docs/content/dns/zz_gen_mythicbeasts.md @@ -51,10 +51,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). |--------------------------------|-------------| | `MYTHICBEASTS_API_ENDPOINT` | The endpoint for the API (must implement v2) | | `MYTHICBEASTS_AUTH_API_ENDPOINT` | The endpoint for Mythic Beasts' Authentication | -| `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 | +| `MYTHICBEASTS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `MYTHICBEASTS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `MYTHICBEASTS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `MYTHICBEASTS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_namecheap.md b/docs/content/dns/zz_gen_namecheap.md index 850a9ef8b..706651660 100644 --- a/docs/content/dns/zz_gen_namecheap.md +++ b/docs/content/dns/zz_gen_namecheap.md @@ -54,11 +54,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `NAMECHEAP_HTTP_TIMEOUT` | API request timeout | -| `NAMECHEAP_POLLING_INTERVAL` | Time between DNS propagation check | -| `NAMECHEAP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `NAMECHEAP_HTTP_TIMEOUT` | API request timeout in seconds (Default: 60) | +| `NAMECHEAP_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 15) | +| `NAMECHEAP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 3600) | | `NAMECHEAP_SANDBOX` | Activate the sandbox (boolean) | -| `NAMECHEAP_TTL` | The TTL of the TXT record used for the DNS challenge | +| `NAMECHEAP_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_namedotcom.md b/docs/content/dns/zz_gen_namedotcom.md index df4c94559..36a423faa 100644 --- a/docs/content/dns/zz_gen_namedotcom.md +++ b/docs/content/dns/zz_gen_namedotcom.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `NAMECOM_HTTP_TIMEOUT` | API request timeout | -| `NAMECOM_POLLING_INTERVAL` | Time between DNS propagation check | -| `NAMECOM_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `NAMECOM_TTL` | The TTL of the TXT record used for the DNS challenge | +| `NAMECOM_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `NAMECOM_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 20) | +| `NAMECOM_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 900) | +| `NAMECOM_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_namesilo.md b/docs/content/dns/zz_gen_namesilo.md index 1b69a3524..397a1a3ca 100644 --- a/docs/content/dns/zz_gen_namesilo.md +++ b/docs/content/dns/zz_gen_namesilo.md @@ -47,9 +47,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `NAMESILO_POLLING_INTERVAL` | Time between DNS propagation check | -| `NAMESILO_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation, it is better to set larger than 15m | -| `NAMESILO_TTL` | The TTL of the TXT record used for the DNS challenge, should be in [3600, 2592000] | +| `NAMESILO_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `NAMESILO_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60), it is better to set larger than 15 minutes | +| `NAMESILO_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600), should be in [3600, 2592000] | 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" %}}). diff --git a/docs/content/dns/zz_gen_nearlyfreespeech.md b/docs/content/dns/zz_gen_nearlyfreespeech.md index 1649fd34c..86f6152f9 100644 --- a/docs/content/dns/zz_gen_nearlyfreespeech.md +++ b/docs/content/dns/zz_gen_nearlyfreespeech.md @@ -49,11 +49,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `NEARLYFREESPEECH_HTTP_TIMEOUT` | API request timeout | -| `NEARLYFREESPEECH_POLLING_INTERVAL` | Time between DNS propagation check | -| `NEARLYFREESPEECH_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `NEARLYFREESPEECH_SEQUENCE_INTERVAL` | Time between sequential requests | -| `NEARLYFREESPEECH_TTL` | The TTL of the TXT record used for the DNS challenge | +| `NEARLYFREESPEECH_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `NEARLYFREESPEECH_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `NEARLYFREESPEECH_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `NEARLYFREESPEECH_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | +| `NEARLYFREESPEECH_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_netcup.md b/docs/content/dns/zz_gen_netcup.md index cd19c45c6..337baf59d 100644 --- a/docs/content/dns/zz_gen_netcup.md +++ b/docs/content/dns/zz_gen_netcup.md @@ -51,9 +51,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `NETCUP_HTTP_TIMEOUT` | API request timeout | -| `NETCUP_POLLING_INTERVAL` | Time between DNS propagation check | -| `NETCUP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `NETCUP_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `NETCUP_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 30) | +| `NETCUP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 900) | 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" %}}). diff --git a/docs/content/dns/zz_gen_netlify.md b/docs/content/dns/zz_gen_netlify.md index ad41146dc..b08f650f0 100644 --- a/docs/content/dns/zz_gen_netlify.md +++ b/docs/content/dns/zz_gen_netlify.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `NETLIFY_HTTP_TIMEOUT` | API request timeout | -| `NETLIFY_POLLING_INTERVAL` | Time between DNS propagation check | -| `NETLIFY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `NETLIFY_TTL` | The TTL of the TXT record used for the DNS challenge | +| `NETLIFY_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `NETLIFY_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `NETLIFY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `NETLIFY_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_nicmanager.md b/docs/content/dns/zz_gen_nicmanager.md index 1ae8806cc..db06fb2a6 100644 --- a/docs/content/dns/zz_gen_nicmanager.md +++ b/docs/content/dns/zz_gen_nicmanager.md @@ -70,10 +70,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). |--------------------------------|-------------| | `NICMANAGER_API_MODE` | mode: 'anycast' or 'zone' (default: 'anycast') | | `NICMANAGER_API_OTP` | TOTP Secret (optional) | -| `NICMANAGER_HTTP_TIMEOUT` | API request timeout | -| `NICMANAGER_POLLING_INTERVAL` | Time between DNS propagation check | -| `NICMANAGER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `NICMANAGER_TTL` | The TTL of the TXT record used for the DNS challenge | +| `NICMANAGER_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `NICMANAGER_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `NICMANAGER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | +| `NICMANAGER_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 900) | 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" %}}). diff --git a/docs/content/dns/zz_gen_nifcloud.md b/docs/content/dns/zz_gen_nifcloud.md index bd5d25321..9b9929ce2 100644 --- a/docs/content/dns/zz_gen_nifcloud.md +++ b/docs/content/dns/zz_gen_nifcloud.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `NIFCLOUD_HTTP_TIMEOUT` | API request timeout | -| `NIFCLOUD_POLLING_INTERVAL` | Time between DNS propagation check | -| `NIFCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `NIFCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge | +| `NIFCLOUD_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `NIFCLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `NIFCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `NIFCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_njalla.md b/docs/content/dns/zz_gen_njalla.md index f846cf1e8..cf268041c 100644 --- a/docs/content/dns/zz_gen_njalla.md +++ b/docs/content/dns/zz_gen_njalla.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `NJALLA_HTTP_TIMEOUT` | API request timeout | -| `NJALLA_POLLING_INTERVAL` | Time between DNS propagation check | -| `NJALLA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `NJALLA_TTL` | The TTL of the TXT record used for the DNS challenge | +| `NJALLA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `NJALLA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `NJALLA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `NJALLA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_nodion.md b/docs/content/dns/zz_gen_nodion.md index fc1f820f8..c11759e8e 100644 --- a/docs/content/dns/zz_gen_nodion.md +++ b/docs/content/dns/zz_gen_nodion.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `NODION_HTTP_TIMEOUT` | API request timeout | -| `NODION_POLLING_INTERVAL` | Time between DNS propagation check | -| `NODION_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `NODION_TTL` | The TTL of the TXT record used for the DNS challenge | +| `NODION_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `NODION_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `NODION_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `NODION_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_ns1.md b/docs/content/dns/zz_gen_ns1.md index 9e4c906ad..547a51c1c 100644 --- a/docs/content/dns/zz_gen_ns1.md +++ b/docs/content/dns/zz_gen_ns1.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `NS1_HTTP_TIMEOUT` | API request timeout | -| `NS1_POLLING_INTERVAL` | Time between DNS propagation check | -| `NS1_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `NS1_TTL` | The TTL of the TXT record used for the DNS challenge | +| `NS1_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `NS1_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `NS1_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `NS1_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_oraclecloud.md b/docs/content/dns/zz_gen_oraclecloud.md index 1b6647ce5..1adf58088 100644 --- a/docs/content/dns/zz_gen_oraclecloud.md +++ b/docs/content/dns/zz_gen_oraclecloud.md @@ -59,9 +59,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `OCI_POLLING_INTERVAL` | Time between DNS propagation check | -| `OCI_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `OCI_TTL` | The TTL of the TXT record used for the DNS challenge | +| `OCI_HTTP_TIMEOUT` | API request timeout in seconds (Default: 60) | +| `OCI_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `OCI_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `OCI_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_otc.md b/docs/content/dns/zz_gen_otc.md index 0de59fd64..fe92f3001 100644 --- a/docs/content/dns/zz_gen_otc.md +++ b/docs/content/dns/zz_gen_otc.md @@ -48,11 +48,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `OTC_HTTP_TIMEOUT` | API request timeout | -| `OTC_POLLING_INTERVAL` | Time between DNS propagation check | -| `OTC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `OTC_SEQUENCE_INTERVAL` | Time between sequential requests | -| `OTC_TTL` | The TTL of the TXT record used for the DNS challenge | +| `OTC_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `OTC_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `OTC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `OTC_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | +| `OTC_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_ovh.md b/docs/content/dns/zz_gen_ovh.md index fad507cbd..7abc01b92 100644 --- a/docs/content/dns/zz_gen_ovh.md +++ b/docs/content/dns/zz_gen_ovh.md @@ -71,10 +71,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `OVH_HTTP_TIMEOUT` | API request timeout | -| `OVH_POLLING_INTERVAL` | Time between DNS propagation check | -| `OVH_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `OVH_TTL` | The TTL of the TXT record used for the DNS challenge | +| `OVH_HTTP_TIMEOUT` | API request timeout in seconds (Default: 180) | +| `OVH_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `OVH_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `OVH_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_pdns.md b/docs/content/dns/zz_gen_pdns.md index 31870fbc0..34a22cf84 100644 --- a/docs/content/dns/zz_gen_pdns.md +++ b/docs/content/dns/zz_gen_pdns.md @@ -50,11 +50,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `PDNS_API_VERSION` | Skip API version autodetection and use the provided version number. | -| `PDNS_HTTP_TIMEOUT` | API request timeout | -| `PDNS_POLLING_INTERVAL` | Time between DNS propagation check | -| `PDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `PDNS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `PDNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `PDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | | `PDNS_SERVER_NAME` | Name of the server in the URL, 'localhost' by default | -| `PDNS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `PDNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_plesk.md b/docs/content/dns/zz_gen_plesk.md index 5c9d060cf..b18b2656a 100644 --- a/docs/content/dns/zz_gen_plesk.md +++ b/docs/content/dns/zz_gen_plesk.md @@ -51,10 +51,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `PLESK_HTTP_TIMEOUT` | API request timeout | -| `PLESK_POLLING_INTERVAL` | Time between DNS propagation check | -| `PLESK_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `PLESK_TTL` | The TTL of the TXT record used for the DNS challenge | +| `PLESK_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `PLESK_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `PLESK_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `PLESK_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_porkbun.md b/docs/content/dns/zz_gen_porkbun.md index 5e96e239e..9fd230d0d 100644 --- a/docs/content/dns/zz_gen_porkbun.md +++ b/docs/content/dns/zz_gen_porkbun.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `PORKBUN_HTTP_TIMEOUT` | API request timeout | -| `PORKBUN_POLLING_INTERVAL` | Time between DNS propagation check | -| `PORKBUN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `PORKBUN_TTL` | The TTL of the TXT record used for the DNS challenge | +| `PORKBUN_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `PORKBUN_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `PORKBUN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 600) | +| `PORKBUN_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_rackspace.md b/docs/content/dns/zz_gen_rackspace.md index bbdd8cbfb..6dcf6b2b2 100644 --- a/docs/content/dns/zz_gen_rackspace.md +++ b/docs/content/dns/zz_gen_rackspace.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `RACKSPACE_HTTP_TIMEOUT` | API request timeout | -| `RACKSPACE_POLLING_INTERVAL` | Time between DNS propagation check | -| `RACKSPACE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `RACKSPACE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `RACKSPACE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `RACKSPACE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 3) | +| `RACKSPACE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `RACKSPACE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_rainyun.md b/docs/content/dns/zz_gen_rainyun.md index c0ff646b8..74ced9f54 100644 --- a/docs/content/dns/zz_gen_rainyun.md +++ b/docs/content/dns/zz_gen_rainyun.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `RAINYUN_HTTP_TIMEOUT` | API request timeout | -| `RAINYUN_POLLING_INTERVAL` | Time between DNS propagation check | -| `RAINYUN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `RAINYUN_TTL` | The TTL of the TXT record used for the DNS challenge | +| `RAINYUN_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `RAINYUN_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `RAINYUN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `RAINYUN_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_rcodezero.md b/docs/content/dns/zz_gen_rcodezero.md index 8677de764..98eaea9ca 100644 --- a/docs/content/dns/zz_gen_rcodezero.md +++ b/docs/content/dns/zz_gen_rcodezero.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `RCODEZERO_HTTP_TIMEOUT` | API request timeout | -| `RCODEZERO_POLLING_INTERVAL` | Time between DNS propagation check | -| `RCODEZERO_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `RCODEZERO_TTL` | The TTL of the TXT record used for the DNS challenge | +| `RCODEZERO_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `RCODEZERO_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `RCODEZERO_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 240) | +| `RCODEZERO_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_regfish.md b/docs/content/dns/zz_gen_regfish.md index f5310db53..149338e5e 100644 --- a/docs/content/dns/zz_gen_regfish.md +++ b/docs/content/dns/zz_gen_regfish.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | 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 | +| `REGFISH_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `REGFISH_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `REGFISH_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `REGFISH_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_regru.md b/docs/content/dns/zz_gen_regru.md index 8c6bea662..1d0e0053d 100644 --- a/docs/content/dns/zz_gen_regru.md +++ b/docs/content/dns/zz_gen_regru.md @@ -49,12 +49,12 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `REGRU_HTTP_TIMEOUT` | API request timeout | -| `REGRU_POLLING_INTERVAL` | Time between DNS propagation check | -| `REGRU_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `REGRU_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `REGRU_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `REGRU_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | | `REGRU_TLS_CERT` | authentication certificate | | `REGRU_TLS_KEY` | authentication private key | -| `REGRU_TTL` | The TTL of the TXT record used for the DNS challenge | +| `REGRU_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_rfc2136.md b/docs/content/dns/zz_gen_rfc2136.md index ad52005d4..ffdbc4b54 100644 --- a/docs/content/dns/zz_gen_rfc2136.md +++ b/docs/content/dns/zz_gen_rfc2136.md @@ -61,12 +61,12 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `RFC2136_DNS_TIMEOUT` | API request timeout | -| `RFC2136_POLLING_INTERVAL` | Time between DNS propagation check | -| `RFC2136_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `RFC2136_SEQUENCE_INTERVAL` | Time between sequential requests | +| `RFC2136_DNS_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `RFC2136_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `RFC2136_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `RFC2136_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | | `RFC2136_TSIG_FILE` | Path to a key file generated by tsig-keygen | -| `RFC2136_TTL` | The TTL of the TXT record used for the DNS challenge | +| `RFC2136_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_rimuhosting.md b/docs/content/dns/zz_gen_rimuhosting.md index 46687484c..2a703dec7 100644 --- a/docs/content/dns/zz_gen_rimuhosting.md +++ b/docs/content/dns/zz_gen_rimuhosting.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `RIMUHOSTING_HTTP_TIMEOUT` | API request timeout | -| `RIMUHOSTING_POLLING_INTERVAL` | Time between DNS propagation check | -| `RIMUHOSTING_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `RIMUHOSTING_TTL` | The TTL of the TXT record used for the DNS challenge | +| `RIMUHOSTING_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `RIMUHOSTING_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `RIMUHOSTING_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `RIMUHOSTING_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_route53.md b/docs/content/dns/zz_gen_route53.md index cd18a5c1d..0d06299a1 100644 --- a/docs/content/dns/zz_gen_route53.md +++ b/docs/content/dns/zz_gen_route53.md @@ -59,10 +59,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `AWS_MAX_RETRIES` | The number of maximum returns the service will use to make an individual API request | -| `AWS_POLLING_INTERVAL` | Time between DNS propagation check | -| `AWS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `AWS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 4) | +| `AWS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | | `AWS_SHARED_CREDENTIALS_FILE` | Managed by the AWS client. Shared credentials file. | -| `AWS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `AWS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 10) | 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" %}}). diff --git a/docs/content/dns/zz_gen_safedns.md b/docs/content/dns/zz_gen_safedns.md index c6d4cd745..2a9e179f5 100644 --- a/docs/content/dns/zz_gen_safedns.md +++ b/docs/content/dns/zz_gen_safedns.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `SAFEDNS_HTTP_TIMEOUT` | API request timeout | -| `SAFEDNS_POLLING_INTERVAL` | Time between DNS propagation check | -| `SAFEDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `SAFEDNS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `SAFEDNS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `SAFEDNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `SAFEDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `SAFEDNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_sakuracloud.md b/docs/content/dns/zz_gen_sakuracloud.md index e0af53acf..e08e73e70 100644 --- a/docs/content/dns/zz_gen_sakuracloud.md +++ b/docs/content/dns/zz_gen_sakuracloud.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `SAKURACLOUD_HTTP_TIMEOUT` | API request timeout | -| `SAKURACLOUD_POLLING_INTERVAL` | Time between DNS propagation check | -| `SAKURACLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `SAKURACLOUD_TTL` | The TTL of the TXT record used for the DNS challenge | +| `SAKURACLOUD_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `SAKURACLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `SAKURACLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `SAKURACLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_scaleway.md b/docs/content/dns/zz_gen_scaleway.md index 111d18a42..7f9d6b7c7 100644 --- a/docs/content/dns/zz_gen_scaleway.md +++ b/docs/content/dns/zz_gen_scaleway.md @@ -49,9 +49,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `SCW_ACCESS_KEY` | Access key | -| `SCW_POLLING_INTERVAL` | Time between DNS propagation check | -| `SCW_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `SCW_TTL` | The TTL of the TXT record used for the DNS challenge | +| `SCW_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `SCW_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `SCW_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_selectel.md b/docs/content/dns/zz_gen_selectel.md index 00e5b5bad..33dc859bb 100644 --- a/docs/content/dns/zz_gen_selectel.md +++ b/docs/content/dns/zz_gen_selectel.md @@ -48,10 +48,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `SELECTEL_BASE_URL` | API endpoint URL | -| `SELECTEL_HTTP_TIMEOUT` | API request timeout | -| `SELECTEL_POLLING_INTERVAL` | Time between DNS propagation check | -| `SELECTEL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `SELECTEL_TTL` | The TTL of the TXT record used for the DNS challenge | +| `SELECTEL_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `SELECTEL_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `SELECTEL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `SELECTEL_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_selectelv2.md b/docs/content/dns/zz_gen_selectelv2.md index bb09241aa..24dd67d1e 100644 --- a/docs/content/dns/zz_gen_selectelv2.md +++ b/docs/content/dns/zz_gen_selectelv2.md @@ -54,10 +54,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `SELECTELV2_BASE_URL` | API endpoint URL | -| `SELECTELV2_HTTP_TIMEOUT` | API request timeout | -| `SELECTELV2_POLLING_INTERVAL` | Time between DNS propagation check | -| `SELECTELV2_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `SELECTELV2_TTL` | The TTL of the TXT record used for the DNS challenge | +| `SELECTELV2_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `SELECTELV2_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 5) | +| `SELECTELV2_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `SELECTELV2_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_selfhostde.md b/docs/content/dns/zz_gen_selfhostde.md index 81abe85c1..12df0c10d 100644 --- a/docs/content/dns/zz_gen_selfhostde.md +++ b/docs/content/dns/zz_gen_selfhostde.md @@ -51,10 +51,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `SELFHOSTDE_HTTP_TIMEOUT` | API request timeout | -| `SELFHOSTDE_POLLING_INTERVAL` | Time between DNS propagation check | -| `SELFHOSTDE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `SELFHOSTDE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `SELFHOSTDE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `SELFHOSTDE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 30) | +| `SELFHOSTDE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 240) | +| `SELFHOSTDE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_servercow.md b/docs/content/dns/zz_gen_servercow.md index ce47077df..3214b0ca5 100644 --- a/docs/content/dns/zz_gen_servercow.md +++ b/docs/content/dns/zz_gen_servercow.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `SERVERCOW_HTTP_TIMEOUT` | API request timeout | -| `SERVERCOW_POLLING_INTERVAL` | Time between DNS propagation check | -| `SERVERCOW_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `SERVERCOW_TTL` | The TTL of the TXT record used for the DNS challenge | +| `SERVERCOW_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `SERVERCOW_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `SERVERCOW_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `SERVERCOW_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_shellrent.md b/docs/content/dns/zz_gen_shellrent.md index 1719e07c9..6c1365b7e 100644 --- a/docs/content/dns/zz_gen_shellrent.md +++ b/docs/content/dns/zz_gen_shellrent.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `SHELLRENT_HTTP_TIMEOUT` | API request timeout | -| `SHELLRENT_POLLING_INTERVAL` | Time between DNS propagation check | -| `SHELLRENT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `SHELLRENT_TTL` | The TTL of the TXT record used for the DNS challenge | +| `SHELLRENT_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `SHELLRENT_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `SHELLRENT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | +| `SHELLRENT_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_simply.md b/docs/content/dns/zz_gen_simply.md index 1603ee53f..32df66f05 100644 --- a/docs/content/dns/zz_gen_simply.md +++ b/docs/content/dns/zz_gen_simply.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `SIMPLY_HTTP_TIMEOUT` | API request timeout | -| `SIMPLY_POLLING_INTERVAL` | Time between DNS propagation check | -| `SIMPLY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `SIMPLY_TTL` | The TTL of the TXT record used for the DNS challenge | +| `SIMPLY_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `SIMPLY_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `SIMPLY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | +| `SIMPLY_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_sonic.md b/docs/content/dns/zz_gen_sonic.md index 2adb435a9..f56a23151 100644 --- a/docs/content/dns/zz_gen_sonic.md +++ b/docs/content/dns/zz_gen_sonic.md @@ -49,11 +49,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `SONIC_HTTP_TIMEOUT` | API request timeout | -| `SONIC_POLLING_INTERVAL` | Time between DNS propagation check | -| `SONIC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `SONIC_SEQUENCE_INTERVAL` | Time between sequential requests | -| `SONIC_TTL` | The TTL of the TXT record used for the DNS challenge | +| `SONIC_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `SONIC_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `SONIC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `SONIC_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | +| `SONIC_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_stackpath.md b/docs/content/dns/zz_gen_stackpath.md index cbafa4289..ce0a02eac 100644 --- a/docs/content/dns/zz_gen_stackpath.md +++ b/docs/content/dns/zz_gen_stackpath.md @@ -51,9 +51,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `STACKPATH_POLLING_INTERVAL` | Time between DNS propagation check | -| `STACKPATH_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `STACKPATH_TTL` | The TTL of the TXT record used for the DNS challenge | +| `STACKPATH_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `STACKPATH_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `STACKPATH_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_technitium.md b/docs/content/dns/zz_gen_technitium.md index ecfa204ce..80f7c6a1f 100644 --- a/docs/content/dns/zz_gen_technitium.md +++ b/docs/content/dns/zz_gen_technitium.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `TECHNITIUM_HTTP_TIMEOUT` | API request timeout | -| `TECHNITIUM_POLLING_INTERVAL` | Time between DNS propagation check | -| `TECHNITIUM_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `TECHNITIUM_TTL` | The TTL of the TXT record used for the DNS challenge | +| `TECHNITIUM_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `TECHNITIUM_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `TECHNITIUM_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `TECHNITIUM_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_tencentcloud.md b/docs/content/dns/zz_gen_tencentcloud.md index bc93c225e..bc08c43ce 100644 --- a/docs/content/dns/zz_gen_tencentcloud.md +++ b/docs/content/dns/zz_gen_tencentcloud.md @@ -49,12 +49,12 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `TENCENTCLOUD_HTTP_TIMEOUT` | API request timeout | -| `TENCENTCLOUD_POLLING_INTERVAL` | Time between DNS propagation check | -| `TENCENTCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `TENCENTCLOUD_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `TENCENTCLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `TENCENTCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | | `TENCENTCLOUD_REGION` | Region | | `TENCENTCLOUD_SESSION_TOKEN` | Access Key token | -| `TENCENTCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge | +| `TENCENTCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_timewebcloud.md b/docs/content/dns/zz_gen_timewebcloud.md index e933043a4..af218ddce 100644 --- a/docs/content/dns/zz_gen_timewebcloud.md +++ b/docs/content/dns/zz_gen_timewebcloud.md @@ -47,9 +47,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `TIMEWEBCLOUD_HTTP_TIMEOUT` | API request timeout | -| `TIMEWEBCLOUD_POLLING_INTERVAL` | Time between DNS propagation check | -| `TIMEWEBCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `TIMEWEBCLOUD_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `TIMEWEBCLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `TIMEWEBCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_transip.md b/docs/content/dns/zz_gen_transip.md index 64db62dc6..68b0f7acf 100644 --- a/docs/content/dns/zz_gen_transip.md +++ b/docs/content/dns/zz_gen_transip.md @@ -49,9 +49,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `TRANSIP_POLLING_INTERVAL` | Time between DNS propagation check | -| `TRANSIP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `TRANSIP_TTL` | The TTL of the TXT record used for the DNS challenge | +| `TRANSIP_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `TRANSIP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 600) | +| `TRANSIP_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 10) | 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" %}}). diff --git a/docs/content/dns/zz_gen_ultradns.md b/docs/content/dns/zz_gen_ultradns.md index 36a233ae2..8e0fa9b20 100644 --- a/docs/content/dns/zz_gen_ultradns.md +++ b/docs/content/dns/zz_gen_ultradns.md @@ -50,9 +50,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `ULTRADNS_ENDPOINT` | API endpoint URL, defaults to https://api.ultradns.com/ | -| `ULTRADNS_POLLING_INTERVAL` | Time between DNS propagation check | -| `ULTRADNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `ULTRADNS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `ULTRADNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 4) | +| `ULTRADNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `ULTRADNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_variomedia.md b/docs/content/dns/zz_gen_variomedia.md index 5fc6dfea6..282ec9da3 100644 --- a/docs/content/dns/zz_gen_variomedia.md +++ b/docs/content/dns/zz_gen_variomedia.md @@ -47,11 +47,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `VARIOMEDIA_HTTP_TIMEOUT` | API request timeout | -| `VARIOMEDIA_POLLING_INTERVAL` | Time between DNS propagation check | -| `VARIOMEDIA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `VARIOMEDIA_SEQUENCE_INTERVAL` | Time between sequential requests | -| `VARIOMEDIA_TTL` | The TTL of the TXT record used for the DNS challenge | +| `VARIOMEDIA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `VARIOMEDIA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `VARIOMEDIA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `VARIOMEDIA_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | +| `VARIOMEDIA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_vegadns.md b/docs/content/dns/zz_gen_vegadns.md index b9fe43c1f..e06eebce7 100644 --- a/docs/content/dns/zz_gen_vegadns.md +++ b/docs/content/dns/zz_gen_vegadns.md @@ -46,9 +46,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `VEGADNS_POLLING_INTERVAL` | Time between DNS propagation check | -| `VEGADNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `VEGADNS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `VEGADNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 60) | +| `VEGADNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 720) | +| `VEGADNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 10) | 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" %}}). diff --git a/docs/content/dns/zz_gen_vercel.md b/docs/content/dns/zz_gen_vercel.md index e092b4fff..d9e24eee3 100644 --- a/docs/content/dns/zz_gen_vercel.md +++ b/docs/content/dns/zz_gen_vercel.md @@ -47,11 +47,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `VERCEL_HTTP_TIMEOUT` | API request timeout | -| `VERCEL_POLLING_INTERVAL` | Time between DNS propagation check | -| `VERCEL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `VERCEL_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `VERCEL_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 5) | +| `VERCEL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | | `VERCEL_TEAM_ID` | Team ID (ex: team_xxxxxxxxxxxxxxxxxxxxxxxx) | -| `VERCEL_TTL` | The TTL of the TXT record used for the DNS challenge | +| `VERCEL_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_versio.md b/docs/content/dns/zz_gen_versio.md index 3941605c4..0e2edfa1e 100644 --- a/docs/content/dns/zz_gen_versio.md +++ b/docs/content/dns/zz_gen_versio.md @@ -50,11 +50,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `VERSIO_ENDPOINT` | The endpoint URL of the API Server | -| `VERSIO_HTTP_TIMEOUT` | API request timeout | -| `VERSIO_POLLING_INTERVAL` | Time between DNS propagation check | -| `VERSIO_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `VERSIO_SEQUENCE_INTERVAL` | Time between sequential requests, default 60s | -| `VERSIO_TTL` | The TTL of the TXT record used for the DNS challenge | +| `VERSIO_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `VERSIO_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 5) | +| `VERSIO_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `VERSIO_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | +| `VERSIO_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_vinyldns.md b/docs/content/dns/zz_gen_vinyldns.md index 92e0138dd..03d4b6bb5 100644 --- a/docs/content/dns/zz_gen_vinyldns.md +++ b/docs/content/dns/zz_gen_vinyldns.md @@ -51,9 +51,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `VINYLDNS_POLLING_INTERVAL` | Time between DNS propagation check | -| `VINYLDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `VINYLDNS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `VINYLDNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 4) | +| `VINYLDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `VINYLDNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 30) | 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" %}}). diff --git a/docs/content/dns/zz_gen_vkcloud.md b/docs/content/dns/zz_gen_vkcloud.md index d3c33e9c2..eede62cf5 100644 --- a/docs/content/dns/zz_gen_vkcloud.md +++ b/docs/content/dns/zz_gen_vkcloud.md @@ -54,9 +54,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | `VK_CLOUD_DNS_ENDPOINT` | URL of DNS API. Defaults to https://mcs.mail.ru/public-dns but can be changed for usage with private clouds | | `VK_CLOUD_DOMAIN_NAME` | Openstack users domain name. Defaults to `users` but can be changed for usage with private clouds | | `VK_CLOUD_IDENTITY_ENDPOINT` | URL of OpenStack Auth API, Defaults to https://infra.mail.ru:35357/v3/ but can be changed for usage with private clouds | -| `VK_CLOUD_POLLING_INTERVAL` | Time between DNS propagation check | -| `VK_CLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `VK_CLOUD_TTL` | The TTL of the TXT record used for the DNS challenge | +| `VK_CLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `VK_CLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `VK_CLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_volcengine.md b/docs/content/dns/zz_gen_volcengine.md index a1eb5d4ec..9d3c92d0d 100644 --- a/docs/content/dns/zz_gen_volcengine.md +++ b/docs/content/dns/zz_gen_volcengine.md @@ -50,12 +50,12 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `VOLC_HOST` | API host | -| `VOLC_HTTP_TIMEOUT` | API request timeout | -| `VOLC_POLLING_INTERVAL` | Time between DNS propagation check | -| `VOLC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | +| `VOLC_HTTP_TIMEOUT` | API request timeout in seconds (Default: 15) | +| `VOLC_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `VOLC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 240) | | `VOLC_REGION` | Region | | `VOLC_SCHEME` | API scheme | -| `VOLC_TTL` | The TTL of the TXT record used for the DNS challenge | +| `VOLC_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_vscale.md b/docs/content/dns/zz_gen_vscale.md index 696d404d8..660542d61 100644 --- a/docs/content/dns/zz_gen_vscale.md +++ b/docs/content/dns/zz_gen_vscale.md @@ -48,10 +48,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `VSCALE_BASE_URL` | API endpoint URL | -| `VSCALE_HTTP_TIMEOUT` | API request timeout | -| `VSCALE_POLLING_INTERVAL` | Time between DNS propagation check | -| `VSCALE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `VSCALE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `VSCALE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `VSCALE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `VSCALE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `VSCALE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_vultr.md b/docs/content/dns/zz_gen_vultr.md index 0334a69ad..a3807c1a1 100644 --- a/docs/content/dns/zz_gen_vultr.md +++ b/docs/content/dns/zz_gen_vultr.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `VULTR_HTTP_TIMEOUT` | API request timeout | -| `VULTR_POLLING_INTERVAL` | Time between DNS propagation check | -| `VULTR_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `VULTR_TTL` | The TTL of the TXT record used for the DNS challenge | +| `VULTR_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `VULTR_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `VULTR_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `VULTR_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_webnames.md b/docs/content/dns/zz_gen_webnames.md index 2fdc09cd3..d721466b3 100644 --- a/docs/content/dns/zz_gen_webnames.md +++ b/docs/content/dns/zz_gen_webnames.md @@ -47,10 +47,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `WEBNAMES_HTTP_TIMEOUT` | API request timeout | -| `WEBNAMES_POLLING_INTERVAL` | Time between DNS propagation check | -| `WEBNAMES_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `WEBNAMES_TTL` | The TTL of the TXT record used for the DNS challenge | +| `WEBNAMES_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `WEBNAMES_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `WEBNAMES_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_websupport.md b/docs/content/dns/zz_gen_websupport.md index c48181a54..de3262112 100644 --- a/docs/content/dns/zz_gen_websupport.md +++ b/docs/content/dns/zz_gen_websupport.md @@ -49,11 +49,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `WEBSUPPORT_HTTP_TIMEOUT` | API request timeout | -| `WEBSUPPORT_POLLING_INTERVAL` | Time between DNS propagation check | -| `WEBSUPPORT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `WEBSUPPORT_SEQUENCE_INTERVAL` | Time between sequential requests | -| `WEBSUPPORT_TTL` | The TTL of the TXT record used for the DNS challenge | +| `WEBSUPPORT_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `WEBSUPPORT_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `WEBSUPPORT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `WEBSUPPORT_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | +| `WEBSUPPORT_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_wedos.md b/docs/content/dns/zz_gen_wedos.md index 1762cf4ca..8fe6ba00d 100644 --- a/docs/content/dns/zz_gen_wedos.md +++ b/docs/content/dns/zz_gen_wedos.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `WEDOS_HTTP_TIMEOUT` | API request timeout | -| `WEDOS_POLLING_INTERVAL` | Time between DNS propagation check | -| `WEDOS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `WEDOS_TTL` | The TTL of the TXT record used for the DNS challenge | +| `WEDOS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `WEDOS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `WEDOS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 600) | +| `WEDOS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_westcn.md b/docs/content/dns/zz_gen_westcn.md index fdda3b246..434e5b601 100644 --- a/docs/content/dns/zz_gen_westcn.md +++ b/docs/content/dns/zz_gen_westcn.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `WESTCN_HTTP_TIMEOUT` | API request timeout | -| `WESTCN_POLLING_INTERVAL` | Time between DNS propagation check | -| `WESTCN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `WESTCN_TTL` | The TTL of the TXT record used for the DNS challenge | +| `WESTCN_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `WESTCN_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `WESTCN_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `WESTCN_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_yandex.md b/docs/content/dns/zz_gen_yandex.md index 60b8a0ac3..6100c02fe 100644 --- a/docs/content/dns/zz_gen_yandex.md +++ b/docs/content/dns/zz_gen_yandex.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `YANDEX_HTTP_TIMEOUT` | API request timeout | -| `YANDEX_POLLING_INTERVAL` | Time between DNS propagation check | -| `YANDEX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `YANDEX_TTL` | The TTL of the TXT record used for the DNS challenge | +| `YANDEX_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `YANDEX_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `YANDEX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `YANDEX_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 21600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_yandex360.md b/docs/content/dns/zz_gen_yandex360.md index 04eeab45c..66b90e049 100644 --- a/docs/content/dns/zz_gen_yandex360.md +++ b/docs/content/dns/zz_gen_yandex360.md @@ -49,10 +49,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `YANDEX360_HTTP_TIMEOUT` | API request timeout | -| `YANDEX360_POLLING_INTERVAL` | Time between DNS propagation check | -| `YANDEX360_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `YANDEX360_TTL` | The TTL of the TXT record used for the DNS challenge | +| `YANDEX360_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `YANDEX360_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `YANDEX360_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `YANDEX360_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 21600) | 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" %}}). diff --git a/docs/content/dns/zz_gen_yandexcloud.md b/docs/content/dns/zz_gen_yandexcloud.md index 0831e8c49..f5aeba09d 100644 --- a/docs/content/dns/zz_gen_yandexcloud.md +++ b/docs/content/dns/zz_gen_yandexcloud.md @@ -62,9 +62,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `YANDEX_CLOUD_POLLING_INTERVAL` | Time between DNS propagation check | -| `YANDEX_CLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `YANDEX_CLOUD_TTL` | The TTL of the TXT record used for the DNS challenge | +| `YANDEX_CLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `YANDEX_CLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `YANDEX_CLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | 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" %}}). diff --git a/docs/content/dns/zz_gen_zoneee.md b/docs/content/dns/zz_gen_zoneee.md index a6df03b56..cfc6be692 100644 --- a/docs/content/dns/zz_gen_zoneee.md +++ b/docs/content/dns/zz_gen_zoneee.md @@ -50,10 +50,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `ZONEEE_ENDPOINT` | API endpoint URL | -| `ZONEEE_HTTP_TIMEOUT` | API request timeout | -| `ZONEEE_POLLING_INTERVAL` | Time between DNS propagation check | -| `ZONEEE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `ZONEEE_TTL` | The TTL of the TXT record used for the DNS challenge | +| `ZONEEE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `ZONEEE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 5) | +| `ZONEEE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | 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" %}}). diff --git a/docs/content/dns/zz_gen_zonomi.md b/docs/content/dns/zz_gen_zonomi.md index 51c25d95d..1e90a7285 100644 --- a/docs/content/dns/zz_gen_zonomi.md +++ b/docs/content/dns/zz_gen_zonomi.md @@ -47,10 +47,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `ZONOMI_HTTP_TIMEOUT` | API request timeout | -| `ZONOMI_POLLING_INTERVAL` | Time between DNS propagation check | -| `ZONOMI_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation | -| `ZONOMI_TTL` | The TTL of the TXT record used for the DNS challenge | +| `ZONOMI_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `ZONOMI_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `ZONOMI_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `ZONOMI_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600) | 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" %}}). diff --git a/providers/dns/alidns/alidns.toml b/providers/dns/alidns/alidns.toml index e2d5af8f8..7098213e3 100644 --- a/providers/dns/alidns/alidns.toml +++ b/providers/dns/alidns/alidns.toml @@ -23,10 +23,10 @@ lego --email you@example.com --dns alidns - -d '*.example.com' -d example.com ru ALICLOUD_SECRET_KEY = "Access Key secret" ALICLOUD_SECURITY_TOKEN = "STS Security Token (optional)" [Configuration.Additional] - ALICLOUD_POLLING_INTERVAL = "Time between DNS propagation check" - ALICLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - ALICLOUD_TTL = "The TTL of the TXT record used for the DNS challenge" - ALICLOUD_HTTP_TIMEOUT = "API request timeout" + ALICLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + ALICLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + ALICLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)" + ALICLOUD_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://www.alibabacloud.com/help/en/alibaba-cloud-dns/latest/api-alidns-2015-01-09-dir-parsing-records" diff --git a/providers/dns/allinkl/allinkl.toml b/providers/dns/allinkl/allinkl.toml index 4a308d653..d9c937ee1 100644 --- a/providers/dns/allinkl/allinkl.toml +++ b/providers/dns/allinkl/allinkl.toml @@ -15,9 +15,9 @@ lego --email you@example.com --dns allinkl -d '*.example.com' -d example.com run ALL_INKL_LOGIN = "KAS login" ALL_INKL_PASSWORD = "KAS password" [Configuration.Additional] - ALL_INKL_POLLING_INTERVAL = "Time between DNS propagation check" - ALL_INKL_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - ALL_INKL_HTTP_TIMEOUT = "API request timeout" + ALL_INKL_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + ALL_INKL_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + ALL_INKL_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://kasapi.kasserver.com/dokumentation/phpdoc/index.html" diff --git a/providers/dns/arvancloud/arvancloud.toml b/providers/dns/arvancloud/arvancloud.toml index 3c0fed4ac..e94452a8b 100644 --- a/providers/dns/arvancloud/arvancloud.toml +++ b/providers/dns/arvancloud/arvancloud.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns arvancloud -d '*.example.com' -d example.com [Configuration.Credentials] ARVANCLOUD_API_KEY = "API key" [Configuration.Additional] - ARVANCLOUD_POLLING_INTERVAL = "Time between DNS propagation check" - ARVANCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - ARVANCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge" - ARVANCLOUD_HTTP_TIMEOUT = "API request timeout" + ARVANCLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + ARVANCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + ARVANCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)" + ARVANCLOUD_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.arvancloud.ir/docs/api/cdn/4.0" diff --git a/providers/dns/auroradns/auroradns.toml b/providers/dns/auroradns/auroradns.toml index 4ee8c0975..e000e015e 100644 --- a/providers/dns/auroradns/auroradns.toml +++ b/providers/dns/auroradns/auroradns.toml @@ -16,9 +16,9 @@ lego --email you@example.com --dns auroradns -d '*.example.com' -d example.com r AURORA_SECRET = "Secret password to be used" [Configuration.Additional] AURORA_ENDPOINT = "API endpoint URL" - AURORA_POLLING_INTERVAL = "Time between DNS propagation check" - AURORA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - AURORA_TTL = "The TTL of the TXT record used for the DNS challenge" + AURORA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + AURORA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + AURORA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" [Links] API = "https://libcloud.readthedocs.io/en/latest/dns/drivers/auroradns.html#api-docs" diff --git a/providers/dns/autodns/autodns.toml b/providers/dns/autodns/autodns.toml index 353f223a9..78015e431 100644 --- a/providers/dns/autodns/autodns.toml +++ b/providers/dns/autodns/autodns.toml @@ -17,10 +17,10 @@ lego --email you@example.com --dns autodns -d '*.example.com' -d example.com run [Configuration.Additional] AUTODNS_ENDPOINT = "API endpoint URL, defaults to https://api.autodns.com/v1/" AUTODNS_CONTEXT = "API context (4 for production, 1 for testing. Defaults to 4)" - AUTODNS_TTL = "The TTL of the TXT record used for the DNS challenge" - AUTODNS_POLLING_INTERVAL = "Time between DNS propagation check" - AUTODNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - AUTODNS_HTTP_TIMEOUT = "API request timeout, defaults to 30 seconds" + AUTODNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)" + AUTODNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + AUTODNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + AUTODNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://help.internetx.com/display/APIJSONEN" diff --git a/providers/dns/azure/azure.toml b/providers/dns/azure/azure.toml index c4e3b674a..a38ed55ab 100644 --- a/providers/dns/azure/azure.toml +++ b/providers/dns/azure/azure.toml @@ -19,9 +19,9 @@ Example = '''''' AZURE_METADATA_ENDPOINT = "Metadata Service endpoint URL" AZURE_PRIVATE_ZONE = "Set to true to use Azure Private DNS Zones and not public" AZURE_ZONE_NAME = "Zone name to use inside Azure DNS service to add the TXT record in" - AZURE_POLLING_INTERVAL = "Time between DNS propagation check" - AZURE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - AZURE_TTL = "The TTL of the TXT record used for the DNS challenge" + AZURE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + AZURE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + AZURE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" [Links] API = "https://docs.microsoft.com/en-us/go/azure/" diff --git a/providers/dns/azuredns/azuredns.toml b/providers/dns/azuredns/azuredns.toml index 1f160a856..8d14105cb 100644 --- a/providers/dns/azuredns/azuredns.toml +++ b/providers/dns/azuredns/azuredns.toml @@ -191,9 +191,9 @@ It can be enabled by setting the `AZURE_AUTH_METHOD` environment variable to `oi AZURE_ZONE_NAME = "Zone name to use inside Azure DNS service to add the TXT record in" AZURE_AUTH_METHOD = "Specify which authentication method to use" AZURE_AUTH_MSI_TIMEOUT = "Managed Identity timeout duration" - AZURE_TTL = "The TTL of the TXT record used for the DNS challenge" - AZURE_POLLING_INTERVAL = "Time between DNS propagation check" - AZURE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" + AZURE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + AZURE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + AZURE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" [Links] API = "https://docs.microsoft.com/en-us/go/azure/" diff --git a/providers/dns/bindman/bindman.toml b/providers/dns/bindman/bindman.toml index 4befe9e9d..5c69e18ff 100644 --- a/providers/dns/bindman/bindman.toml +++ b/providers/dns/bindman/bindman.toml @@ -13,9 +13,9 @@ lego --email you@example.com --dns bindman -d '*.example.com' -d example.com run [Configuration.Credentials] BINDMAN_MANAGER_ADDRESS = "The server URL, should have scheme, hostname, and port (if required) of the Bindman-DNS Manager server" [Configuration.Additional] - BINDMAN_POLLING_INTERVAL = "Time between DNS propagation check" - BINDMAN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - BINDMAN_HTTP_TIMEOUT = "API request timeout" + BINDMAN_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + BINDMAN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + BINDMAN_HTTP_TIMEOUT = "API request timeout in seconds (Default: 60)" [Links] API = "https://gitlab.isc.org/isc-projects/bind9" diff --git a/providers/dns/bluecat/bluecat.toml b/providers/dns/bluecat/bluecat.toml index e7eb45664..a01a5918d 100644 --- a/providers/dns/bluecat/bluecat.toml +++ b/providers/dns/bluecat/bluecat.toml @@ -22,10 +22,10 @@ lego --email you@example.com --dns bluecat -d '*.example.com' -d example.com run BLUECAT_CONFIG_NAME = "Configuration name" BLUECAT_DNS_VIEW = "External DNS View Name" [Configuration.Additional] - BLUECAT_POLLING_INTERVAL = "Time between DNS propagation check" - BLUECAT_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - BLUECAT_TTL = "The TTL of the TXT record used for the DNS challenge" - BLUECAT_HTTP_TIMEOUT = "API request timeout" + BLUECAT_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + BLUECAT_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + BLUECAT_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + BLUECAT_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" BLUECAT_SKIP_DEPLOY = "Skip deployements" [Links] diff --git a/providers/dns/brandit/brandit.toml b/providers/dns/brandit/brandit.toml index 1c70eb1ca..32d15c15c 100644 --- a/providers/dns/brandit/brandit.toml +++ b/providers/dns/brandit/brandit.toml @@ -20,10 +20,10 @@ lego --email you@example.com --dns brandit -d '*.example.com' -d example.com run BRANDIT_API_KEY = "The API key" BRANDIT_API_USERNAME = "The API username" [Configuration.Additional] - BRANDIT_POLLING_INTERVAL = "Time between DNS propagation check" - BRANDIT_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - BRANDIT_TTL = "The TTL of the TXT record used for the DNS challenge" - BRANDIT_HTTP_TIMEOUT = "API request timeout" + BRANDIT_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + BRANDIT_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 600)" + BRANDIT_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)" + BRANDIT_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://portal.brandit.com/apidocv3" diff --git a/providers/dns/bunny/bunny.go b/providers/dns/bunny/bunny.go index 9716a20c7..c5bfcb173 100644 --- a/providers/dns/bunny/bunny.go +++ b/providers/dns/bunny/bunny.go @@ -45,7 +45,7 @@ func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), } } diff --git a/providers/dns/bunny/bunny.toml b/providers/dns/bunny/bunny.toml index 22b119bbb..bdbbf3177 100644 --- a/providers/dns/bunny/bunny.toml +++ b/providers/dns/bunny/bunny.toml @@ -13,9 +13,9 @@ lego --email you@example.com --dns bunny -d '*.example.com' -d example.com run [Configuration.Credentials] BUNNY_API_KEY = "API key" [Configuration.Additional] - BUNNY_POLLING_INTERVAL = "Time between DNS propagation check" - BUNNY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - BUNNY_TTL = "The TTL of the TXT record used for the DNS challenge" + BUNNY_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + BUNNY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + BUNNY_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" [Links] API = "https://docs.bunny.net/reference/dnszonepublic_index" diff --git a/providers/dns/checkdomain/checkdomain.toml b/providers/dns/checkdomain/checkdomain.toml index 309b1dfa1..c3ac14e36 100644 --- a/providers/dns/checkdomain/checkdomain.toml +++ b/providers/dns/checkdomain/checkdomain.toml @@ -14,10 +14,10 @@ lego --email you@example.com --dns checkdomain -d '*.example.com' -d example.com CHECKDOMAIN_TOKEN = "API token" [Configuration.Additional] CHECKDOMAIN_ENDPOINT = "API endpoint URL, defaults to https://api.checkdomain.de" - CHECKDOMAIN_TTL = "The TTL of the TXT record used for the DNS challenge" - CHECKDOMAIN_POLLING_INTERVAL = "Time between DNS propagation check" - CHECKDOMAIN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - CHECKDOMAIN_HTTP_TIMEOUT = "API request timeout, defaults to 30 seconds" + CHECKDOMAIN_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + CHECKDOMAIN_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 300)" + CHECKDOMAIN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 7)" + CHECKDOMAIN_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://developer.checkdomain.de/reference/" diff --git a/providers/dns/civo/civo.toml b/providers/dns/civo/civo.toml index fe29364a4..9458f01c3 100644 --- a/providers/dns/civo/civo.toml +++ b/providers/dns/civo/civo.toml @@ -10,12 +10,12 @@ lego --email you@example.com --dns civo -d '*.example.com' -d example.com run ''' [Configuration] - [Configuration.Credentials] - CIVO_TOKEN = "Authentication token" - [Configuration.Additional] - CIVO_POLLING_INTERVAL = "Time between DNS propagation check" - CIVO_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - CIVO_TTL = "The TTL of the TXT record used for the DNS challenge" + [Configuration.Credentials] + CIVO_TOKEN = "Authentication token" + [Configuration.Additional] + CIVO_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 30)" + CIVO_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" + CIVO_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)" [Links] API = "https://www.civo.com/api/dns" diff --git a/providers/dns/clouddns/clouddns.toml b/providers/dns/clouddns/clouddns.toml index 1927e21b5..154d4da67 100644 --- a/providers/dns/clouddns/clouddns.toml +++ b/providers/dns/clouddns/clouddns.toml @@ -17,10 +17,10 @@ lego --email you@example.com --dns clouddns -d '*.example.com' -d example.com ru CLOUDDNS_EMAIL = "Account email" CLOUDDNS_PASSWORD = "Account password" [Configuration.Additional] - CLOUDDNS_POLLING_INTERVAL = "Time between DNS propagation check" - CLOUDDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - CLOUDDNS_TTL = "The TTL of the TXT record used for the DNS challenge" - CLOUDDNS_HTTP_TIMEOUT = "API request timeout" + CLOUDDNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 5)" + CLOUDDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + CLOUDDNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + CLOUDDNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://admin.vshosting.cloud/clouddns/swagger/" diff --git a/providers/dns/cloudflare/cloudflare.go b/providers/dns/cloudflare/cloudflare.go index ded6150e3..d65926f81 100644 --- a/providers/dns/cloudflare/cloudflare.go +++ b/providers/dns/cloudflare/cloudflare.go @@ -64,7 +64,7 @@ func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOneWithFallback(EnvTTL, minTTL, strconv.Atoi, altEnvName(EnvTTL)), PropagationTimeout: env.GetOneWithFallback(EnvPropagationTimeout, 2*time.Minute, env.ParseSecond, altEnvName(EnvPropagationTimeout)), - PollingInterval: env.GetOneWithFallback(EnvPollingInterval, 2*time.Second, env.ParseSecond, altEnvName(EnvPollingInterval)), + PollingInterval: env.GetOneWithFallback(EnvPollingInterval, dns01.DefaultPollingInterval, env.ParseSecond, altEnvName(EnvPollingInterval)), HTTPClient: &http.Client{ Timeout: env.GetOneWithFallback(EnvHTTPTimeout, 30*time.Second, env.ParseSecond, altEnvName(EnvHTTPTimeout)), }, diff --git a/providers/dns/cloudflare/cloudflare.toml b/providers/dns/cloudflare/cloudflare.toml index 0a8295f69..820101053 100644 --- a/providers/dns/cloudflare/cloudflare.toml +++ b/providers/dns/cloudflare/cloudflare.toml @@ -69,10 +69,10 @@ It follows the principle of least privilege and limits the possible damage, shou CLOUDFLARE_DNS_API_TOKEN = "Alias to CF_DNS_API_TOKEN" CLOUDFLARE_ZONE_API_TOKEN = "Alias to CF_ZONE_API_TOKEN" [Configuration.Additional] - CLOUDFLARE_POLLING_INTERVAL = "Time between DNS propagation check (in seconds)" - CLOUDFLARE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation (in seconds)" - CLOUDFLARE_TTL = "The TTL of the TXT record used for the DNS challenge (in seconds)" - CLOUDFLARE_HTTP_TIMEOUT = "API request timeout (in seconds)" + CLOUDFLARE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + CLOUDFLARE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + CLOUDFLARE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + CLOUDFLARE_HTTP_TIMEOUT = "API request timeout in seconds (Default: )" [Links] API = "https://api.cloudflare.com/" diff --git a/providers/dns/cloudns/cloudns.toml b/providers/dns/cloudns/cloudns.toml index dd81da462..dd191f06a 100644 --- a/providers/dns/cloudns/cloudns.toml +++ b/providers/dns/cloudns/cloudns.toml @@ -16,10 +16,10 @@ lego --email you@example.com --dns cloudns -d '*.example.com' -d example.com run CLOUDNS_AUTH_PASSWORD = "The password for API user ID" [Configuration.Additional] CLOUDNS_SUB_AUTH_ID = "The API sub user ID" - CLOUDNS_POLLING_INTERVAL = "Time between DNS propagation check" - CLOUDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - CLOUDNS_TTL = "The TTL of the TXT record used for the DNS challenge" - CLOUDNS_HTTP_TIMEOUT = "API request timeout" + CLOUDNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + CLOUDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 180)" + CLOUDNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + CLOUDNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.cloudns.net/wiki/article/42/" diff --git a/providers/dns/cloudru/cloudru.toml b/providers/dns/cloudru/cloudru.toml index f795c7ac4..a6563a3df 100644 --- a/providers/dns/cloudru/cloudru.toml +++ b/providers/dns/cloudru/cloudru.toml @@ -17,11 +17,11 @@ lego --email you@example.com --dns cloudru -d '*.example.com' -d example.com run CLOUDRU_KEY_ID = "Key ID (login)" CLOUDRU_SECRET = "Key Secret" [Configuration.Additional] - CLOUDRU_POLLING_INTERVAL = "Time between DNS propagation check" - CLOUDRU_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - CLOUDRU_TTL = "The TTL of the TXT record used for the DNS challenge" - CLOUDRU_HTTP_TIMEOUT = "API request timeout" - CLOUDRU_SEQUENCE_INTERVAL = "Time between sequential requests" + CLOUDRU_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 5)" + CLOUDRU_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" + CLOUDRU_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + CLOUDRU_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + CLOUDRU_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 120)" [Links] API = "https://cloud.ru/ru/docs/clouddns/ug/topics/api-ref.html" diff --git a/providers/dns/cloudxns/cloudxns.toml b/providers/dns/cloudxns/cloudxns.toml index 1486cc4fa..e87a741df 100644 --- a/providers/dns/cloudxns/cloudxns.toml +++ b/providers/dns/cloudxns/cloudxns.toml @@ -17,7 +17,7 @@ lego --email you@example.com --dns cloudxns -d '*.example.com' -d example.com ru CLOUDXNS_API_KEY = "The API key" CLOUDXNS_SECRET_KEY = "The API secret key" [Configuration.Additional] - CLOUDXNS_POLLING_INTERVAL = "Time between DNS propagation check" - CLOUDXNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - CLOUDXNS_TTL = "The TTL of the TXT record used for the DNS challenge" - CLOUDXNS_HTTP_TIMEOUT = "API request timeout" + CLOUDXNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: )" + CLOUDXNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: )" + CLOUDXNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: )" + CLOUDXNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: )" diff --git a/providers/dns/conoha/conoha.toml b/providers/dns/conoha/conoha.toml index 87903365f..92c45a555 100644 --- a/providers/dns/conoha/conoha.toml +++ b/providers/dns/conoha/conoha.toml @@ -17,11 +17,11 @@ lego --email you@example.com --dns conoha -d '*.example.com' -d example.com run CONOHA_API_USERNAME = "The API username" CONOHA_API_PASSWORD = "The API password" [Configuration.Additional] - CONOHA_POLLING_INTERVAL = "Time between DNS propagation check" - CONOHA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - CONOHA_TTL = "The TTL of the TXT record used for the DNS challenge" - CONOHA_HTTP_TIMEOUT = "API request timeout" - CONOHA_REGION = "The region" + CONOHA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + CONOHA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + CONOHA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + CONOHA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + CONOHA_REGION = "The region (Default: tyo1)" [Links] API = "https://www.conoha.jp/docs/" diff --git a/providers/dns/constellix/constellix.toml b/providers/dns/constellix/constellix.toml index 02442d31d..c4ae0a194 100644 --- a/providers/dns/constellix/constellix.toml +++ b/providers/dns/constellix/constellix.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns constellix -d '*.example.com' -d example.com CONSTELLIX_API_KEY = "User API key" CONSTELLIX_SECRET_KEY = "User secret key" [Configuration.Additional] - CONSTELLIX_POLLING_INTERVAL = "Time between DNS propagation check" - CONSTELLIX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - CONSTELLIX_TTL = "The TTL of the TXT record used for the DNS challenge" - CONSTELLIX_HTTP_TIMEOUT = "API request timeout" + CONSTELLIX_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + CONSTELLIX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + CONSTELLIX_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + CONSTELLIX_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://api-docs.constellix.com" diff --git a/providers/dns/corenetworks/corenetworks.toml b/providers/dns/corenetworks/corenetworks.toml index f2bae017c..8546d8723 100644 --- a/providers/dns/corenetworks/corenetworks.toml +++ b/providers/dns/corenetworks/corenetworks.toml @@ -15,11 +15,11 @@ lego --email you@example.com --dns corenetworks -d '*.example.com' -d example.co CORENETWORKS_LOGIN = "The username of the API account" CORENETWORKS_PASSWORD = "The password" [Configuration.Additional] - CORENETWORKS_POLLING_INTERVAL = "Time between DNS propagation check" - CORENETWORKS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - CORENETWORKS_TTL = "The TTL of the TXT record used for the DNS challenge" - CORENETWORKS_HTTP_TIMEOUT = "API request timeout" - CORENETWORKS_SEQUENCE_INTERVAL = "Time between sequential requests" + CORENETWORKS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + CORENETWORKS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + CORENETWORKS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)" + CORENETWORKS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + CORENETWORKS_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" [Links] API = "https://beta.api.core-networks.de/doc/" diff --git a/providers/dns/cpanel/cpanel.toml b/providers/dns/cpanel/cpanel.toml index 10f75b385..6b7359e41 100644 --- a/providers/dns/cpanel/cpanel.toml +++ b/providers/dns/cpanel/cpanel.toml @@ -28,11 +28,10 @@ lego --email you@example.com --dns cpanel -d '*.example.com' -d example.com run CPANEL_BASE_URL = "API server URL" [Configuration.Additional] CPANEL_MODE = "use cpanel API or WHM API (Default: cpanel)" - CPANEL_POLLING_INTERVAL = "Time between DNS propagation check" - CPANEL_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - CPANEL_TTL = "The TTL of the TXT record used for the DNS challenge" - CPANEL_HTTP_TIMEOUT = "API request timeout" - CPANEL_REGION = "The region" + CPANEL_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + CPANEL_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + CPANEL_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + CPANEL_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API_CPANEL = "https://api.docs.cpanel.net/cpanel/introduction/" diff --git a/providers/dns/derak/derak.toml b/providers/dns/derak/derak.toml index 202d20834..45d7e1fcf 100644 --- a/providers/dns/derak/derak.toml +++ b/providers/dns/derak/derak.toml @@ -14,7 +14,7 @@ lego --email you@example.com --dns derak -d '*.example.com' -d example.com run DERAK_API_KEY = "The API key" [Configuration.Additional] DERAK_WEBSITE_ID = "Force the zone/website ID" - DERAK_POLLING_INTERVAL = "Time between DNS propagation check" - DERAK_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - DERAK_TTL = "The TTL of the TXT record used for the DNS challenge" - DERAK_HTTP_TIMEOUT = "API request timeout" + DERAK_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 5)" + DERAK_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + DERAK_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + DERAK_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" diff --git a/providers/dns/desec/desec.toml b/providers/dns/desec/desec.toml index 6f5486027..a79b38cd3 100644 --- a/providers/dns/desec/desec.toml +++ b/providers/dns/desec/desec.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns desec -d '*.example.com' -d example.com run [Configuration.Credentials] DESEC_TOKEN = "Domain token" [Configuration.Additional] - DESEC_POLLING_INTERVAL = "Time between DNS propagation check" - DESEC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - DESEC_TTL = "The TTL of the TXT record used for the DNS challenge" - DESEC_HTTP_TIMEOUT = "API request timeout" + DESEC_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 4)" + DESEC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + DESEC_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)" + DESEC_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://desec.readthedocs.io/en/latest/" diff --git a/providers/dns/designate/designate.toml b/providers/dns/designate/designate.toml index aec11eb1e..3ea6260a6 100644 --- a/providers/dns/designate/designate.toml +++ b/providers/dns/designate/designate.toml @@ -64,9 +64,9 @@ Public cloud providers with support for Designate: OS_PROJECT_ID = "Project ID" OS_TENANT_NAME = "Tenant name (deprecated see OS_PROJECT_NAME and OS_PROJECT_ID)" DESIGNATE_ZONE_NAME = "The zone name to use in the OpenStack Project to manage TXT records." - DESIGNATE_POLLING_INTERVAL = "Time between DNS propagation check" - DESIGNATE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - DESIGNATE_TTL = "The TTL of the TXT record used for the DNS challenge" + DESIGNATE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + DESIGNATE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 600)" + DESIGNATE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 10)" [Links] API = "https://docs.openstack.org/designate/latest/" diff --git a/providers/dns/digitalocean/digitalocean.go b/providers/dns/digitalocean/digitalocean.go index 976b1f2e6..0b68aa5c9 100644 --- a/providers/dns/digitalocean/digitalocean.go +++ b/providers/dns/digitalocean/digitalocean.go @@ -46,7 +46,7 @@ func NewDefaultConfig() *Config { return &Config{ BaseURL: env.GetOrDefaultString(EnvAPIUrl, internal.DefaultBaseURL), TTL: env.GetOrDefaultInt(EnvTTL, 30), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 60*time.Second), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 5*time.Second), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), diff --git a/providers/dns/digitalocean/digitalocean.toml b/providers/dns/digitalocean/digitalocean.toml index ef2e9de7c..b30d986f2 100644 --- a/providers/dns/digitalocean/digitalocean.toml +++ b/providers/dns/digitalocean/digitalocean.toml @@ -14,10 +14,10 @@ lego --email you@example.com --dns digitalocean -d '*.example.com' -d example.co DO_AUTH_TOKEN = "Authentication token" [Configuration.Additional] DO_API_URL = "The URL of the API" - DO_POLLING_INTERVAL = "Time between DNS propagation check" - DO_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - DO_TTL = "The TTL of the TXT record used for the DNS challenge" - DO_HTTP_TIMEOUT = "API request timeout" + DO_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 5)" + DO_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + DO_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 30)" + DO_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://developers.digitalocean.com/documentation/v2/#domain-records" diff --git a/providers/dns/directadmin/directadmin.toml b/providers/dns/directadmin/directadmin.toml index 6b9f1353f..bd1c9316a 100644 --- a/providers/dns/directadmin/directadmin.toml +++ b/providers/dns/directadmin/directadmin.toml @@ -18,10 +18,10 @@ lego --email you@example.com --dns directadmin -d '*.example.com' -d example.com DIRECTADMIN_PASSWORD = "API password" [Configuration.Additional] DIRECTADMIN_ZONE_NAME = "Zone name used to add the TXT record" - DIRECTADMIN_POLLING_INTERVAL = "Time between DNS propagation check" - DIRECTADMIN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - DIRECTADMIN_TTL = "The TTL of the TXT record used for the DNS challenge" - DIRECTADMIN_HTTP_TIMEOUT = "API request timeout" + DIRECTADMIN_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 5)" + DIRECTADMIN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + DIRECTADMIN_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 30)" + DIRECTADMIN_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.directadmin.com/api.php" diff --git a/providers/dns/dnshomede/dnshomede.toml b/providers/dns/dnshomede/dnshomede.toml index 3aafb4ef8..bc52bb6dd 100644 --- a/providers/dns/dnshomede/dnshomede.toml +++ b/providers/dns/dnshomede/dnshomede.toml @@ -16,7 +16,7 @@ lego --email you@example.com --dns dnshomede -d my.example.org -d demo.example.o [Configuration.Credentials] DNSHOMEDE_CREDENTIALS = "Comma-separated list of domain:password credential pairs" [Configuration.Additional] - DNSHOMEDE_POLLING_INTERVAL = "Time between DNS propagation checks" - DNSHOMEDE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation; defaults to 300s (5 minutes)" - DNSHOMEDE_SEQUENCE_INTERVAL = "Time between sequential requests" - DNSHOMEDE_HTTP_TIMEOUT = "API request timeout" + DNSHOMEDE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 1200)" + DNSHOMEDE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 2)" + DNSHOMEDE_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 120)" + DNSHOMEDE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" diff --git a/providers/dns/dnsimple/dnsimple.toml b/providers/dns/dnsimple/dnsimple.toml index 4d31daae1..dcf999136 100644 --- a/providers/dns/dnsimple/dnsimple.toml +++ b/providers/dns/dnsimple/dnsimple.toml @@ -32,9 +32,9 @@ Only Account API tokens are supported, if you try to use a User API token you wi DNSIMPLE_OAUTH_TOKEN = "OAuth token" [Configuration.Additional] DNSIMPLE_BASE_URL = "API endpoint URL" - DNSIMPLE_POLLING_INTERVAL = "Time between DNS propagation check" - DNSIMPLE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - DNSIMPLE_TTL = "The TTL of the TXT record used for the DNS challenge" + DNSIMPLE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + DNSIMPLE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + DNSIMPLE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" [Links] API = "https://developer.dnsimple.com/v2/" diff --git a/providers/dns/dnsmadeeasy/dnsmadeeasy.toml b/providers/dns/dnsmadeeasy/dnsmadeeasy.toml index 28b38e771..11a5f85ac 100644 --- a/providers/dns/dnsmadeeasy/dnsmadeeasy.toml +++ b/providers/dns/dnsmadeeasy/dnsmadeeasy.toml @@ -16,10 +16,10 @@ lego --email you@example.com --dns dnsmadeeasy -d '*.example.com' -d example.com DNSMADEEASY_API_SECRET = "The API Secret key" [Configuration.Additional] DNSMADEEASY_SANDBOX = "Activate the sandbox (boolean)" - DNSMADEEASY_POLLING_INTERVAL = "Time between DNS propagation check" - DNSMADEEASY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - DNSMADEEASY_TTL = "The TTL of the TXT record used for the DNS challenge" - DNSMADEEASY_HTTP_TIMEOUT = "API request timeout" + DNSMADEEASY_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + DNSMADEEASY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + DNSMADEEASY_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + DNSMADEEASY_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://api-docs.dnsmadeeasy.com/" diff --git a/providers/dns/dnspod/dnspod.toml b/providers/dns/dnspod/dnspod.toml index 7723f12ed..a0bf50e31 100644 --- a/providers/dns/dnspod/dnspod.toml +++ b/providers/dns/dnspod/dnspod.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns dnspod -d '*.example.com' -d example.com run [Configuration.Credentials] DNSPOD_API_KEY = "The user token" [Configuration.Additional] - DNSPOD_POLLING_INTERVAL = "Time between DNS propagation check" - DNSPOD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - DNSPOD_TTL = "The TTL of the TXT record used for the DNS challenge" - DNSPOD_HTTP_TIMEOUT = "API request timeout" + DNSPOD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + DNSPOD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + DNSPOD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)" + DNSPOD_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://docs.dnspod.com/api/" diff --git a/providers/dns/dode/dode.toml b/providers/dns/dode/dode.toml index a6a6e8f29..0954c902e 100644 --- a/providers/dns/dode/dode.toml +++ b/providers/dns/dode/dode.toml @@ -13,11 +13,11 @@ lego --email you@example.com --dns dode -d '*.example.com' -d example.com run [Configuration.Credentials] DODE_TOKEN = "API token" [Configuration.Additional] - DODE_POLLING_INTERVAL = "Time between DNS propagation check" - DODE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - DODE_TTL = "The TTL of the TXT record used for the DNS challenge" - DODE_HTTP_TIMEOUT = "API request timeout" - DODE_SEQUENCE_INTERVAL = "Time between sequential requests" + DODE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + DODE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + DODE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + DODE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + DODE_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" [Links] API = "https://www.do.de/wiki/freie-ssl-tls-zertifikate-ueber-acme/" diff --git a/providers/dns/domeneshop/domeneshop.toml b/providers/dns/domeneshop/domeneshop.toml index 8dfe806e5..a8d2a1064 100644 --- a/providers/dns/domeneshop/domeneshop.toml +++ b/providers/dns/domeneshop/domeneshop.toml @@ -24,9 +24,9 @@ Visit the following page for information on how to create API credentials with D DOMENESHOP_API_TOKEN = "API token" DOMENESHOP_API_SECRET = "API secret" [Configuration.Additional] - DOMENESHOP_POLLING_INTERVAL = "Time between DNS propagation check" - DOMENESHOP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - DOMENESHOP_HTTP_TIMEOUT = "API request timeout" + DOMENESHOP_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 20)" + DOMENESHOP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" + DOMENESHOP_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://api.domeneshop.no/docs" diff --git a/providers/dns/dreamhost/dreamhost.toml b/providers/dns/dreamhost/dreamhost.toml index a359ad97f..4345e9ece 100644 --- a/providers/dns/dreamhost/dreamhost.toml +++ b/providers/dns/dreamhost/dreamhost.toml @@ -13,10 +13,9 @@ lego --email you@example.com --dns dreamhost -d '*.example.com' -d example.com r [Configuration.Credentials] DREAMHOST_API_KEY = "The API key" [Configuration.Additional] - DREAMHOST_POLLING_INTERVAL = "Time between DNS propagation check" - DREAMHOST_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - DREAMHOST_TTL = "The TTL of the TXT record used for the DNS challenge" - DREAMHOST_HTTP_TIMEOUT = "API request timeout" + DREAMHOST_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 60)" + DREAMHOST_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 3600)" + DREAMHOST_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://help.dreamhost.com/hc/en-us/articles/217560167-API_overview" diff --git a/providers/dns/duckdns/duckdns.toml b/providers/dns/duckdns/duckdns.toml index a0ae92c2d..35041a96b 100644 --- a/providers/dns/duckdns/duckdns.toml +++ b/providers/dns/duckdns/duckdns.toml @@ -13,11 +13,11 @@ lego --email you@example.com --dns duckdns -d '*.example.com' -d example.com run [Configuration.Credentials] DUCKDNS_TOKEN = "Account token" [Configuration.Additional] - DUCKDNS_POLLING_INTERVAL = "Time between DNS propagation check" - DUCKDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - DUCKDNS_TTL = "The TTL of the TXT record used for the DNS challenge" - DUCKDNS_HTTP_TIMEOUT = "API request timeout" - DUCKDNS_SEQUENCE_INTERVAL = "Time between sequential requests" + DUCKDNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + DUCKDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + DUCKDNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + DUCKDNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + DUCKDNS_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" [Links] API = "https://www.duckdns.org/spec.jsp" diff --git a/providers/dns/dyn/dyn.toml b/providers/dns/dyn/dyn.toml index e7607d0a2..4b0d3e652 100644 --- a/providers/dns/dyn/dyn.toml +++ b/providers/dns/dyn/dyn.toml @@ -17,10 +17,10 @@ lego --email you@example.com --dns dyn -d '*.example.com' -d example.com run DYN_USER_NAME = "User name" DYN_PASSWORD = "Password" [Configuration.Additional] - DYN_POLLING_INTERVAL = "Time between DNS propagation check" - DYN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - DYN_TTL = "The TTL of the TXT record used for the DNS challenge" - DYN_HTTP_TIMEOUT = "API request timeout" + DYN_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + DYN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + DYN_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + DYN_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://help.dyn.com/rest/" diff --git a/providers/dns/dynu/dynu.toml b/providers/dns/dynu/dynu.toml index 7d12b428e..ba59034dd 100644 --- a/providers/dns/dynu/dynu.toml +++ b/providers/dns/dynu/dynu.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns dynu -d '*.example.com' -d example.com run [Configuration.Credentials] DYNU_API_KEY = "API key" [Configuration.Additional] - DYNU_POLLING_INTERVAL = "Time between DNS propagation check" - DYNU_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - DYNU_TTL = "The TTL of the TXT record used for the DNS challenge" - DYNU_HTTP_TIMEOUT = "API request timeout" + DYNU_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + DYNU_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 180)" + DYNU_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + DYNU_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.dynu.com/en-US/Support/API" diff --git a/providers/dns/easydns/easydns.toml b/providers/dns/easydns/easydns.toml index 4c775fb5a..71521bbd6 100644 --- a/providers/dns/easydns/easydns.toml +++ b/providers/dns/easydns/easydns.toml @@ -20,11 +20,11 @@ To test with the sandbox environment set ```EASYDNS_ENDPOINT=https://sandbox.res EASYDNS_KEY = "API Key" [Configuration.Additional] EASYDNS_ENDPOINT = "The endpoint URL of the API Server" - EASYDNS_POLLING_INTERVAL = "Time between DNS propagation check" - EASYDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - EASYDNS_SEQUENCE_INTERVAL = "Time between sequential requests" - EASYDNS_TTL = "The TTL of the TXT record used for the DNS challenge" - EASYDNS_HTTP_TIMEOUT = "API request timeout" + EASYDNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + EASYDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + EASYDNS_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" + EASYDNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + EASYDNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://docs.sandbox.rest.easydns.net" diff --git a/providers/dns/edgedns/edgedns.toml b/providers/dns/edgedns/edgedns.toml index c01500112..e925c4aa6 100644 --- a/providers/dns/edgedns/edgedns.toml +++ b/providers/dns/edgedns/edgedns.toml @@ -53,9 +53,9 @@ See also: AKAMAI_EDGERC = "Path to the .edgerc file, managed by the Akamai EdgeGrid client" AKAMAI_EDGERC_SECTION = "Configuration section, managed by the Akamai EdgeGrid client" [Configuration.Additional] - AKAMAI_POLLING_INTERVAL = "Time between DNS propagation check. Default: 15 seconds" - AKAMAI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation. Default: 3 minutes" - AKAMAI_TTL = "The TTL of the TXT record used for the DNS challenge" + AKAMAI_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 15)" + AKAMAI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 180)" + AKAMAI_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" [Links] API = "https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html" diff --git a/providers/dns/efficientip/efficientip.toml b/providers/dns/efficientip/efficientip.toml index f03a8026f..565c9575b 100644 --- a/providers/dns/efficientip/efficientip.toml +++ b/providers/dns/efficientip/efficientip.toml @@ -21,7 +21,6 @@ lego --email you@example.com --dns efficientip -d '*.example.com' -d example.com [Configuration.Additional] EFFICIENTIP_INSECURE_SKIP_VERIFY = "Whether or not to verify EfficientIP API certificate" EFFICIENTIP_VIEW_NAME = "View name (ex: external)" - EFFICIENTIP_POLLING_INTERVAL = "Time between DNS propagation check" - EFFICIENTIP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - EFFICIENTIP_TTL = "The TTL of the TXT record used for the DNS challenge" - EFFICIENTIP_HTTP_TIMEOUT = "API request timeout" + EFFICIENTIP_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + EFFICIENTIP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + EFFICIENTIP_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" diff --git a/providers/dns/epik/epik.toml b/providers/dns/epik/epik.toml index d0f1fda03..7b4688609 100644 --- a/providers/dns/epik/epik.toml +++ b/providers/dns/epik/epik.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns epik -d '*.example.com' -d example.com run [Configuration.Credentials] EPIK_SIGNATURE = "Epik API signature (https://registrar.epik.com/account/api-settings/)" [Configuration.Additional] - EPIK_POLLING_INTERVAL = "Time between DNS propagation check" - EPIK_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - EPIK_TTL = "The TTL of the TXT record used for the DNS challenge" - EPIK_HTTP_TIMEOUT = "API request timeout" + EPIK_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + EPIK_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + EPIK_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)" + EPIK_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://docs-userapi.epik.com/v2/" diff --git a/providers/dns/exec/exec.toml b/providers/dns/exec/exec.toml index b5a68e36a..4c8d70b1c 100644 --- a/providers/dns/exec/exec.toml +++ b/providers/dns/exec/exec.toml @@ -21,11 +21,11 @@ Additional = ''' ## Additional Configuration -| Environment Variable Name | Description | -|----------------------------|-------------------------------------------| -| `EXEC_POLLING_INTERVAL` | Time between DNS propagation check. | -| `EXEC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation. | -| `EXEC_SEQUENCE_INTERVAL` | Time between sequential requests. | +| Environment Variable Name | Description | +|----------------------------|--------------------------------------------------------------------| +| `EXEC_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 3). | +| `EXEC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60). | +| `EXEC_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60). | ## Description diff --git a/providers/dns/exoscale/exoscale.toml b/providers/dns/exoscale/exoscale.toml index 28a756413..82c005d26 100644 --- a/providers/dns/exoscale/exoscale.toml +++ b/providers/dns/exoscale/exoscale.toml @@ -16,10 +16,10 @@ lego --email you@example.com --dns exoscale -d '*.example.com' -d example.com ru EXOSCALE_API_SECRET = "API secret" [Configuration.Additional] EXOSCALE_ENDPOINT = "API endpoint URL" - EXOSCALE_POLLING_INTERVAL = "Time between DNS propagation check" - EXOSCALE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - EXOSCALE_TTL = "The TTL of the TXT record used for the DNS challenge" - EXOSCALE_HTTP_TIMEOUT = "API request timeout" + EXOSCALE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + EXOSCALE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + EXOSCALE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + EXOSCALE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 60)" [Links] API = "https://openapi-v2.exoscale.com/#endpoint-dns" diff --git a/providers/dns/freemyip/freemyip.toml b/providers/dns/freemyip/freemyip.toml index a71538ee3..4821e2a9c 100644 --- a/providers/dns/freemyip/freemyip.toml +++ b/providers/dns/freemyip/freemyip.toml @@ -13,11 +13,11 @@ lego --email you@example.com --dns freemyip -d '*.example.com' -d example.com ru [Configuration.Credentials] FREEMYIP_TOKEN = "Account token" [Configuration.Additional] - FREEMYIP_POLLING_INTERVAL = "Time between DNS propagation check" - FREEMYIP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - FREEMYIP_TTL = "The TTL of the TXT record used for the DNS challenge" - FREEMYIP_HTTP_TIMEOUT = "API request timeout" - FREEMYIP_SEQUENCE_INTERVAL = "Time between sequential requests" + FREEMYIP_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + FREEMYIP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + FREEMYIP_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)" + FREEMYIP_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + FREEMYIP_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" [Links] API = "https://freemyip.com/help" diff --git a/providers/dns/gandi/gandi.toml b/providers/dns/gandi/gandi.toml index be5bc00d2..96d5233be 100644 --- a/providers/dns/gandi/gandi.toml +++ b/providers/dns/gandi/gandi.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns gandi -d '*.example.com' -d example.com run [Configuration.Credentials] GANDI_API_KEY = "API key" [Configuration.Additional] - GANDI_POLLING_INTERVAL = "Time between DNS propagation check" - GANDI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - GANDI_TTL = "The TTL of the TXT record used for the DNS challenge" - GANDI_HTTP_TIMEOUT = "API request timeout" + GANDI_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 60)" + GANDI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 2400)" + GANDI_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + GANDI_HTTP_TIMEOUT = "API request timeout in seconds (Default: 60)" [Links] API = "https://doc.rpc.gandi.net/index.html" diff --git a/providers/dns/gandiv5/gandiv5.toml b/providers/dns/gandiv5/gandiv5.toml index ebeef84b8..246b03524 100644 --- a/providers/dns/gandiv5/gandiv5.toml +++ b/providers/dns/gandiv5/gandiv5.toml @@ -14,10 +14,10 @@ lego --email you@example.com --dns gandiv5 -d '*.example.com' -d example.com run GANDIV5_PERSONAL_ACCESS_TOKEN = "Personal Access Token" GANDIV5_API_KEY = "API key (Deprecated)" [Configuration.Additional] - GANDIV5_POLLING_INTERVAL = "Time between DNS propagation check" - GANDIV5_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - GANDIV5_TTL = "The TTL of the TXT record used for the DNS challenge" - GANDIV5_HTTP_TIMEOUT = "API request timeout" + GANDIV5_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 20)" + GANDIV5_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 1200)" + GANDIV5_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + GANDIV5_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://api.gandi.net/docs/livedns/" diff --git a/providers/dns/gcloud/gcloud.toml b/providers/dns/gcloud/gcloud.toml index ed12a75dc..07a264dae 100644 --- a/providers/dns/gcloud/gcloud.toml +++ b/providers/dns/gcloud/gcloud.toml @@ -19,9 +19,9 @@ lego --email you@email.com --dns gcloud -d '*.example.com' -d example.com run [Configuration.Additional] GCE_ALLOW_PRIVATE_ZONE = "Allows requested domain to be in private DNS zone, works only with a private ACME server (by default: false)" GCE_ZONE_ID = "Allows to skip the automatic detection of the zone" - GCE_POLLING_INTERVAL = "Time between DNS propagation check" - GCE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - GCE_TTL = "The TTL of the TXT record used for the DNS challenge" + GCE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 5)" + GCE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 180)" + GCE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" [Links] API = "https://cloud.google.com/dns/api/v1/" diff --git a/providers/dns/gcore/gcore.toml b/providers/dns/gcore/gcore.toml index bd514ac78..986455e80 100644 --- a/providers/dns/gcore/gcore.toml +++ b/providers/dns/gcore/gcore.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns gcore -d '*.example.com' -d example.com run [Configuration.Credentials] GCORE_PERMANENT_API_TOKEN = "Permanent API token (https://gcore.com/blog/permanent-api-token-explained/)" [Configuration.Additional] - GCORE_POLLING_INTERVAL = "Time between DNS propagation check" - GCORE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - GCORE_TTL = "The TTL of the TXT record used for the DNS challenge" - GCORE_HTTP_TIMEOUT = "API request timeout" + GCORE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 20)" + GCORE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 360)" + GCORE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + GCORE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://api.gcore.com/docs/dns#tag/zones" diff --git a/providers/dns/glesys/glesys.toml b/providers/dns/glesys/glesys.toml index 146b24517..1bdd43c2b 100644 --- a/providers/dns/glesys/glesys.toml +++ b/providers/dns/glesys/glesys.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns glesys -d '*.example.com' -d example.com run GLESYS_API_USER = "API user" GLESYS_API_KEY = "API key" [Configuration.Additional] - GLESYS_POLLING_INTERVAL = "Time between DNS propagation check" - GLESYS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - GLESYS_TTL = "The TTL of the TXT record used for the DNS challenge" - GLESYS_HTTP_TIMEOUT = "API request timeout" + GLESYS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 20)" + GLESYS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 1200)" + GLESYS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + GLESYS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://github.com/GleSYS/API/wiki/API-Documentation" diff --git a/providers/dns/godaddy/godaddy.go b/providers/dns/godaddy/godaddy.go index bc0f42339..38e470509 100644 --- a/providers/dns/godaddy/godaddy.go +++ b/providers/dns/godaddy/godaddy.go @@ -46,7 +46,7 @@ func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), }, diff --git a/providers/dns/godaddy/godaddy.toml b/providers/dns/godaddy/godaddy.toml index aa835d087..acf0bf404 100644 --- a/providers/dns/godaddy/godaddy.toml +++ b/providers/dns/godaddy/godaddy.toml @@ -24,10 +24,10 @@ https://community.letsencrypt.org/t/getting-unauthorized-url-error-while-trying- GODADDY_API_KEY = "API key" GODADDY_API_SECRET = "API secret" [Configuration.Additional] - GODADDY_POLLING_INTERVAL = "Time between DNS propagation check" - GODADDY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - GODADDY_TTL = "The TTL of the TXT record used for the DNS challenge" - GODADDY_HTTP_TIMEOUT = "API request timeout" + GODADDY_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + GODADDY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + GODADDY_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)" + GODADDY_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://developer.godaddy.com/doc/endpoint/domains" diff --git a/providers/dns/googledomains/googledomains.go b/providers/dns/googledomains/googledomains.go index 933929147..aef4454fd 100644 --- a/providers/dns/googledomains/googledomains.go +++ b/providers/dns/googledomains/googledomains.go @@ -39,7 +39,7 @@ type Config struct { func NewDefaultConfig() *Config { return &Config{ PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), }, diff --git a/providers/dns/googledomains/googledomains.toml b/providers/dns/googledomains/googledomains.toml index 97e5452cc..580d05dbf 100644 --- a/providers/dns/googledomains/googledomains.toml +++ b/providers/dns/googledomains/googledomains.toml @@ -13,9 +13,9 @@ lego --email you@example.com --dns googledomains -d '*.example.com' -d example.c [Configuration.Credentials] GOOGLE_DOMAINS_ACCESS_TOKEN = "Access token" [Configuration.Additional] - GOOGLE_DOMAINS_POLLING_INTERVAL = "Time between DNS propagation check" - GOOGLE_DOMAINS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - GOOGLE_DOMAINS_HTTP_TIMEOUT = "API request timeout" + GOOGLE_DOMAINS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + GOOGLE_DOMAINS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + GOOGLE_DOMAINS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] GoClient = "https://github.com/googleapis/google-api-go-client" diff --git a/providers/dns/hetzner/hetzner.go b/providers/dns/hetzner/hetzner.go index e5c5ca266..4dcd8e071 100644 --- a/providers/dns/hetzner/hetzner.go +++ b/providers/dns/hetzner/hetzner.go @@ -44,7 +44,7 @@ func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), }, diff --git a/providers/dns/hetzner/hetzner.toml b/providers/dns/hetzner/hetzner.toml index 77d23acb8..033ae2d2f 100644 --- a/providers/dns/hetzner/hetzner.toml +++ b/providers/dns/hetzner/hetzner.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns hetzner -d '*.example.com' -d example.com run [Configuration.Credentials] HETZNER_API_KEY = "API key" [Configuration.Additional] - HETZNER_POLLING_INTERVAL = "Time between DNS propagation check" - HETZNER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - HETZNER_TTL = "The TTL of the TXT record used for the DNS challenge" - HETZNER_HTTP_TIMEOUT = "API request timeout" + HETZNER_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + HETZNER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + HETZNER_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + HETZNER_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://dns.hetzner.com/api-docs" diff --git a/providers/dns/hostingde/hostingde.go b/providers/dns/hostingde/hostingde.go index 67c4661bd..87fc73d34 100644 --- a/providers/dns/hostingde/hostingde.go +++ b/providers/dns/hostingde/hostingde.go @@ -46,7 +46,7 @@ func NewDefaultConfig() *Config { ZoneName: env.GetOrFile(EnvZoneName), TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), }, diff --git a/providers/dns/hostingde/hostingde.toml b/providers/dns/hostingde/hostingde.toml index 39e7ab0f9..569e8a781 100644 --- a/providers/dns/hostingde/hostingde.toml +++ b/providers/dns/hostingde/hostingde.toml @@ -14,10 +14,10 @@ lego --email you@example.com --dns hostingde -d '*.example.com' -d example.com r HOSTINGDE_API_KEY = "API key" [Configuration.Additional] HOSTINGDE_ZONE_NAME = "Zone name in ACE format" - HOSTINGDE_POLLING_INTERVAL = "Time between DNS propagation check" - HOSTINGDE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - HOSTINGDE_TTL = "The TTL of the TXT record used for the DNS challenge" - HOSTINGDE_HTTP_TIMEOUT = "API request timeout" + HOSTINGDE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + HOSTINGDE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + HOSTINGDE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + HOSTINGDE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.hosting.de/api/#dns" diff --git a/providers/dns/hosttech/hosttech.toml b/providers/dns/hosttech/hosttech.toml index 89d495b0c..5d7555499 100644 --- a/providers/dns/hosttech/hosttech.toml +++ b/providers/dns/hosttech/hosttech.toml @@ -14,10 +14,10 @@ lego --email you@example.com --dns hosttech -d '*.example.com' -d example.com ru HOSTTECH_API_KEY = "API login" HOSTTECH_PASSWORD = "API password" [Configuration.Additional] - HOSTTECH_POLLING_INTERVAL = "Time between DNS propagation check" - HOSTTECH_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - HOSTTECH_TTL = "The TTL of the TXT record used for the DNS challenge" - HOSTTECH_HTTP_TIMEOUT = "API request timeout" + HOSTTECH_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + HOSTTECH_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + HOSTTECH_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)" + HOSTTECH_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://api.ns1.hosttech.eu/api/documentation" diff --git a/providers/dns/httpnet/httpnet.go b/providers/dns/httpnet/httpnet.go index 41f4ffbf8..56bd92712 100644 --- a/providers/dns/httpnet/httpnet.go +++ b/providers/dns/httpnet/httpnet.go @@ -47,7 +47,7 @@ func NewDefaultConfig() *Config { ZoneName: env.GetOrFile(EnvZoneName), TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), }, diff --git a/providers/dns/httpnet/httpnet.toml b/providers/dns/httpnet/httpnet.toml index baf170973..204f5bc54 100644 --- a/providers/dns/httpnet/httpnet.toml +++ b/providers/dns/httpnet/httpnet.toml @@ -14,10 +14,10 @@ lego --email you@example.com --dns httpnet -d '*.example.com' -d example.com run HTTPNET_API_KEY = "API key" [Configuration.Additional] HTTPNET_ZONE_NAME = "Zone name in ACE format" - HTTPNET_POLLING_INTERVAL = "Time between DNS propagation check" - HTTPNET_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - HTTPNET_TTL = "The TTL of the TXT record used for the DNS challenge" - HTTPNET_HTTP_TIMEOUT = "API request timeout" + HTTPNET_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + HTTPNET_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + HTTPNET_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + HTTPNET_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.http.net/docs/api/#dns" diff --git a/providers/dns/httpreq/httpreq.toml b/providers/dns/httpreq/httpreq.toml index 43f3e4f62..6c3f8719b 100644 --- a/providers/dns/httpreq/httpreq.toml +++ b/providers/dns/httpreq/httpreq.toml @@ -56,6 +56,6 @@ Basic authentication (optional) can be set with some environment variables: [Configuration.Additional] HTTPREQ_USERNAME = "Basic authentication username" HTTPREQ_PASSWORD = "Basic authentication password" - HTTPREQ_POLLING_INTERVAL = "Time between DNS propagation check" - HTTPREQ_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - HTTPREQ_HTTP_TIMEOUT = "API request timeout" + HTTPREQ_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + HTTPREQ_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + HTTPREQ_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" diff --git a/providers/dns/huaweicloud/huaweicloud.toml b/providers/dns/huaweicloud/huaweicloud.toml index 423dd9d7d..f7991dfae 100644 --- a/providers/dns/huaweicloud/huaweicloud.toml +++ b/providers/dns/huaweicloud/huaweicloud.toml @@ -18,10 +18,10 @@ lego --email you@example.com --dns huaweicloud -d '*.example.com' -d example.com HUAWEICLOUD_REGION = "Region" [Configuration.Additional] - HUAWEICLOUD_POLLING_INTERVAL = "Time between DNS propagation check" - HUAWEICLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - HUAWEICLOUD_TTL = "The TTL of the TXT record used for the DNS challenge" - HUAWEICLOUD_HTTP_TIMEOUT = "API request timeout" + HUAWEICLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + HUAWEICLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + HUAWEICLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + HUAWEICLOUD_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://console-intl.huaweicloud.com/apiexplorer/#/openapi/DNS/doc?locale=en-us" diff --git a/providers/dns/hurricane/hurricane.toml b/providers/dns/hurricane/hurricane.toml index 88e73dea9..033c73984 100644 --- a/providers/dns/hurricane/hurricane.toml +++ b/providers/dns/hurricane/hurricane.toml @@ -39,10 +39,10 @@ HURRICANE_TOKENS=example.org:token [Configuration.Credentials] HURRICANE_TOKENS = "TXT record names and tokens" [Configuration.Additional] - HURRICANE_POLLING_INTERVAL = "Time between DNS propagation checks" - HURRICANE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation; defaults to 300s (5 minutes)" - HURRICANE_SEQUENCE_INTERVAL = "Time between sequential requests" - HURRICANE_HTTP_TIMEOUT = "API request timeout" + HURRICANE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + HURRICANE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation (Default: 300)" + HURRICANE_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" + HURRICANE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://dns.he.net/" diff --git a/providers/dns/hyperone/hyperone.toml b/providers/dns/hyperone/hyperone.toml index bebde3185..0f23976c4 100644 --- a/providers/dns/hyperone/hyperone.toml +++ b/providers/dns/hyperone/hyperone.toml @@ -41,9 +41,10 @@ All required permissions are available via platform role `tool.lego`. HYPERONE_PASSPORT_LOCATION = "Allows to pass custom passport file location (default ~/.h1/passport.json)" HYPERONE_API_URL = "Allows to pass custom API Endpoint to be used in the challenge (default https://api.hyperone.com/v2)" HYPERONE_LOCATION_ID = "Specifies location (region) to be used in API calls. (default pl-waw-1)" - HYPERONE_TTL = "The TTL of the TXT record used for the DNS challenge" - HYPERONE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - HYPERONE_POLLING_INTERVAL = "Time between DNS propagation check" + HYPERONE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + HYPERONE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 2)" + HYPERONE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 60)" + HYPERONE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://api.hyperone.com/v2/docs" diff --git a/providers/dns/ibmcloud/ibmcloud.toml b/providers/dns/ibmcloud/ibmcloud.toml index 270995465..090c010c9 100644 --- a/providers/dns/ibmcloud/ibmcloud.toml +++ b/providers/dns/ibmcloud/ibmcloud.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns ibmcloud -d '*.example.com' -d example.com ru SOFTLAYER_USERNAME = "Username (IBM Cloud is _)" SOFTLAYER_API_KEY = "Classic Infrastructure API key" [Configuration.Additional] - SOFTLAYER_POLLING_INTERVAL = "Time between DNS propagation check" - SOFTLAYER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - SOFTLAYER_TTL = "The TTL of the TXT record used for the DNS challenge" - SOFTLAYER_TIMEOUT = "API request timeout" + SOFTLAYER_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + SOFTLAYER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + SOFTLAYER_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + SOFTLAYER_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://cloud.ibm.com/docs/dns?topic=dns-getting-started-with-the-dns-api" diff --git a/providers/dns/iij/iij.toml b/providers/dns/iij/iij.toml index da7590dd9..8dbf5ba1a 100644 --- a/providers/dns/iij/iij.toml +++ b/providers/dns/iij/iij.toml @@ -17,9 +17,9 @@ lego --email you@example.com --dns iij -d '*.example.com' -d example.com run IIJ_API_SECRET_KEY = "API secret key" IIJ_DO_SERVICE_CODE = "DO service code" [Configuration.Additional] - IIJ_POLLING_INTERVAL = "Time between DNS propagation check" - IIJ_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - IIJ_TTL = "The TTL of the TXT record used for the DNS challenge" + IIJ_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 4)" + IIJ_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 240)" + IIJ_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" [Links] API = "https://manual.iij.jp/p2/pubapi/" diff --git a/providers/dns/iijdpf/iijdpf.toml b/providers/dns/iijdpf/iijdpf.toml index 297866e2b..4aaa9ca37 100644 --- a/providers/dns/iijdpf/iijdpf.toml +++ b/providers/dns/iijdpf/iijdpf.toml @@ -16,9 +16,9 @@ lego --email you@example.com --dns iijdpf -d '*.example.com' -d example.com run IIJ_DPF_DPM_SERVICE_CODE = "IIJ Managed DNS Service's service code" [Configuration.Additional] IIJ_DPF_API_ENDPOINT = "API endpoint URL, defaults to https://api.dns-platform.jp/dpf/v1" - IIJ_DPF_POLLING_INTERVAL = "Time between DNS propagation check, defaults to 5 second" - IIJ_DPF_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation, defaults to 660 second" - IIJ_DPF_TTL = "The TTL of the TXT record used for the DNS challenge, default to 300" + IIJ_DPF_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 5)" + IIJ_DPF_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 660)" + IIJ_DPF_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" [Links] API = "https://manual.iij.jp/dpf/dpfapi/" diff --git a/providers/dns/infoblox/infoblox.toml b/providers/dns/infoblox/infoblox.toml index ad7cb5cef..5cd355c1a 100644 --- a/providers/dns/infoblox/infoblox.toml +++ b/providers/dns/infoblox/infoblox.toml @@ -21,14 +21,14 @@ When creating an API's user ensure it has the proper permissions for the view yo INFOBLOX_PASSWORD = "Account Password" INFOBLOX_HOST = "Host URI" [Configuration.Additional] - INFOBLOX_DNS_VIEW = "The view for the TXT records, default: External" - INFOBLOX_WAPI_VERSION = "The version of WAPI being used, default: 2.11" - INFOBLOX_PORT = "The port for the infoblox grid manager, default: 443" - INFOBLOX_SSL_VERIFY = "Whether or not to verify the TLS certificate, default: true" - INFOBLOX_POLLING_INTERVAL = "Time between DNS propagation check" - INFOBLOX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - INFOBLOX_TTL = "The TTL of the TXT record used for the DNS challenge" - INFOBLOX_HTTP_TIMEOUT = "HTTP request timeout" + INFOBLOX_DNS_VIEW = "The view for the TXT records (Default: External)" + INFOBLOX_WAPI_VERSION = "The version of WAPI being used (Default: 2.11)" + INFOBLOX_PORT = "The port for the infoblox grid manager (Default: 443)" + INFOBLOX_SSL_VERIFY = "Whether or not to verify the TLS certificate (Default: true)" + INFOBLOX_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + INFOBLOX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + INFOBLOX_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + INFOBLOX_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] diff --git a/providers/dns/infomaniak/infomaniak.toml b/providers/dns/infomaniak/infomaniak.toml index 2de205b8f..283838053 100644 --- a/providers/dns/infomaniak/infomaniak.toml +++ b/providers/dns/infomaniak/infomaniak.toml @@ -21,10 +21,10 @@ You will need domain scope. INFOMANIAK_ACCESS_TOKEN = "Access token" [Configuration.Additional] INFOMANIAK_ENDPOINT = "https://api.infomaniak.com" - INFOMANIAK_POLLING_INTERVAL = "Time between DNS propagation check" - INFOMANIAK_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - INFOMANIAK_TTL = "The TTL of the TXT record used for the DNS challenge in seconds" - INFOMANIAK_HTTP_TIMEOUT = "API request timeout" + INFOMANIAK_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + INFOMANIAK_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + INFOMANIAK_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + INFOMANIAK_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://api.infomaniak.com/doc" diff --git a/providers/dns/internetbs/internetbs.toml b/providers/dns/internetbs/internetbs.toml index 054a1f6e9..d25418f22 100644 --- a/providers/dns/internetbs/internetbs.toml +++ b/providers/dns/internetbs/internetbs.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns internetbs -d '*.example.com' -d example.com INTERNET_BS_API_KEY = "API key" INTERNET_BS_PASSWORD = "API password" [Configuration.Additional] - INTERNET_BS_POLLING_INTERVAL = "Time between DNS propagation check" - INTERNET_BS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - INTERNET_BS_TTL = "The TTL of the TXT record used for the DNS challenge" - INTERNET_BS_HTTP_TIMEOUT = "API request timeout" + INTERNET_BS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + INTERNET_BS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + INTERNET_BS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)" + INTERNET_BS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://internetbs.net/internet-bs-api.pdf" diff --git a/providers/dns/inwx/inwx.go b/providers/dns/inwx/inwx.go index f316fd548..9945d904c 100644 --- a/providers/dns/inwx/inwx.go +++ b/providers/dns/inwx/inwx.go @@ -46,7 +46,7 @@ func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, 300), // INWX has rather unstable propagation delays, thus using a larger default value - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 360*time.Second), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 6*time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), Sandbox: env.GetOrDefaultBool(EnvSandbox, false), } diff --git a/providers/dns/inwx/inwx.toml b/providers/dns/inwx/inwx.toml index 1186dcf20..aeab5a242 100644 --- a/providers/dns/inwx/inwx.toml +++ b/providers/dns/inwx/inwx.toml @@ -22,9 +22,9 @@ lego --email you@example.com --dns inwx -d '*.example.com' -d example.com run INWX_PASSWORD = "Password" [Configuration.Additional] INWX_SHARED_SECRET = "shared secret related to 2FA" - INWX_POLLING_INTERVAL = "Time between DNS propagation check" - INWX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation (default 360s)" - INWX_TTL = "The TTL of the TXT record used for the DNS challenge" + INWX_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + INWX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 360)" + INWX_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" INWX_SANDBOX = "Activate the sandbox (boolean)" [Links] diff --git a/providers/dns/ionos/ionos.toml b/providers/dns/ionos/ionos.toml index e9bfd7319..3c5239594 100644 --- a/providers/dns/ionos/ionos.toml +++ b/providers/dns/ionos/ionos.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns ionos -d '*.example.com' -d example.com run [Configuration.Credentials] IONOS_API_KEY = "API key `.` https://developer.hosting.ionos.com/docs/getstarted" [Configuration.Additional] - IONOS_POLLING_INTERVAL = "Time between DNS propagation check" - IONOS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - IONOS_TTL = "The TTL of the TXT record used for the DNS challenge" - IONOS_HTTP_TIMEOUT = "API request timeout" + IONOS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + IONOS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + IONOS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + IONOS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://developer.hosting.ionos.com/docs/dns" diff --git a/providers/dns/ipv64/ipv64.toml b/providers/dns/ipv64/ipv64.toml index ece506c34..fba210bdb 100644 --- a/providers/dns/ipv64/ipv64.toml +++ b/providers/dns/ipv64/ipv64.toml @@ -13,10 +13,9 @@ lego --email you@example.com --dns ipv64 -d '*.example.com' -d example.com run [Configuration.Credentials] IPV64_API_KEY = "Account API Key" [Configuration.Additional] - IPV64_POLLING_INTERVAL = "Time between DNS propagation check" - IPV64_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - IPV64_TTL = "The TTL of the TXT record used for the DNS challenge" - IPV64_HTTP_TIMEOUT = "API request timeout" + IPV64_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + IPV64_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + IPV64_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://ipv64.net/dyndns_updater_api" diff --git a/providers/dns/iwantmyname/iwantmyname.toml b/providers/dns/iwantmyname/iwantmyname.toml index 678977029..00a45b714 100644 --- a/providers/dns/iwantmyname/iwantmyname.toml +++ b/providers/dns/iwantmyname/iwantmyname.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns iwantmyname -d '*.example.com' -d example.com IWANTMYNAME_USERNAME = "API username" IWANTMYNAME_PASSWORD = "API password" [Configuration.Additional] - IWANTMYNAME_POLLING_INTERVAL = "Time between DNS propagation check" - IWANTMYNAME_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - IWANTMYNAME_TTL = "The TTL of the TXT record used for the DNS challenge" - IWANTMYNAME_HTTP_TIMEOUT = "API request timeout" + IWANTMYNAME_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + IWANTMYNAME_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + IWANTMYNAME_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + IWANTMYNAME_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://iwantmyname.com/developer/domain-dns-api" diff --git a/providers/dns/joker/joker.toml b/providers/dns/joker/joker.toml index 1f5acf17f..35713df18 100644 --- a/providers/dns/joker/joker.toml +++ b/providers/dns/joker/joker.toml @@ -48,11 +48,11 @@ As per [Joker.com documentation](https://joker.com/faq/content/6/496/en/let_s-en JOKER_PASSWORD = "Joker.com password" JOKER_API_KEY = "API key (only with DMAPI mode)" [Configuration.Additional] - JOKER_POLLING_INTERVAL = "Time between DNS propagation check" - JOKER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - JOKER_TTL = "The TTL of the TXT record used for the DNS challenge" - JOKER_HTTP_TIMEOUT = "API request timeout" - JOKER_SEQUENCE_INTERVAL = "Time between sequential requests (only with 'SVC' mode)" + JOKER_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + JOKER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + JOKER_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + JOKER_HTTP_TIMEOUT = "API request timeout in seconds (Default: 60)" + JOKER_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60), only with 'SVC' mode" [Links] API = "https://joker.com/faq/category/39/22-dmapi.html" diff --git a/providers/dns/liara/liara.toml b/providers/dns/liara/liara.toml index aaa4061f5..cf0e08b18 100644 --- a/providers/dns/liara/liara.toml +++ b/providers/dns/liara/liara.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns liara -d '*.example.com' -d example.com run [Configuration.Credentials] LIARA_API_KEY = "The API key" [Configuration.Additional] - LIARA_POLLING_INTERVAL = "Time between DNS propagation check" - LIARA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - LIARA_TTL = "The TTL of the TXT record used for the DNS challenge" - LIARA_HTTP_TIMEOUT = "API request timeout" + LIARA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + LIARA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + LIARA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)" + LIARA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://dns-service.iran.liara.ir/swagger" diff --git a/providers/dns/lightsail/lightsail.toml b/providers/dns/lightsail/lightsail.toml index 4ade894d1..47b212f62 100644 --- a/providers/dns/lightsail/lightsail.toml +++ b/providers/dns/lightsail/lightsail.toml @@ -52,8 +52,8 @@ Alternatively, you can also set the `Resource` to `*` (wildcard), which allow to DNS_ZONE = "Domain name of the DNS zone" [Configuration.Additional] AWS_SHARED_CREDENTIALS_FILE = "Managed by the AWS client. Shared credentials file." - LIGHTSAIL_POLLING_INTERVAL = "Time between DNS propagation check" - LIGHTSAIL_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" + LIGHTSAIL_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + LIGHTSAIL_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" [Links] GoClient = "https://github.com/aws/aws-sdk-go-v2" diff --git a/providers/dns/limacity/limacity.toml b/providers/dns/limacity/limacity.toml index c9bcaf16e..b9b9f0018 100644 --- a/providers/dns/limacity/limacity.toml +++ b/providers/dns/limacity/limacity.toml @@ -13,11 +13,11 @@ lego --email you@example.com --dns limacity -d '*.example.com' -d example.com ru [Configuration.Credentials] LIMACITY_API_KEY = "The API key" [Configuration.Additional] - LIMACITY_POLLING_INTERVAL = "Time between DNS propagation check" - LIMACITY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - LIMACITY_SEQUENCE_INTERVAL = "Time between sequential requests" - LIMACITY_TTL = "The TTL of the TXT record used for the DNS challenge" - LIMACITY_HTTP_TIMEOUT = "API request timeout" + LIMACITY_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 80)" + LIMACITY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 480)" + LIMACITY_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 90)" + LIMACITY_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + LIMACITY_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.lima-city.de/hilfe/lima-city-api" diff --git a/providers/dns/linode/linode.go b/providers/dns/linode/linode.go index 841e24c69..25e1b07d1 100644 --- a/providers/dns/linode/linode.go +++ b/providers/dns/linode/linode.go @@ -50,9 +50,9 @@ type Config struct { func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, minTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 0), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 15*time.Second), - HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 0), + HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), } } diff --git a/providers/dns/linode/linode.toml b/providers/dns/linode/linode.toml index 790a2238c..f046d3f9b 100644 --- a/providers/dns/linode/linode.toml +++ b/providers/dns/linode/linode.toml @@ -14,10 +14,10 @@ lego --email you@example.com --dns linode -d '*.example.com' -d example.com run [Configuration.Credentials] LINODE_TOKEN = "API token" [Configuration.Additional] - LINODE_POLLING_INTERVAL = "Time between DNS propagation check" - LINODE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - LINODE_TTL = "The TTL of the TXT record used for the DNS challenge" - LINODE_HTTP_TIMEOUT = "API request timeout" + LINODE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 15)" + LINODE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + LINODE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + LINODE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://developers.linode.com/api/v4" diff --git a/providers/dns/liquidweb/liquidweb.go b/providers/dns/liquidweb/liquidweb.go index 76f965123..2d0a46142 100644 --- a/providers/dns/liquidweb/liquidweb.go +++ b/providers/dns/liquidweb/liquidweb.go @@ -55,7 +55,7 @@ func NewDefaultConfig() *Config { BaseURL: defaultBaseURL, TTL: env.GetOneWithFallback(EnvTTL, 300, strconv.Atoi, altEnvName(EnvTTL)), PropagationTimeout: env.GetOneWithFallback(EnvPropagationTimeout, 2*time.Minute, env.ParseSecond, altEnvName(EnvPropagationTimeout)), - PollingInterval: env.GetOneWithFallback(EnvPollingInterval, 2*time.Second, env.ParseSecond, altEnvName(EnvPollingInterval)), + PollingInterval: env.GetOneWithFallback(EnvPollingInterval, dns01.DefaultPollingInterval, env.ParseSecond, altEnvName(EnvPollingInterval)), HTTPTimeout: env.GetOneWithFallback(EnvHTTPTimeout, 1*time.Minute, env.ParseSecond, altEnvName(EnvHTTPTimeout)), } } diff --git a/providers/dns/liquidweb/liquidweb.toml b/providers/dns/liquidweb/liquidweb.toml index 987b8027d..22789f41e 100644 --- a/providers/dns/liquidweb/liquidweb.toml +++ b/providers/dns/liquidweb/liquidweb.toml @@ -17,10 +17,10 @@ lego --email you@example.com --dns liquidweb -d '*.example.com' -d example.com r [Configuration.Additional] LWAPI_ZONE = "DNS Zone" LWAPI_URL = "Liquid Web API endpoint" - LWAPI_TTL = "The TTL of the TXT record used for the DNS challenge" - LWAPI_POLLING_INTERVAL = "Time between DNS propagation check" - LWAPI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - LWAPI_HTTP_TIMEOUT = "Maximum waiting time for the DNS records to be created (not verified)" + LWAPI_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + LWAPI_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + LWAPI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + LWAPI_HTTP_TIMEOUT = "API request timeout in seconds (Default: 60)" [Links] API = "https://api.liquidweb.com/docs/" diff --git a/providers/dns/loopia/loopia.go b/providers/dns/loopia/loopia.go index 34d4374fb..3ba6018b9 100644 --- a/providers/dns/loopia/loopia.go +++ b/providers/dns/loopia/loopia.go @@ -56,9 +56,9 @@ func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 40*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 60*time.Second), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPropagationTimeout), HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 60*time.Second), + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, time.Minute), }, } } diff --git a/providers/dns/loopia/loopia.toml b/providers/dns/loopia/loopia.toml index f1065b35e..4a127ec55 100644 --- a/providers/dns/loopia/loopia.toml +++ b/providers/dns/loopia/loopia.toml @@ -29,10 +29,10 @@ It needs to have the following permissions: LOOPIA_API_PASSWORD = "API password" [Configuration.Additional] LOOPIA_API_URL = "API endpoint. Ex: https://api.loopia.se/RPCSERV or https://api.loopia.rs/RPCSERV" - LOOPIA_POLLING_INTERVAL = "Time between DNS propagation check" - LOOPIA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - LOOPIA_TTL = "The TTL of the TXT record used for the DNS challenge" - LOOPIA_HTTP_TIMEOUT = "API request timeout" + LOOPIA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2400)" + LOOPIA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + LOOPIA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + LOOPIA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 60)" [Links] API = "https://www.loopia.com/api" diff --git a/providers/dns/luadns/luadns.go b/providers/dns/luadns/luadns.go index ef0a9b7d6..026a0da70 100644 --- a/providers/dns/luadns/luadns.go +++ b/providers/dns/luadns/luadns.go @@ -48,7 +48,7 @@ func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), }, diff --git a/providers/dns/luadns/luadns.toml b/providers/dns/luadns/luadns.toml index b55751f55..c80929c21 100644 --- a/providers/dns/luadns/luadns.toml +++ b/providers/dns/luadns/luadns.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns luadns -d '*.example.com' -d example.com run LUADNS_API_USERNAME = "Username (your email)" LUADNS_API_TOKEN = "API token" [Configuration.Additional] - LUADNS_POLLING_INTERVAL = "Time between DNS propagation check" - LUADNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - LUADNS_TTL = "The TTL of the TXT record used for the DNS challenge" - LUADNS_HTTP_TIMEOUT = "API request timeout" + LUADNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + LUADNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + LUADNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + LUADNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://luadns.com/api.html" diff --git a/providers/dns/mailinabox/mailinabox.toml b/providers/dns/mailinabox/mailinabox.toml index 8ee282396..4b30dd9e2 100644 --- a/providers/dns/mailinabox/mailinabox.toml +++ b/providers/dns/mailinabox/mailinabox.toml @@ -17,8 +17,8 @@ lego --email you@example.com --dns mailinabox -d '*.example.com' -d example.com MAILINABOX_PASSWORD = "User password" MAILINABOX_BASE_URL = "Base API URL (ex: https://box.example.com)" [Configuration.Additional] - MAILINABOX_POLLING_INTERVAL = "Time between DNS propagation check" - MAILINABOX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" + MAILINABOX_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 4)" + MAILINABOX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" [Links] API = "https://mailinabox.email/api-docs.html" diff --git a/providers/dns/manageengine/manageengine.toml b/providers/dns/manageengine/manageengine.toml index dea92b3e6..7708fa74f 100644 --- a/providers/dns/manageengine/manageengine.toml +++ b/providers/dns/manageengine/manageengine.toml @@ -15,10 +15,9 @@ lego --email you@example.com --dns manageengine -d '*.example.com' -d example.co MANAGEENGINE_CLIENT_ID = "Client ID" MANAGEENGINE_CLIENT_SECRET = "Client Secret" [Configuration.Additional] - MANAGEENGINE_POLLING_INTERVAL = "Time between DNS propagation check" - MANAGEENGINE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - MANAGEENGINE_TTL = "The TTL of the TXT record used for the DNS challenge" - MANAGEENGINE_HTTP_TIMEOUT = "API request timeout" + MANAGEENGINE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + MANAGEENGINE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + MANAGEENGINE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" [Links] API = "https://pitstop.manageengine.com/portal/en/kb/articles/manageengine-clouddns-rest-api-documentation" diff --git a/providers/dns/metaname/metaname.toml b/providers/dns/metaname/metaname.toml index 142f06639..4a147d043 100644 --- a/providers/dns/metaname/metaname.toml +++ b/providers/dns/metaname/metaname.toml @@ -15,9 +15,9 @@ lego --email you@example.com --dns metaname -d '*.example.com' -d example.com ru METANAME_ACCOUNT_REFERENCE = "The four-digit reference of a Metaname account" METANAME_API_KEY = "API Key" [Configuration.Additional] - METANAME_POLLING_INTERVAL = "Time between DNS propagation check" - METANAME_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - METANAME_TTL = "The TTL of the TXT record used for the DNS challenge" + METANAME_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + METANAME_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + METANAME_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" [Links] API = "https://metaname.net/api/1.1/doc" diff --git a/providers/dns/mijnhost/mijnhost.toml b/providers/dns/mijnhost/mijnhost.toml index 7cea55a18..00152e132 100644 --- a/providers/dns/mijnhost/mijnhost.toml +++ b/providers/dns/mijnhost/mijnhost.toml @@ -13,11 +13,11 @@ lego --email you@example.com --dns mijnhost -d '*.example.com' -d example.com ru [Configuration.Credentials] MIJNHOST_API_KEY = "The API key" [Configuration.Additional] - MIJNHOST_POLLING_INTERVAL = "Time between DNS propagation check" - MIJNHOST_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - MIJNHOST_SEQUENCE_INTERVAL = "Time between sequential requests" - MIJNHOST_TTL = "The TTL of the TXT record used for the DNS challenge" - MIJNHOST_HTTP_TIMEOUT = "API request timeout" + MIJNHOST_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + MIJNHOST_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + MIJNHOST_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" + MIJNHOST_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + MIJNHOST_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://mijn.host/api/doc/" diff --git a/providers/dns/mittwald/mittwald.toml b/providers/dns/mittwald/mittwald.toml index 7df9797b6..937b9c172 100644 --- a/providers/dns/mittwald/mittwald.toml +++ b/providers/dns/mittwald/mittwald.toml @@ -13,11 +13,11 @@ lego --email you@example.com --dns mittwald -d '*.example.com' -d example.com ru [Configuration.Credentials] MITTWALD_TOKEN = "API token" [Configuration.Additional] - MITTWALD_POLLING_INTERVAL = "Time between DNS propagation check" - MITTWALD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - MITTWALD_TTL = "The TTL of the TXT record used for the DNS challenge" - MITTWALD_HTTP_TIMEOUT = "API request timeout" - MITTWALD_SEQUENCE_INTERVAL = "Time between sequential requests" + MITTWALD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + MITTWALD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + MITTWALD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + MITTWALD_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 120)" + MITTWALD_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://api.mittwald.de/v2/docs/" diff --git a/providers/dns/mydnsjp/mydnsjp.go b/providers/dns/mydnsjp/mydnsjp.go index ec1aca357..d0565e8bd 100644 --- a/providers/dns/mydnsjp/mydnsjp.go +++ b/providers/dns/mydnsjp/mydnsjp.go @@ -41,7 +41,7 @@ type Config struct { func NewDefaultConfig() *Config { return &Config{ PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), }, diff --git a/providers/dns/mydnsjp/mydnsjp.toml b/providers/dns/mydnsjp/mydnsjp.toml index d462e9537..ab842e37f 100644 --- a/providers/dns/mydnsjp/mydnsjp.toml +++ b/providers/dns/mydnsjp/mydnsjp.toml @@ -15,10 +15,9 @@ lego --email you@example.com --dns mydnsjp -d '*.example.com' -d example.com run MYDNSJP_MASTER_ID = "Master ID" MYDNSJP_PASSWORD = "Password" [Configuration.Additional] - MYDNSJP_POLLING_INTERVAL = "Time between DNS propagation check" - MYDNSJP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - MYDNSJP_TTL = "The TTL of the TXT record used for the DNS challenge" - MYDNSJP_HTTP_TIMEOUT = "API request timeout" + MYDNSJP_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + MYDNSJP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + MYDNSJP_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.mydns.jp/?MENU=030" diff --git a/providers/dns/mythicbeasts/mythicbeasts.toml b/providers/dns/mythicbeasts/mythicbeasts.toml index 86d69d017..011abba1f 100644 --- a/providers/dns/mythicbeasts/mythicbeasts.toml +++ b/providers/dns/mythicbeasts/mythicbeasts.toml @@ -23,10 +23,10 @@ Your API key name is not needed to operate lego. [Configuration.Additional] MYTHICBEASTS_API_ENDPOINT = "The endpoint for the API (must implement v2)" MYTHICBEASTS_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" + MYTHICBEASTS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + MYTHICBEASTS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + MYTHICBEASTS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + MYTHICBEASTS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://www.mythic-beasts.com/support/api/dnsv2" diff --git a/providers/dns/namecheap/namecheap.go b/providers/dns/namecheap/namecheap.go index f410fa5a3..48b9492c4 100644 --- a/providers/dns/namecheap/namecheap.go +++ b/providers/dns/namecheap/namecheap.go @@ -72,10 +72,10 @@ func NewDefaultConfig() *Config { BaseURL: baseURL, Debug: env.GetOrDefaultBool(EnvDebug, false), TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 60*time.Minute), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, time.Hour), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 15*time.Second), HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 60*time.Second), + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, time.Minute), }, } } diff --git a/providers/dns/namecheap/namecheap.toml b/providers/dns/namecheap/namecheap.toml index ef2ef53c4..3a5be870c 100644 --- a/providers/dns/namecheap/namecheap.toml +++ b/providers/dns/namecheap/namecheap.toml @@ -22,10 +22,10 @@ lego --email you@example.com --dns namecheap -d '*.example.com' -d example.com r NAMECHEAP_API_USER = "API user" NAMECHEAP_API_KEY = "API key" [Configuration.Additional] - NAMECHEAP_POLLING_INTERVAL = "Time between DNS propagation check" - NAMECHEAP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - NAMECHEAP_TTL = "The TTL of the TXT record used for the DNS challenge" - NAMECHEAP_HTTP_TIMEOUT = "API request timeout" + NAMECHEAP_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 15)" + NAMECHEAP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 3600)" + NAMECHEAP_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + NAMECHEAP_HTTP_TIMEOUT = "API request timeout in seconds (Default: 60)" NAMECHEAP_SANDBOX = "Activate the sandbox (boolean)" [Links] diff --git a/providers/dns/namedotcom/namedotcom.toml b/providers/dns/namedotcom/namedotcom.toml index 768164cf8..e6de796d1 100644 --- a/providers/dns/namedotcom/namedotcom.toml +++ b/providers/dns/namedotcom/namedotcom.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns namedotcom -d '*.example.com' -d example.com NAMECOM_USERNAME = "Username" NAMECOM_API_TOKEN = "API token" [Configuration.Additional] - NAMECOM_POLLING_INTERVAL = "Time between DNS propagation check" - NAMECOM_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - NAMECOM_TTL = "The TTL of the TXT record used for the DNS challenge" - NAMECOM_HTTP_TIMEOUT = "API request timeout" + NAMECOM_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 20)" + NAMECOM_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 900)" + NAMECOM_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + NAMECOM_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://www.name.com/api-docs/DNS" diff --git a/providers/dns/namesilo/namesilo.toml b/providers/dns/namesilo/namesilo.toml index 991e78fcc..bab7905bf 100644 --- a/providers/dns/namesilo/namesilo.toml +++ b/providers/dns/namesilo/namesilo.toml @@ -13,9 +13,9 @@ lego --email you@example.com --dns namesilo -d '*.example.com' -d example.com ru [Configuration.Credentials] NAMESILO_API_KEY = "Client ID" [Configuration.Additional] - NAMESILO_POLLING_INTERVAL = "Time between DNS propagation check" - NAMESILO_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation, it is better to set larger than 15m" - NAMESILO_TTL = "The TTL of the TXT record used for the DNS challenge, should be in [3600, 2592000]" + NAMESILO_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + NAMESILO_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60), it is better to set larger than 15 minutes" + NAMESILO_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600), should be in [3600, 2592000]" [Links] API = "https://www.namesilo.com/api_reference.php" diff --git a/providers/dns/nearlyfreespeech/nearlyfreespeech.toml b/providers/dns/nearlyfreespeech/nearlyfreespeech.toml index 985df6cba..80d4fd6bc 100644 --- a/providers/dns/nearlyfreespeech/nearlyfreespeech.toml +++ b/providers/dns/nearlyfreespeech/nearlyfreespeech.toml @@ -15,11 +15,11 @@ lego --email you@example.com --dns nearlyfreespeech -d '*.example.com' -d exampl NEARLYFREESPEECH_API_KEY = "API Key for API requests" NEARLYFREESPEECH_LOGIN = "Username for API requests" [Configuration.Additional] - NEARLYFREESPEECH_POLLING_INTERVAL = "Time between DNS propagation check" - NEARLYFREESPEECH_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - NEARLYFREESPEECH_TTL = "The TTL of the TXT record used for the DNS challenge" - NEARLYFREESPEECH_HTTP_TIMEOUT = "API request timeout" - NEARLYFREESPEECH_SEQUENCE_INTERVAL = "Time between sequential requests" + NEARLYFREESPEECH_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + NEARLYFREESPEECH_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + NEARLYFREESPEECH_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)" + NEARLYFREESPEECH_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" + NEARLYFREESPEECH_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://members.nearlyfreespeech.net/wiki/API/Reference" diff --git a/providers/dns/netcup/netcup.toml b/providers/dns/netcup/netcup.toml index 67c52cf04..0df09b0df 100644 --- a/providers/dns/netcup/netcup.toml +++ b/providers/dns/netcup/netcup.toml @@ -17,9 +17,9 @@ lego --email you@example.com --dns netcup -d '*.example.com' -d example.com run NETCUP_API_KEY = "API key" NETCUP_API_PASSWORD = "API password" [Configuration.Additional] - NETCUP_POLLING_INTERVAL = "Time between DNS propagation check" - NETCUP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - NETCUP_HTTP_TIMEOUT = "API request timeout" + NETCUP_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 30)" + NETCUP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 900)" + NETCUP_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://www.netcup-wiki.de/wiki/DNS_API" diff --git a/providers/dns/netlify/netlify.toml b/providers/dns/netlify/netlify.toml index 1191c6beb..c5cb670f9 100644 --- a/providers/dns/netlify/netlify.toml +++ b/providers/dns/netlify/netlify.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns netlify -d '*.example.com' -d example.com run [Configuration.Credentials] NETLIFY_TOKEN = "Token" [Configuration.Additional] - NETLIFY_POLLING_INTERVAL = "Time between DNS propagation check" - NETLIFY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - NETLIFY_TTL = "The TTL of the TXT record used for the DNS challenge" - NETLIFY_HTTP_TIMEOUT = "API request timeout" + NETLIFY_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + NETLIFY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + NETLIFY_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + NETLIFY_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://open-api.netlify.com/" diff --git a/providers/dns/nicmanager/nicmanager.toml b/providers/dns/nicmanager/nicmanager.toml index 7be44deb8..cfe41455c 100644 --- a/providers/dns/nicmanager/nicmanager.toml +++ b/providers/dns/nicmanager/nicmanager.toml @@ -43,10 +43,10 @@ Optionally if TOTP is configured for your account, set `NICMANAGER_API_OTP`. [Configuration.Additional] NICMANAGER_API_OTP = "TOTP Secret (optional)" NICMANAGER_API_MODE = "mode: 'anycast' or 'zone' (default: 'anycast')" - NICMANAGER_POLLING_INTERVAL = "Time between DNS propagation check" - NICMANAGER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - NICMANAGER_TTL = "The TTL of the TXT record used for the DNS challenge" - NICMANAGER_HTTP_TIMEOUT = "API request timeout" + NICMANAGER_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + NICMANAGER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" + NICMANAGER_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 900)" + NICMANAGER_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://api.nicmanager.com/docs/v1/" diff --git a/providers/dns/nifcloud/nifcloud.toml b/providers/dns/nifcloud/nifcloud.toml index 9966ce882..b692bb9d3 100644 --- a/providers/dns/nifcloud/nifcloud.toml +++ b/providers/dns/nifcloud/nifcloud.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns nifcloud -d '*.example.com' -d example.com ru NIFCLOUD_ACCESS_KEY_ID = "Access key" NIFCLOUD_SECRET_ACCESS_KEY = "Secret access key" [Configuration.Additional] - NIFCLOUD_POLLING_INTERVAL = "Time between DNS propagation check" - NIFCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - NIFCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge" - NIFCLOUD_HTTP_TIMEOUT = "API request timeout" + NIFCLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + NIFCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + NIFCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + NIFCLOUD_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://mbaas.nifcloud.com/doc/current/rest/common/format.html" diff --git a/providers/dns/njalla/njalla.toml b/providers/dns/njalla/njalla.toml index a7e46c02d..ef1fe158e 100644 --- a/providers/dns/njalla/njalla.toml +++ b/providers/dns/njalla/njalla.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns njalla -d '*.example.com' -d example.com run [Configuration.Credentials] NJALLA_TOKEN = "API token" [Configuration.Additional] - NJALLA_POLLING_INTERVAL = "Time between DNS propagation check" - NJALLA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - NJALLA_TTL = "The TTL of the TXT record used for the DNS challenge" - NJALLA_HTTP_TIMEOUT = "API request timeout" + NJALLA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + NJALLA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + NJALLA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + NJALLA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://njal.la/api/" diff --git a/providers/dns/nodion/nodion.toml b/providers/dns/nodion/nodion.toml index 5bf2e1df1..0888f96c3 100644 --- a/providers/dns/nodion/nodion.toml +++ b/providers/dns/nodion/nodion.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns nodion -d '*.example.com' -d example.com run [Configuration.Credentials] NODION_API_TOKEN = "The API token" [Configuration.Additional] - NODION_POLLING_INTERVAL = "Time between DNS propagation check" - NODION_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - NODION_TTL = "The TTL of the TXT record used for the DNS challenge" - NODION_HTTP_TIMEOUT = "API request timeout" + NODION_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + NODION_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + NODION_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + NODION_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.nodion.com/en/docs/dns/api/" diff --git a/providers/dns/ns1/ns1.toml b/providers/dns/ns1/ns1.toml index 9aeb0841e..2a6b10deb 100644 --- a/providers/dns/ns1/ns1.toml +++ b/providers/dns/ns1/ns1.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns ns1 -d '*.example.com' -d example.com run [Configuration.Credentials] NS1_API_KEY = "API key" [Configuration.Additional] - NS1_POLLING_INTERVAL = "Time between DNS propagation check" - NS1_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - NS1_TTL = "The TTL of the TXT record used for the DNS challenge" - NS1_HTTP_TIMEOUT = "API request timeout" + NS1_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + NS1_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + NS1_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + NS1_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://ns1.com/api" diff --git a/providers/dns/oraclecloud/oraclecloud.go b/providers/dns/oraclecloud/oraclecloud.go index 535c691ba..e41227da0 100644 --- a/providers/dns/oraclecloud/oraclecloud.go +++ b/providers/dns/oraclecloud/oraclecloud.go @@ -53,7 +53,7 @@ func NewDefaultConfig() *Config { PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 60*time.Second), + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, time.Minute), }, } } diff --git a/providers/dns/oraclecloud/oraclecloud.toml b/providers/dns/oraclecloud/oraclecloud.toml index 70b776554..8c756a374 100644 --- a/providers/dns/oraclecloud/oraclecloud.toml +++ b/providers/dns/oraclecloud/oraclecloud.toml @@ -25,9 +25,10 @@ lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com OCI_REGION = "Region" OCI_COMPARTMENT_OCID = "Compartment OCID" [Configuration.Additional] - OCI_POLLING_INTERVAL = "Time between DNS propagation check" - OCI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - OCI_TTL = "The TTL of the TXT record used for the DNS challenge" + OCI_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + OCI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + OCI_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + OCI_HTTP_TIMEOUT = "API request timeout in seconds (Default: 60)" [Links] API = "https://docs.cloud.oracle.com/iaas/Content/DNS/Concepts/dnszonemanagement.htm" diff --git a/providers/dns/otc/otc.toml b/providers/dns/otc/otc.toml index e3c60158c..cb1910d26 100644 --- a/providers/dns/otc/otc.toml +++ b/providers/dns/otc/otc.toml @@ -14,11 +14,11 @@ Example = '''''' OTC_DOMAIN_NAME = "Domain name" OTC_IDENTITY_ENDPOINT = "Identity endpoint URL" [Configuration.Additional] - OTC_POLLING_INTERVAL = "Time between DNS propagation check" - OTC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - OTC_SEQUENCE_INTERVAL = "Time between sequential requests" - OTC_TTL = "The TTL of the TXT record used for the DNS challenge" - OTC_HTTP_TIMEOUT = "API request timeout" + OTC_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + OTC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + OTC_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" + OTC_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + OTC_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://docs.otc.t-systems.com/domain-name-service/api-ref/index.html" diff --git a/providers/dns/ovh/ovh.toml b/providers/dns/ovh/ovh.toml index cbdcb43ae..95162185b 100644 --- a/providers/dns/ovh/ovh.toml +++ b/providers/dns/ovh/ovh.toml @@ -76,10 +76,10 @@ Both authentication methods cannot be used at the same time. OVH_CLIENT_SECRET = "Client secret (OAuth2)" OVH_ACCESS_TOKEN = "Access token" [Configuration.Additional] - OVH_POLLING_INTERVAL = "Time between DNS propagation check" - OVH_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - OVH_TTL = "The TTL of the TXT record used for the DNS challenge" - OVH_HTTP_TIMEOUT = "API request timeout" + OVH_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + OVH_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + OVH_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + OVH_HTTP_TIMEOUT = "API request timeout in seconds (Default: 180)" [Links] API = "https://eu.api.ovh.com/" diff --git a/providers/dns/pdns/pdns.toml b/providers/dns/pdns/pdns.toml index 81158c444..53b5547b9 100644 --- a/providers/dns/pdns/pdns.toml +++ b/providers/dns/pdns/pdns.toml @@ -28,10 +28,10 @@ PowerDNS Notes: [Configuration.Additional] PDNS_SERVER_NAME = "Name of the server in the URL, 'localhost' by default" PDNS_API_VERSION = "Skip API version autodetection and use the provided version number." - PDNS_POLLING_INTERVAL = "Time between DNS propagation check" - PDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - PDNS_TTL = "The TTL of the TXT record used for the DNS challenge" - PDNS_HTTP_TIMEOUT = "API request timeout" + PDNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + PDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + PDNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + PDNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://doc.powerdns.com/md/httpapi/README/" diff --git a/providers/dns/plesk/plesk.toml b/providers/dns/plesk/plesk.toml index 3a67065d6..5fb4ce073 100644 --- a/providers/dns/plesk/plesk.toml +++ b/providers/dns/plesk/plesk.toml @@ -17,10 +17,10 @@ lego --email you@example.com --dns plesk -d '*.example.com' -d example.com run PLESK_USERNAME = "API username" PLESK_PASSWORD = "API password" [Configuration.Additional] - PLESK_POLLING_INTERVAL = "Time between DNS propagation check" - PLESK_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - PLESK_TTL = "The TTL of the TXT record used for the DNS challenge" - PLESK_HTTP_TIMEOUT = "API request timeout" + PLESK_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + PLESK_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + PLESK_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + PLESK_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference.28784/" diff --git a/providers/dns/porkbun/porkbun.toml b/providers/dns/porkbun/porkbun.toml index 91b0b1329..610702cee 100644 --- a/providers/dns/porkbun/porkbun.toml +++ b/providers/dns/porkbun/porkbun.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns porkbun -d '*.example.com' -d example.com run PORKBUN_SECRET_API_KEY = "secret API key" PORKBUN_API_KEY = "API key" [Configuration.Additional] - PORKBUN_POLLING_INTERVAL = "Time between DNS propagation check" - PORKBUN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - PORKBUN_TTL = "The TTL of the TXT record used for the DNS challenge" - PORKBUN_HTTP_TIMEOUT = "API request timeout" + PORKBUN_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + PORKBUN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 600)" + PORKBUN_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + PORKBUN_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://porkbun.com/api/json/v3/documentation" diff --git a/providers/dns/rackspace/rackspace.toml b/providers/dns/rackspace/rackspace.toml index ae0b0fca4..7ca2c3b7a 100644 --- a/providers/dns/rackspace/rackspace.toml +++ b/providers/dns/rackspace/rackspace.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns rackspace -d '*.example.com' -d example.com r RACKSPACE_USER = "API user" RACKSPACE_API_KEY = "API key" [Configuration.Additional] - RACKSPACE_POLLING_INTERVAL = "Time between DNS propagation check" - RACKSPACE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - RACKSPACE_TTL = "The TTL of the TXT record used for the DNS challenge" - RACKSPACE_HTTP_TIMEOUT = "API request timeout" + RACKSPACE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 3)" + RACKSPACE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + RACKSPACE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + RACKSPACE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://developer.rackspace.com/docs/cloud-dns/v1/" diff --git a/providers/dns/rainyun/rainyun.toml b/providers/dns/rainyun/rainyun.toml index ea12b475f..cca16cffe 100644 --- a/providers/dns/rainyun/rainyun.toml +++ b/providers/dns/rainyun/rainyun.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns rainyun -d '*.example.com' -d example.com run [Configuration.Credentials] RAINYUN_API_KEY = "API key" [Configuration.Additional] - RAINYUN_POLLING_INTERVAL = "Time between DNS propagation check" - RAINYUN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - RAINYUN_TTL = "The TTL of the TXT record used for the DNS challenge" - RAINYUN_HTTP_TIMEOUT = "API request timeout" + RAINYUN_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + RAINYUN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + RAINYUN_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + RAINYUN_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.apifox.cn/apidoc/shared-a4595cc8-44c5-4678-a2a3-eed7738dab03/api-151416609" diff --git a/providers/dns/rcodezero/rcodezero.go b/providers/dns/rcodezero/rcodezero.go index c88caefe4..93f3e957a 100644 --- a/providers/dns/rcodezero/rcodezero.go +++ b/providers/dns/rcodezero/rcodezero.go @@ -41,7 +41,7 @@ type Config struct { func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 240*time.Second), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 4*time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), diff --git a/providers/dns/rcodezero/rcodezero.toml b/providers/dns/rcodezero/rcodezero.toml index 7ab451e5f..bba5588da 100644 --- a/providers/dns/rcodezero/rcodezero.toml +++ b/providers/dns/rcodezero/rcodezero.toml @@ -23,10 +23,10 @@ RcodeZero is an Anycast Network so the distribution of the DNS01-Challenge can t [Configuration.Credentials] RCODEZERO_API_TOKEN = "API token" [Configuration.Additional] - RCODEZERO_POLLING_INTERVAL = "Time between DNS propagation check" - RCODEZERO_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - RCODEZERO_TTL = "The TTL of the TXT record used for the DNS challenge" - RCODEZERO_HTTP_TIMEOUT = "API request timeout" + RCODEZERO_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + RCODEZERO_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 240)" + RCODEZERO_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + RCODEZERO_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] # Note: the API endpoint used inside the client is not documented. diff --git a/providers/dns/regfish/regfish.toml b/providers/dns/regfish/regfish.toml index fbc4bdd70..9869ed96e 100644 --- a/providers/dns/regfish/regfish.toml +++ b/providers/dns/regfish/regfish.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns regfish -d '*.example.com' -d example.com run [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" + REGFISH_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + REGFISH_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + REGFISH_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + REGFISH_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://regfish.readme.io/" diff --git a/providers/dns/regru/regru.toml b/providers/dns/regru/regru.toml index 16d8e4e3a..2ccf3a58f 100644 --- a/providers/dns/regru/regru.toml +++ b/providers/dns/regru/regru.toml @@ -17,10 +17,10 @@ lego --email you@example.com --dns regru -d '*.example.com' -d example.com run [Configuration.Additional] REGRU_TLS_CERT = "authentication certificate" REGRU_TLS_KEY = "authentication private key" - REGRU_POLLING_INTERVAL = "Time between DNS propagation check" - REGRU_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - REGRU_TTL = "The TTL of the TXT record used for the DNS challenge" - REGRU_HTTP_TIMEOUT = "API request timeout" + REGRU_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + REGRU_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + REGRU_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + REGRU_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.reg.ru/support/help/api2" diff --git a/providers/dns/rfc2136/rfc2136.go b/providers/dns/rfc2136/rfc2136.go index d533f4d16..6b5c47072 100644 --- a/providers/dns/rfc2136/rfc2136.go +++ b/providers/dns/rfc2136/rfc2136.go @@ -58,8 +58,8 @@ func NewDefaultConfig() *Config { return &Config{ TSIGAlgorithm: env.GetOrDefaultString(EnvTSIGAlgorithm, dns.HmacSHA1), TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, env.GetOrDefaultSecond("RFC2136_TIMEOUT", 60*time.Second)), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, env.GetOrDefaultSecond("RFC2136_TIMEOUT", dns01.DefaultPropagationTimeout)), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), SequenceInterval: env.GetOrDefaultSecond(EnvSequenceInterval, dns01.DefaultPropagationTimeout), DNSTimeout: env.GetOrDefaultSecond(EnvDNSTimeout, 10*time.Second), } diff --git a/providers/dns/rfc2136/rfc2136.toml b/providers/dns/rfc2136/rfc2136.toml index df313fde7..9243440a4 100644 --- a/providers/dns/rfc2136/rfc2136.toml +++ b/providers/dns/rfc2136/rfc2136.toml @@ -28,11 +28,11 @@ lego --email you@example.com --dns rfc2136 -d '*.example.com' -d example.com run RFC2136_NAMESERVER = 'Network address in the form "host" or "host:port"' [Configuration.Additional] RFC2136_TSIG_FILE = "Path to a key file generated by tsig-keygen" - RFC2136_POLLING_INTERVAL = "Time between DNS propagation check" - RFC2136_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - RFC2136_TTL = "The TTL of the TXT record used for the DNS challenge" - RFC2136_DNS_TIMEOUT = "API request timeout" - RFC2136_SEQUENCE_INTERVAL = "Time between sequential requests" + RFC2136_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + RFC2136_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + RFC2136_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + RFC2136_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" + RFC2136_DNS_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://www.rfc-editor.org/rfc/rfc2136.html" diff --git a/providers/dns/rimuhosting/rimuhosting.toml b/providers/dns/rimuhosting/rimuhosting.toml index 4b4fa5ea7..0a4f983e2 100644 --- a/providers/dns/rimuhosting/rimuhosting.toml +++ b/providers/dns/rimuhosting/rimuhosting.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns rimuhosting -d '*.example.com' -d example.com [Configuration.Credentials] RIMUHOSTING_API_KEY = "User API key" [Configuration.Additional] - RIMUHOSTING_POLLING_INTERVAL = "Time between DNS propagation check" - RIMUHOSTING_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - RIMUHOSTING_TTL = "The TTL of the TXT record used for the DNS challenge" - RIMUHOSTING_HTTP_TIMEOUT = "API request timeout" + RIMUHOSTING_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + RIMUHOSTING_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + RIMUHOSTING_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)" + RIMUHOSTING_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://rimuhosting.com/dns/dyndns.jsp" diff --git a/providers/dns/route53/route53.toml b/providers/dns/route53/route53.toml index 53c1d61d1..0004e9546 100644 --- a/providers/dns/route53/route53.toml +++ b/providers/dns/route53/route53.toml @@ -135,9 +135,9 @@ Replace `Z11111112222222333333` with your hosted zone ID and `example.com` with [Configuration.Additional] AWS_SHARED_CREDENTIALS_FILE = "Managed by the AWS client. Shared credentials file." AWS_MAX_RETRIES = "The number of maximum returns the service will use to make an individual API request" - AWS_POLLING_INTERVAL = "Time between DNS propagation check" - AWS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - AWS_TTL = "The TTL of the TXT record used for the DNS challenge" + AWS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 4)" + AWS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + AWS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 10)" [Links] API = "https://docs.aws.amazon.com/Route53/latest/APIReference/API_Operations_Amazon_Route_53.html" diff --git a/providers/dns/safedns/safedns.toml b/providers/dns/safedns/safedns.toml index 11b2a289c..dcc7bc90e 100644 --- a/providers/dns/safedns/safedns.toml +++ b/providers/dns/safedns/safedns.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns safedns -d '*.example.com' -d example.com run [Configuration.Credentials] SAFEDNS_AUTH_TOKEN = "Authentication token" [Configuration.Additional] - SAFEDNS_POLLING_INTERVAL = "Time between DNS propagation check" - SAFEDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - SAFEDNS_TTL = "The TTL of the TXT record used for the DNS challenge" - SAFEDNS_HTTP_TIMEOUT = "API request timeout" + SAFEDNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + SAFEDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + SAFEDNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + SAFEDNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://developers.ukfast.io/documentation/safedns" diff --git a/providers/dns/sakuracloud/sakuracloud.toml b/providers/dns/sakuracloud/sakuracloud.toml index f86f215e5..f754e0c89 100644 --- a/providers/dns/sakuracloud/sakuracloud.toml +++ b/providers/dns/sakuracloud/sakuracloud.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns sakuracloud -d '*.example.com' -d example.com SAKURACLOUD_ACCESS_TOKEN = "Access token" SAKURACLOUD_ACCESS_TOKEN_SECRET = "Access token secret" [Configuration.Additional] - SAKURACLOUD_POLLING_INTERVAL = "Time between DNS propagation check" - SAKURACLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - SAKURACLOUD_TTL = "The TTL of the TXT record used for the DNS challenge" - SAKURACLOUD_HTTP_TIMEOUT = "API request timeout" + SAKURACLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + SAKURACLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + SAKURACLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + SAKURACLOUD_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://developer.sakura.ad.jp/cloud/api/1.1/" diff --git a/providers/dns/scaleway/scaleway.toml b/providers/dns/scaleway/scaleway.toml index a13a34d22..21839e061 100644 --- a/providers/dns/scaleway/scaleway.toml +++ b/providers/dns/scaleway/scaleway.toml @@ -15,9 +15,9 @@ lego --email you@example.com --dns scaleway -d '*.example.com' -d example.com ru SCW_PROJECT_ID = "Project to use (optional)" [Configuration.Additional] SCW_ACCESS_KEY = "Access key" - SCW_POLLING_INTERVAL = "Time between DNS propagation check" - SCW_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - SCW_TTL = "The TTL of the TXT record used for the DNS challenge" + SCW_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + SCW_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + SCW_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" [Links] API = "https://developers.scaleway.com/en/products/domain/dns/api/" diff --git a/providers/dns/selectel/selectel.go b/providers/dns/selectel/selectel.go index 744523230..c5da2215f 100644 --- a/providers/dns/selectel/selectel.go +++ b/providers/dns/selectel/selectel.go @@ -50,7 +50,7 @@ func NewDefaultConfig() *Config { BaseURL: env.GetOrDefaultString(EnvBaseURL, selectel.DefaultSelectelBaseURL), TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), }, diff --git a/providers/dns/selectel/selectel.toml b/providers/dns/selectel/selectel.toml index a37565d4d..f9add7ea9 100644 --- a/providers/dns/selectel/selectel.toml +++ b/providers/dns/selectel/selectel.toml @@ -14,10 +14,10 @@ lego --email you@example.com --dns selectel -d '*.example.com' -d example.com ru SELECTEL_API_TOKEN = "API token" [Configuration.Additional] SELECTEL_BASE_URL = "API endpoint URL" - SELECTEL_POLLING_INTERVAL = "Time between DNS propagation check" - SELECTEL_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - SELECTEL_TTL = "The TTL of the TXT record used for the DNS challenge" - SELECTEL_HTTP_TIMEOUT = "API request timeout" + SELECTEL_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + SELECTEL_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + SELECTEL_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + SELECTEL_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://kb.selectel.com/23136054.html" diff --git a/providers/dns/selectelv2/selectelv2.toml b/providers/dns/selectelv2/selectelv2.toml index 4c06949f4..4993cb0f8 100644 --- a/providers/dns/selectelv2/selectelv2.toml +++ b/providers/dns/selectelv2/selectelv2.toml @@ -20,10 +20,10 @@ lego --email you@example.com --dns selectelv2 -d '*.example.com' -d example.com SELECTELV2_PROJECT_ID = "Cloud project ID (UUID)" [Configuration.Additional] SELECTELV2_BASE_URL = "API endpoint URL" - SELECTELV2_POLLING_INTERVAL = "Time between DNS propagation check" - SELECTELV2_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - SELECTELV2_TTL = "The TTL of the TXT record used for the DNS challenge" - SELECTELV2_HTTP_TIMEOUT = "API request timeout" + SELECTELV2_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 5)" + SELECTELV2_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + SELECTELV2_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + SELECTELV2_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://developers.selectel.ru/docs/cloud-services/dns_api/dns_api_actual/" diff --git a/providers/dns/selfhostde/selfhostde.toml b/providers/dns/selfhostde/selfhostde.toml index eba96fce2..619f2cae8 100644 --- a/providers/dns/selfhostde/selfhostde.toml +++ b/providers/dns/selfhostde/selfhostde.toml @@ -48,7 +48,7 @@ The resulting environment variable would then be: `SELFHOSTDE_RECORDS_MAPPING=my SELFHOSTDE_PASSWORD = "Password" SELFHOSTDE_RECORDS_MAPPING = "Record IDs mapping with domains (ex: example.com:123:456,example.org:789,foo.example.com:147)" [Configuration.Additional] - SELFHOSTDE_POLLING_INTERVAL = "Time between DNS propagation check" - SELFHOSTDE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - SELFHOSTDE_TTL = "The TTL of the TXT record used for the DNS challenge" - SELFHOSTDE_HTTP_TIMEOUT = "API request timeout" + SELFHOSTDE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 30)" + SELFHOSTDE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 240)" + SELFHOSTDE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + SELFHOSTDE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" diff --git a/providers/dns/servercow/servercow.go b/providers/dns/servercow/servercow.go index c0c1662f6..56f89f900 100644 --- a/providers/dns/servercow/servercow.go +++ b/providers/dns/servercow/servercow.go @@ -44,7 +44,7 @@ type Config struct { // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, 120), + TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ diff --git a/providers/dns/servercow/servercow.toml b/providers/dns/servercow/servercow.toml index e9ec36be9..655257b84 100644 --- a/providers/dns/servercow/servercow.toml +++ b/providers/dns/servercow/servercow.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns servercow -d '*.example.com' -d example.com r SERVERCOW_USERNAME = "API username" SERVERCOW_PASSWORD = "API password" [Configuration.Additional] - SERVERCOW_POLLING_INTERVAL = "Time between DNS propagation check" - SERVERCOW_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - SERVERCOW_TTL = "The TTL of the TXT record used for the DNS challenge" - SERVERCOW_HTTP_TIMEOUT = "API request timeout" + SERVERCOW_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + SERVERCOW_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + SERVERCOW_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + SERVERCOW_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://cp.servercow.de/client/plugin/support_manager/knowledgebase/view/34/dns-api-v1/7/" diff --git a/providers/dns/shellrent/shellrent.toml b/providers/dns/shellrent/shellrent.toml index 1e19e2d0d..48a5b9ad9 100644 --- a/providers/dns/shellrent/shellrent.toml +++ b/providers/dns/shellrent/shellrent.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns shellrent -d '*.example.com' -d example.com r SHELLRENT_USERNAME = "Username" SHELLRENT_TOKEN = "Token" [Configuration.Additional] - SHELLRENT_POLLING_INTERVAL = "Time between DNS propagation check" - SHELLRENT_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - SHELLRENT_TTL = "The TTL of the TXT record used for the DNS challenge" - SHELLRENT_HTTP_TIMEOUT = "API request timeout" + SHELLRENT_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + SHELLRENT_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" + SHELLRENT_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)" + SHELLRENT_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://api.shellrent.com/section/api2" diff --git a/providers/dns/simply/simply.toml b/providers/dns/simply/simply.toml index 15cf7feb2..2814fd955 100644 --- a/providers/dns/simply/simply.toml +++ b/providers/dns/simply/simply.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns simply -d '*.example.com' -d example.com run SIMPLY_ACCOUNT_NAME = "Account name" SIMPLY_API_KEY = "API key" [Configuration.Additional] - SIMPLY_POLLING_INTERVAL = "Time between DNS propagation check" - SIMPLY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - SIMPLY_TTL = "The TTL of the TXT record used for the DNS challenge" - SIMPLY_HTTP_TIMEOUT = "API request timeout" + SIMPLY_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + SIMPLY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" + SIMPLY_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + SIMPLY_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.simply.com/en/docs/api/" diff --git a/providers/dns/sonic/sonic.toml b/providers/dns/sonic/sonic.toml index f871d3f94..921fe4988 100644 --- a/providers/dns/sonic/sonic.toml +++ b/providers/dns/sonic/sonic.toml @@ -34,11 +34,11 @@ Hostname should be the toplevel domain managed e.g. `example.com` not `www.examp SONIC_USER_ID = "User ID" SONIC_API_KEY = "API Key" [Configuration.Additional] - SONIC_POLLING_INTERVAL = "Time between DNS propagation check" - SONIC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - SONIC_TTL = "The TTL of the TXT record used for the DNS challenge" - SONIC_HTTP_TIMEOUT = "API request timeout" - SONIC_SEQUENCE_INTERVAL = "Time between sequential requests" + SONIC_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + SONIC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + SONIC_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + SONIC_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" + SONIC_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://public-api.sonic.net/dyndns/" diff --git a/providers/dns/stackpath/stackpath.go b/providers/dns/stackpath/stackpath.go index 8a1a2d09e..6d12ce875 100644 --- a/providers/dns/stackpath/stackpath.go +++ b/providers/dns/stackpath/stackpath.go @@ -43,7 +43,7 @@ type Config struct { // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, 120), + TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), } diff --git a/providers/dns/stackpath/stackpath.toml b/providers/dns/stackpath/stackpath.toml index 307922ee2..cc14cdfba 100644 --- a/providers/dns/stackpath/stackpath.toml +++ b/providers/dns/stackpath/stackpath.toml @@ -17,9 +17,9 @@ lego --email you@example.com --dns stackpath -d '*.example.com' -d example.com r STACKPATH_CLIENT_SECRET = "Client secret" STACKPATH_STACK_ID = "Stack ID" [Configuration.Additional] - STACKPATH_POLLING_INTERVAL = "Time between DNS propagation check" - STACKPATH_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - STACKPATH_TTL = "The TTL of the TXT record used for the DNS challenge" + STACKPATH_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + STACKPATH_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + STACKPATH_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" [Links] API = "https://developer.stackpath.com/en/api/dns/#tag/Zone" diff --git a/providers/dns/technitium/technitium.toml b/providers/dns/technitium/technitium.toml index 54502957f..13b40c304 100644 --- a/providers/dns/technitium/technitium.toml +++ b/providers/dns/technitium/technitium.toml @@ -23,10 +23,10 @@ Technitium recommends to use it in production over the HTTP API. TECHNITIUM_SERVER_BASE_URL = "Server base URL" TECHNITIUM_API_TOKEN = "API token" [Configuration.Additional] - TECHNITIUM_POLLING_INTERVAL = "Time between DNS propagation check" - TECHNITIUM_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - TECHNITIUM_TTL = "The TTL of the TXT record used for the DNS challenge" - TECHNITIUM_HTTP_TIMEOUT = "API request timeout" + TECHNITIUM_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + TECHNITIUM_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + TECHNITIUM_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + TECHNITIUM_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://github.com/TechnitiumSoftware/DnsServer/blob/0f83d23e605956b66ac76921199e241d9cc061bd/APIDOCS.md" diff --git a/providers/dns/tencentcloud/tencentcloud.toml b/providers/dns/tencentcloud/tencentcloud.toml index beb138e91..75a950a49 100644 --- a/providers/dns/tencentcloud/tencentcloud.toml +++ b/providers/dns/tencentcloud/tencentcloud.toml @@ -17,10 +17,10 @@ lego --email you@example.com --dns tencentcloud -d '*.example.com' -d example.co [Configuration.Additional] TENCENTCLOUD_SESSION_TOKEN = "Access Key token" TENCENTCLOUD_REGION = "Region" - TENCENTCLOUD_POLLING_INTERVAL = "Time between DNS propagation check" - TENCENTCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - TENCENTCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge" - TENCENTCLOUD_HTTP_TIMEOUT = "API request timeout" + TENCENTCLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + TENCENTCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + TENCENTCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)" + TENCENTCLOUD_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://cloud.tencent.com/document/product/1427/56153" diff --git a/providers/dns/timewebcloud/timewebcloud.toml b/providers/dns/timewebcloud/timewebcloud.toml index 4f8d7e860..8c20b37b9 100644 --- a/providers/dns/timewebcloud/timewebcloud.toml +++ b/providers/dns/timewebcloud/timewebcloud.toml @@ -13,9 +13,9 @@ lego --email you@example.com --dns timewebcloud -d '*.example.com' -d example.co [Configuration.Credentials] TIMEWEBCLOUD_AUTH_TOKEN = "Authentication token" [Configuration.Additional] - TIMEWEBCLOUD_POLLING_INTERVAL = "Time between DNS propagation check" - TIMEWEBCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - TIMEWEBCLOUD_HTTP_TIMEOUT = "API request timeout" + TIMEWEBCLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + TIMEWEBCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + TIMEWEBCLOUD_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" [Links] API = "https://timeweb.cloud/api-docs" diff --git a/providers/dns/transip/transip.toml b/providers/dns/transip/transip.toml index 47059c551..0625f819b 100644 --- a/providers/dns/transip/transip.toml +++ b/providers/dns/transip/transip.toml @@ -15,9 +15,9 @@ lego --email you@example.com --dns transip -d '*.example.com' -d example.com run TRANSIP_ACCOUNT_NAME = "Account name" TRANSIP_PRIVATE_KEY_PATH = "Private key path" [Configuration.Additional] - TRANSIP_POLLING_INTERVAL = "Time between DNS propagation check" - TRANSIP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - TRANSIP_TTL = "The TTL of the TXT record used for the DNS challenge" + TRANSIP_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + TRANSIP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 600)" + TRANSIP_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 10)" [Links] API = "https://api.transip.eu/rest/docs.html" diff --git a/providers/dns/ultradns/ultradns.go b/providers/dns/ultradns/ultradns.go index f95cf18e2..0515cbaae 100644 --- a/providers/dns/ultradns/ultradns.go +++ b/providers/dns/ultradns/ultradns.go @@ -53,7 +53,7 @@ type Config struct { func NewDefaultConfig() *Config { return &Config{ Endpoint: env.GetOrDefaultString(EnvEndpoint, defaultEndpoint), - TTL: env.GetOrDefaultInt(EnvTTL, 120), + TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 4*time.Second), } diff --git a/providers/dns/ultradns/ultradns.toml b/providers/dns/ultradns/ultradns.toml index c6ff72eac..a403a3dcf 100644 --- a/providers/dns/ultradns/ultradns.toml +++ b/providers/dns/ultradns/ultradns.toml @@ -16,9 +16,9 @@ lego --email you@example.com --dns ultradns -d '*.example.com' -d example.com ru ULTRADNS_PASSWORD = "API Password" [Configuration.Additional] ULTRADNS_ENDPOINT = "API endpoint URL, defaults to https://api.ultradns.com/" - ULTRADNS_TTL = "The TTL of the TXT record used for the DNS challenge" - ULTRADNS_POLLING_INTERVAL = "Time between DNS propagation check" - ULTRADNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" + ULTRADNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + ULTRADNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 4)" + ULTRADNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" [Links] API = "https://ultra-portalstatic.ultradns.com/static/docs/REST-API_User_Guide.pdf" diff --git a/providers/dns/variomedia/variomedia.toml b/providers/dns/variomedia/variomedia.toml index 945a6f9f5..fe6f2797a 100644 --- a/providers/dns/variomedia/variomedia.toml +++ b/providers/dns/variomedia/variomedia.toml @@ -13,11 +13,11 @@ lego --email you@example.com --dns variomedia -d '*.example.com' -d example.com [Configuration.Credentials] VARIOMEDIA_API_TOKEN = "API token" [Configuration.Additional] - VARIOMEDIA_POLLING_INTERVAL = "Time between DNS propagation check" - VARIOMEDIA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - VARIOMEDIA_TTL = "The TTL of the TXT record used for the DNS challenge" - VARIOMEDIA_SEQUENCE_INTERVAL = "Time between sequential requests" - VARIOMEDIA_HTTP_TIMEOUT = "API request timeout" + VARIOMEDIA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + VARIOMEDIA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + VARIOMEDIA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + VARIOMEDIA_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" + VARIOMEDIA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://api.variomedia.de/docs/dns-records.html" diff --git a/providers/dns/vegadns/vegadns.go b/providers/dns/vegadns/vegadns.go index b56bce97b..824f727eb 100644 --- a/providers/dns/vegadns/vegadns.go +++ b/providers/dns/vegadns/vegadns.go @@ -42,7 +42,7 @@ func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, 10), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 12*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 1*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, time.Minute), } } diff --git a/providers/dns/vegadns/vegadns.toml b/providers/dns/vegadns/vegadns.toml index e1a7cc713..d01490f55 100644 --- a/providers/dns/vegadns/vegadns.toml +++ b/providers/dns/vegadns/vegadns.toml @@ -12,9 +12,9 @@ Example = '''''' SECRET_VEGADNS_SECRET = "API secret" VEGADNS_URL = "API endpoint URL" [Configuration.Additional] - VEGADNS_POLLING_INTERVAL = "Time between DNS propagation check" - VEGADNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - VEGADNS_TTL = "The TTL of the TXT record used for the DNS challenge" + VEGADNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 60)" + VEGADNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 720)" + VEGADNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 10)" [Links] API = "https://github.com/shupp/VegaDNS-API" diff --git a/providers/dns/vercel/vercel.go b/providers/dns/vercel/vercel.go index bf3a0f532..9ba92e21f 100644 --- a/providers/dns/vercel/vercel.go +++ b/providers/dns/vercel/vercel.go @@ -44,7 +44,7 @@ type Config struct { func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, 60), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 60*time.Second), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 5*time.Second), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), diff --git a/providers/dns/vercel/vercel.toml b/providers/dns/vercel/vercel.toml index 60df41798..2545b9c48 100644 --- a/providers/dns/vercel/vercel.toml +++ b/providers/dns/vercel/vercel.toml @@ -14,10 +14,10 @@ lego --email you@example.com --dns vercel -d '*.example.com' -d example.com run VERCEL_API_TOKEN = "Authentication token" [Configuration.Additional] VERCEL_TEAM_ID = "Team ID (ex: team_xxxxxxxxxxxxxxxxxxxxxxxx)" - VERCEL_POLLING_INTERVAL = "Time between DNS propagation check" - VERCEL_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - VERCEL_TTL = "The TTL of the TXT record used for the DNS challenge" - VERCEL_HTTP_TIMEOUT = "API request timeout" + VERCEL_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 5)" + VERCEL_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + VERCEL_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + VERCEL_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://vercel.com/docs/rest-api#endpoints/dns" diff --git a/providers/dns/versio/versio.go b/providers/dns/versio/versio.go index 08a2d4639..78ddd9bac 100644 --- a/providers/dns/versio/versio.go +++ b/providers/dns/versio/versio.go @@ -55,7 +55,7 @@ func NewDefaultConfig() *Config { return &Config{ BaseURL: baseURL, TTL: env.GetOrDefaultInt(EnvTTL, 300), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 60*time.Second), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 5*time.Second), SequenceInterval: env.GetOrDefaultSecond(EnvSequenceInterval, dns01.DefaultPropagationTimeout), HTTPClient: &http.Client{ diff --git a/providers/dns/versio/versio.toml b/providers/dns/versio/versio.toml index 7fc27ebcd..33f7125c8 100644 --- a/providers/dns/versio/versio.toml +++ b/providers/dns/versio/versio.toml @@ -20,11 +20,11 @@ To test with the sandbox environment set ```VERSIO_ENDPOINT=https://www.versio.n VERSIO_PASSWORD = "Basic authentication password" [Configuration.Additional] VERSIO_ENDPOINT = "The endpoint URL of the API Server" - VERSIO_POLLING_INTERVAL = "Time between DNS propagation check" - VERSIO_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - VERSIO_HTTP_TIMEOUT = "API request timeout" - VERSIO_SEQUENCE_INTERVAL = "Time between sequential requests, default 60s" - VERSIO_TTL = "The TTL of the TXT record used for the DNS challenge" + VERSIO_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 5)" + VERSIO_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + VERSIO_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" + VERSIO_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + VERSIO_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.versio.nl/RESTapidoc/" diff --git a/providers/dns/vinyldns/vinyldns.toml b/providers/dns/vinyldns/vinyldns.toml index bdd07bae8..6acc623fd 100644 --- a/providers/dns/vinyldns/vinyldns.toml +++ b/providers/dns/vinyldns/vinyldns.toml @@ -22,9 +22,9 @@ Users are required to have DELETE ACL level or zone admin permissions on the Vin VINYLDNS_SECRET_KEY = "The VinylDNS API Secret key" VINYLDNS_HOST = "The VinylDNS API URL" [Configuration.Additional] - VINYLDNS_POLLING_INTERVAL = "Time between DNS propagation check" - VINYLDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - VINYLDNS_TTL = "The TTL of the TXT record used for the DNS challenge" + VINYLDNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 4)" + VINYLDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + VINYLDNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 30)" [Links] API = "https://www.vinyldns.io/api/" diff --git a/providers/dns/vkcloud/vkcloud.toml b/providers/dns/vkcloud/vkcloud.toml index 8e67e2670..366039694 100644 --- a/providers/dns/vkcloud/vkcloud.toml +++ b/providers/dns/vkcloud/vkcloud.toml @@ -33,9 +33,9 @@ You can find all required and additional information on ["Project/Keys" page](ht VK_CLOUD_DNS_ENDPOINT="URL of DNS API. Defaults to https://mcs.mail.ru/public-dns but can be changed for usage with private clouds" VK_CLOUD_IDENTITY_ENDPOINT="URL of OpenStack Auth API, Defaults to https://infra.mail.ru:35357/v3/ but can be changed for usage with private clouds" VK_CLOUD_DOMAIN_NAME="Openstack users domain name. Defaults to `users` but can be changed for usage with private clouds" - VK_CLOUD_POLLING_INTERVAL = "Time between DNS propagation check" - VK_CLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - VK_CLOUD_TTL = "The TTL of the TXT record used for the DNS challenge" + VK_CLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + VK_CLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + VK_CLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" [Links] API = "https://mcs.mail.ru/docs/networks/vnet/networks/publicdns/api" diff --git a/providers/dns/volcengine/volcengine.go b/providers/dns/volcengine/volcengine.go index ed5544592..a26fb88b0 100644 --- a/providers/dns/volcengine/volcengine.go +++ b/providers/dns/volcengine/volcengine.go @@ -63,7 +63,7 @@ func NewDefaultConfig() *Config { Region: env.GetOrDefaultString(EnvRegion, volc.DefaultRegion), TTL: env.GetOrDefaultInt(EnvTTL, defaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 240*time.Second), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 4*time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, volc.Timeout*time.Second), } diff --git a/providers/dns/volcengine/volcengine.toml b/providers/dns/volcengine/volcengine.toml index 85431714f..cb7c219f7 100644 --- a/providers/dns/volcengine/volcengine.toml +++ b/providers/dns/volcengine/volcengine.toml @@ -18,10 +18,10 @@ lego --email you@example.com --dns volcengine -d '*.example.com' -d example.com VOLC_REGION = "Region" VOLC_HOST = "API host" VOLC_SCHEME = "API scheme" - VOLC_POLLING_INTERVAL = "Time between DNS propagation check" - VOLC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - VOLC_TTL = "The TTL of the TXT record used for the DNS challenge" - VOLC_HTTP_TIMEOUT = "API request timeout" + VOLC_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + VOLC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 240)" + VOLC_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)" + VOLC_HTTP_TIMEOUT = "API request timeout in seconds (Default: 15)" [Links] API = "https://www.volcengine.com/docs/6758/155086" diff --git a/providers/dns/vscale/vscale.go b/providers/dns/vscale/vscale.go index a500837bc..6c51ae5ca 100644 --- a/providers/dns/vscale/vscale.go +++ b/providers/dns/vscale/vscale.go @@ -50,7 +50,7 @@ func NewDefaultConfig() *Config { BaseURL: env.GetOrDefaultString(EnvBaseURL, selectel.DefaultVScaleBaseURL), TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), }, diff --git a/providers/dns/vscale/vscale.toml b/providers/dns/vscale/vscale.toml index 83aa6a513..78b6c99e5 100644 --- a/providers/dns/vscale/vscale.toml +++ b/providers/dns/vscale/vscale.toml @@ -14,10 +14,10 @@ lego --email you@example.com --dns vscale -d '*.example.com' -d example.com run VSCALE_API_TOKEN = "API token" [Configuration.Additional] VSCALE_BASE_URL = "API endpoint URL" - VSCALE_POLLING_INTERVAL = "Time between DNS propagation check" - VSCALE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - VSCALE_TTL = "The TTL of the TXT record used for the DNS challenge" - VSCALE_HTTP_TIMEOUT = "API request timeout" + VSCALE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + VSCALE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + VSCALE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + VSCALE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://developers.vscale.io/documentation/api/v1/#api-Domains_Records" diff --git a/providers/dns/vultr/vultr.toml b/providers/dns/vultr/vultr.toml index 83b896f77..7d31bd52b 100644 --- a/providers/dns/vultr/vultr.toml +++ b/providers/dns/vultr/vultr.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns vultr -d '*.example.com' -d example.com run [Configuration.Credentials] VULTR_API_KEY = "API key" [Configuration.Additional] - VULTR_POLLING_INTERVAL = "Time between DNS propagation check" - VULTR_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - VULTR_TTL = "The TTL of the TXT record used for the DNS challenge" - VULTR_HTTP_TIMEOUT = "API request timeout" + VULTR_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + VULTR_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + VULTR_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + VULTR_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.vultr.com/api/#dns" diff --git a/providers/dns/webnames/webnames.toml b/providers/dns/webnames/webnames.toml index 030d385c9..1962a69eb 100644 --- a/providers/dns/webnames/webnames.toml +++ b/providers/dns/webnames/webnames.toml @@ -21,10 +21,9 @@ The API key can be found: Personal account / My domains and services / Select th [Configuration.Credentials] WEBNAMES_API_KEY = "Domain API key" [Configuration.Additional] - WEBNAMES_POLLING_INTERVAL = "Time between DNS propagation check" - WEBNAMES_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - WEBNAMES_TTL = "The TTL of the TXT record used for the DNS challenge" - WEBNAMES_HTTP_TIMEOUT = "API request timeout" + WEBNAMES_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + WEBNAMES_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + WEBNAMES_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://github.com/regtime-ltd/certbot-dns-webnames" diff --git a/providers/dns/websupport/websupport.toml b/providers/dns/websupport/websupport.toml index d1a0af7dc..262df22b6 100644 --- a/providers/dns/websupport/websupport.toml +++ b/providers/dns/websupport/websupport.toml @@ -15,11 +15,11 @@ lego --email you@example.com --dns websupport -d '*.example.com' -d example.com WEBSUPPORT_API_KEY = "API key" WEBSUPPORT_SECRET = "API secret" [Configuration.Additional] - WEBSUPPORT_POLLING_INTERVAL = "Time between DNS propagation check" - WEBSUPPORT_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - WEBSUPPORT_SEQUENCE_INTERVAL = "Time between sequential requests" - WEBSUPPORT_TTL = "The TTL of the TXT record used for the DNS challenge" - WEBSUPPORT_HTTP_TIMEOUT = "API request timeout" + WEBSUPPORT_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + WEBSUPPORT_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + WEBSUPPORT_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" + WEBSUPPORT_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)" + WEBSUPPORT_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://rest.websupport.sk/docs/v1.zone" diff --git a/providers/dns/wedos/wedos.toml b/providers/dns/wedos/wedos.toml index 64845536e..2ed351a2d 100644 --- a/providers/dns/wedos/wedos.toml +++ b/providers/dns/wedos/wedos.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns wedos -d '*.example.com' -d example.com run WEDOS_USERNAME = "Username is the same as for the admin account" WEDOS_WAPI_PASSWORD = "Password needs to be generated and IP allowed in the admin interface" [Configuration.Additional] - WEDOS_POLLING_INTERVAL = "Time between DNS propagation check" - WEDOS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - WEDOS_HTTP_TIMEOUT = "API request timeout" - WEDOS_TTL = "The TTL of the TXT record used for the DNS challenge" + WEDOS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + WEDOS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 600)" + WEDOS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + WEDOS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://kb.wedos.com/en/kategorie/wapi-api-interface/wdns-en/" diff --git a/providers/dns/westcn/westcn.toml b/providers/dns/westcn/westcn.toml index 3b3914eac..acf3a4e49 100644 --- a/providers/dns/westcn/westcn.toml +++ b/providers/dns/westcn/westcn.toml @@ -15,10 +15,10 @@ lego --email you@example.com --dns westcn -d '*.example.com' -d example.com run WESTCN_USERNAME = "Username" WESTCN_PASSWORD = "API password" [Configuration.Additional] - WESTCN_POLLING_INTERVAL = "Time between DNS propagation check" - WESTCN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - WESTCN_TTL = "The TTL of the TXT record used for the DNS challenge" - WESTCN_HTTP_TIMEOUT = "API request timeout" + WESTCN_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + WESTCN_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + WESTCN_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + WESTCN_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.west.cn/CustomerCenter/doc/domain_v2.html" diff --git a/providers/dns/yandex/yandex.toml b/providers/dns/yandex/yandex.toml index 91adf4658..78da1444d 100644 --- a/providers/dns/yandex/yandex.toml +++ b/providers/dns/yandex/yandex.toml @@ -14,10 +14,10 @@ lego --email you@example.com --dns yandex -d '*.example.com' -d example.com run [Configuration.Credentials] YANDEX_PDD_TOKEN = "Basic authentication username" [Configuration.Additional] - YANDEX_POLLING_INTERVAL = "Time between DNS propagation check" - YANDEX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - YANDEX_HTTP_TIMEOUT = "API request timeout" - YANDEX_TTL = "The TTL of the TXT record used for the DNS challenge" + YANDEX_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + YANDEX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + YANDEX_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 21600)" + YANDEX_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://yandex.com/dev/domain/doc/concepts/api-dns.html" diff --git a/providers/dns/yandex360/yandex360.toml b/providers/dns/yandex360/yandex360.toml index 88e4036ab..69ea02a28 100644 --- a/providers/dns/yandex360/yandex360.toml +++ b/providers/dns/yandex360/yandex360.toml @@ -16,10 +16,10 @@ lego --email you@example.com --dns yandex360 -d '*.example.com' -d example.com r YANDEX360_OAUTH_TOKEN = "The OAuth Token" YANDEX360_ORG_ID = "The organization ID" [Configuration.Additional] - YANDEX360_POLLING_INTERVAL = "Time between DNS propagation check" - YANDEX360_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - YANDEX360_HTTP_TIMEOUT = "API request timeout" - YANDEX360_TTL = "The TTL of the TXT record used for the DNS challenge" + YANDEX360_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + YANDEX360_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + YANDEX360_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 21600)" + YANDEX360_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://yandex.ru/dev/api360/doc/ref/DomainDNSService.html" diff --git a/providers/dns/yandexcloud/yandexcloud.toml b/providers/dns/yandexcloud/yandexcloud.toml index c19b9c1cc..a4bf3ebbb 100644 --- a/providers/dns/yandexcloud/yandexcloud.toml +++ b/providers/dns/yandexcloud/yandexcloud.toml @@ -40,9 +40,9 @@ cat key.json | base64 YANDEX_CLOUD_IAM_TOKEN = "The base64 encoded json which contains information about iam token of service account with `dns.admin` permissions" YANDEX_CLOUD_FOLDER_ID = "The string id of folder (aka project) in Yandex Cloud" [Configuration.Additional] - YANDEX_CLOUD_POLLING_INTERVAL = "Time between DNS propagation check" - YANDEX_CLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - YANDEX_CLOUD_TTL = "The TTL of the TXT record used for the DNS challenge" + YANDEX_CLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + YANDEX_CLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + YANDEX_CLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" [Links] API = "https://cloud.yandex.com/en/docs/dns/quickstart" diff --git a/providers/dns/zoneee/zoneee.toml b/providers/dns/zoneee/zoneee.toml index 5d95095e8..75ccabbda 100644 --- a/providers/dns/zoneee/zoneee.toml +++ b/providers/dns/zoneee/zoneee.toml @@ -16,10 +16,9 @@ lego --email you@example.com --dns zoneee -d '*.example.com' -d example.com run ZONEEE_API_KEY = "API key" [Configuration.Additional] ZONEEE_ENDPOINT = "API endpoint URL" - ZONEEE_POLLING_INTERVAL = "Time between DNS propagation check" - ZONEEE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - ZONEEE_TTL = "The TTL of the TXT record used for the DNS challenge" - ZONEEE_HTTP_TIMEOUT = "API request timeout" + ZONEEE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 5)" + ZONEEE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" + ZONEEE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://api.zone.eu/v2" diff --git a/providers/dns/zonomi/zonomi.toml b/providers/dns/zonomi/zonomi.toml index 9780323a7..a5577999a 100644 --- a/providers/dns/zonomi/zonomi.toml +++ b/providers/dns/zonomi/zonomi.toml @@ -13,10 +13,10 @@ lego --email you@example.com --dns zonomi -d '*.example.com' -d example.com run [Configuration.Credentials] ZONOMI_API_KEY = "User API key" [Configuration.Additional] - ZONOMI_POLLING_INTERVAL = "Time between DNS propagation check" - ZONOMI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" - ZONOMI_TTL = "The TTL of the TXT record used for the DNS challenge" - ZONOMI_HTTP_TIMEOUT = "API request timeout" + ZONOMI_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + ZONOMI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + ZONOMI_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)" + ZONOMI_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://zonomi.com/app/dns/dyndns.jsp" From b7947d83c59a993a79d363e0347c83e3d6631b0d Mon Sep 17 00:00:00 2001 From: dmayle Date: Sun, 12 Jan 2025 17:32:20 -0800 Subject: [PATCH 030/298] cli: add environment variable for specifying email (#2398) Co-authored-by: Fernandez Ludovic --- cmd/flags.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/flags.go b/cmd/flags.go index 0a8024dff..fcea5e598 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -71,6 +71,7 @@ func CreateFlags(defaultPath string) []cli.Flag { &cli.StringFlag{ Name: flgEmail, Aliases: []string{"m"}, + EnvVars: []string{"LEGO_EMAIL"}, Usage: "Email used for registration and recovery contact.", }, &cli.StringFlag{ From 248e77578816886e088132c0f5ff9f48812e39b4 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 13 Jan 2025 12:21:31 +0100 Subject: [PATCH 031/298] chore: use constants for env vars related to flags (#2399) --- cmd/cmd_renew.go | 30 +----------------------------- cmd/cmd_run.go | 2 +- cmd/flags.go | 30 +++++++++++++++++++++--------- cmd/hook.go | 30 ++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 39 deletions(-) diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index ca297fae7..f75678ba6 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -30,16 +30,6 @@ const ( flgForceCertDomains = "force-cert-domains" ) -const ( - renewEnvAccountEmail = "LEGO_ACCOUNT_EMAIL" - renewEnvCertDomain = "LEGO_CERT_DOMAIN" - renewEnvCertPath = "LEGO_CERT_PATH" - renewEnvCertKeyPath = "LEGO_CERT_KEY_PATH" - renewEnvIssuerCertKeyPath = "LEGO_ISSUER_CERT_PATH" - renewEnvCertPEMPath = "LEGO_CERT_PEM_PATH" - renewEnvCertPFXPath = "LEGO_CERT_PFX_PATH" -) - func createRenew() *cli.Command { return &cli.Command{ Name: "renew", @@ -139,7 +129,7 @@ func renew(ctx *cli.Context) error { bundle := !ctx.Bool(flgNoBundle) - meta := map[string]string{renewEnvAccountEmail: account.Email} + meta := map[string]string{hookEnvAccountEmail: account.Email} // CSR if ctx.IsSet(flgCSR) { @@ -395,24 +385,6 @@ func getARIRenewalTime(ctx *cli.Context, cert *x509.Certificate, domain string, return renewalTime } -func addPathToMetadata(meta map[string]string, domain string, certRes *certificate.Resource, certsStorage *CertificatesStorage) { - meta[renewEnvCertDomain] = domain - meta[renewEnvCertPath] = certsStorage.GetFileName(domain, certExt) - meta[renewEnvCertKeyPath] = certsStorage.GetFileName(domain, keyExt) - - if certRes.IssuerCertificate != nil { - meta[renewEnvIssuerCertKeyPath] = certsStorage.GetFileName(domain, issuerExt) - } - - if certsStorage.pem { - meta[renewEnvCertPEMPath] = certsStorage.GetFileName(domain, pemExt) - } - - if certsStorage.pfx { - meta[renewEnvCertPFXPath] = certsStorage.GetFileName(domain, pfxExt) - } -} - func merge(prevDomains, nextDomains []string) []string { for _, next := range nextDomains { if slices.Contains(prevDomains, next) { diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index f6d7b70c3..8b57317ec 100644 --- a/cmd/cmd_run.go +++ b/cmd/cmd_run.go @@ -130,7 +130,7 @@ func run(ctx *cli.Context) error { certsStorage.SaveResource(cert) meta := map[string]string{ - renewEnvAccountEmail: account.Email, + hookEnvAccountEmail: account.Email, } addPathToMetadata(meta, cert.Domain, cert, certsStorage) diff --git a/cmd/flags.go b/cmd/flags.go index fcea5e598..48e63d172 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -49,6 +49,18 @@ const ( flgUserAgent = "user-agent" ) +const ( + envEAB = "LEGO_EAB" + envEABHMAC = "LEGO_EAB_HMAC" + envEABKID = "LEGO_EAB_KID" + envEmail = "LEGO_EMAIL" + envPath = "LEGO_PATH" + envPFX = "LEGO_PFX" + envPFXFormat = "LEGO_PFX_FORMAT" + envPFXPassword = "LEGO_PFX_PASSWORD" + envServer = "LEGO_SERVER" +) + func CreateFlags(defaultPath string) []cli.Flag { return []cli.Flag{ &cli.StringSliceFlag{ @@ -59,7 +71,7 @@ func CreateFlags(defaultPath string) []cli.Flag { &cli.StringFlag{ Name: flgServer, Aliases: []string{"s"}, - EnvVars: []string{"LEGO_SERVER"}, + EnvVars: []string{envServer}, Usage: "CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client.", Value: lego.LEDirectoryProduction, }, @@ -71,7 +83,7 @@ func CreateFlags(defaultPath string) []cli.Flag { &cli.StringFlag{ Name: flgEmail, Aliases: []string{"m"}, - EnvVars: []string{"LEGO_EMAIL"}, + EnvVars: []string{envEmail}, Usage: "Email used for registration and recovery contact.", }, &cli.StringFlag{ @@ -81,17 +93,17 @@ func CreateFlags(defaultPath string) []cli.Flag { }, &cli.BoolFlag{ Name: flgEAB, - EnvVars: []string{"LEGO_EAB"}, + EnvVars: []string{envEAB}, Usage: "Use External Account Binding for account registration. Requires --kid and --hmac.", }, &cli.StringFlag{ Name: flgKID, - EnvVars: []string{"LEGO_EAB_KID"}, + EnvVars: []string{envEABKID}, Usage: "Key identifier from External CA. Used for External Account Binding.", }, &cli.StringFlag{ Name: flgHMAC, - EnvVars: []string{"LEGO_EAB_HMAC"}, + EnvVars: []string{envEABHMAC}, Usage: "MAC key from External CA. Should be in Base64 URL Encoding without padding format. Used for External Account Binding.", }, &cli.StringFlag{ @@ -106,7 +118,7 @@ func CreateFlags(defaultPath string) []cli.Flag { }, &cli.StringFlag{ Name: flgPath, - EnvVars: []string{"LEGO_PATH"}, + EnvVars: []string{envPath}, Usage: "Directory to use for storing the data.", Value: defaultPath, }, @@ -193,19 +205,19 @@ func CreateFlags(defaultPath string) []cli.Flag { &cli.BoolFlag{ Name: flgPFX, Usage: "Generate an additional .pfx (PKCS#12) file by concatenating the .key and .crt and issuer .crt files together.", - EnvVars: []string{"LEGO_PFX"}, + EnvVars: []string{envPFX}, }, &cli.StringFlag{ Name: flgPFXPass, Usage: "The password used to encrypt the .pfx (PCKS#12) file.", Value: pkcs12.DefaultPassword, - EnvVars: []string{"LEGO_PFX_PASSWORD"}, + EnvVars: []string{envPFXPassword}, }, &cli.StringFlag{ Name: flgPFXFormat, Usage: "The encoding format to use when encrypting the .pfx (PCKS#12) file. Supported: RC2, DES, SHA256.", Value: "RC2", - EnvVars: []string{"LEGO_PFX_FORMAT"}, + EnvVars: []string{envPFXFormat}, }, &cli.IntFlag{ Name: flgCertTimeout, diff --git a/cmd/hook.go b/cmd/hook.go index 514650ecd..5e19c7995 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -8,6 +8,18 @@ import ( "os/exec" "strings" "time" + + "github.com/go-acme/lego/v4/certificate" +) + +const ( + hookEnvAccountEmail = "LEGO_ACCOUNT_EMAIL" + hookEnvCertDomain = "LEGO_CERT_DOMAIN" + hookEnvCertPath = "LEGO_CERT_PATH" + hookEnvCertKeyPath = "LEGO_CERT_KEY_PATH" + hookEnvIssuerCertKeyPath = "LEGO_ISSUER_CERT_PATH" + hookEnvCertPEMPath = "LEGO_CERT_PEM_PATH" + hookEnvCertPFXPath = "LEGO_CERT_PFX_PATH" ) func launchHook(hook string, timeout time.Duration, meta map[string]string) error { @@ -45,3 +57,21 @@ func metaToEnv(meta map[string]string) []string { return envs } + +func addPathToMetadata(meta map[string]string, domain string, certRes *certificate.Resource, certsStorage *CertificatesStorage) { + meta[hookEnvCertDomain] = domain + meta[hookEnvCertPath] = certsStorage.GetFileName(domain, certExt) + meta[hookEnvCertKeyPath] = certsStorage.GetFileName(domain, keyExt) + + if certRes.IssuerCertificate != nil { + meta[hookEnvIssuerCertKeyPath] = certsStorage.GetFileName(domain, issuerExt) + } + + if certsStorage.pem { + meta[hookEnvCertPEMPath] = certsStorage.GetFileName(domain, pemExt) + } + + if certsStorage.pfx { + meta[hookEnvCertPFXPath] = certsStorage.GetFileName(domain, pfxExt) + } +} From 2211b56fea5f5e27ecd52441cedffc100b8cfa0e Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 15 Jan 2025 13:56:42 +0100 Subject: [PATCH 032/298] chore: update issue template --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + .github/ISSUE_TEMPLATE/feature_request.yml | 1 + .github/ISSUE_TEMPLATE/new_dns_provider.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index a4d077e5a..d08b48777 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: attributes: label: How do you use lego? options: + - I don't know - Library - Binary - Docker image diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index b4e264177..36afb00ef 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -14,6 +14,7 @@ body: attributes: label: How do you use lego? options: + - I don't know - Library - Binary - Docker image diff --git a/.github/ISSUE_TEMPLATE/new_dns_provider.yml b/.github/ISSUE_TEMPLATE/new_dns_provider.yml index 274983636..f310e9815 100644 --- a/.github/ISSUE_TEMPLATE/new_dns_provider.yml +++ b/.github/ISSUE_TEMPLATE/new_dns_provider.yml @@ -24,6 +24,7 @@ body: attributes: label: How do you use lego? options: + - I don't know - Library - Binary - Docker image From 7d7bc7b044ac0c3b635b279f1437dbbba30f2d00 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 28 Jan 2025 18:02:12 +0100 Subject: [PATCH 033/298] Add DNS provider for myaddr.{tools,dev,io} (#2411) --- README.md | 32 ++-- cmd/zz_gen_cmd_dnshelp.go | 22 +++ docs/content/dns/zz_gen_myaddr.md | 68 ++++++++ docs/data/zz_cli_help.toml | 4 +- providers/dns/myaddr/internal/client.go | 115 +++++++++++++ providers/dns/myaddr/internal/client_test.go | 77 +++++++++ .../dns/myaddr/internal/fixtures/error.txt | 1 + providers/dns/myaddr/internal/types.go | 6 + providers/dns/myaddr/myaddr.go | 160 ++++++++++++++++++ providers/dns/myaddr/myaddr.toml | 23 +++ providers/dns/myaddr/myaddr_test.go | 113 +++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 12 files changed, 606 insertions(+), 18 deletions(-) create mode 100644 docs/content/dns/zz_gen_myaddr.md create mode 100644 providers/dns/myaddr/internal/client.go create mode 100644 providers/dns/myaddr/internal/client_test.go create mode 100644 providers/dns/myaddr/internal/fixtures/error.txt create mode 100644 providers/dns/myaddr/internal/types.go create mode 100644 providers/dns/myaddr/myaddr.go create mode 100644 providers/dns/myaddr/myaddr.toml create mode 100644 providers/dns/myaddr/myaddr_test.go diff --git a/README.md b/README.md index cebd033c2..b779a5863 100644 --- a/README.md +++ b/README.md @@ -158,84 +158,84 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). mijn.host Mittwald + myaddr.{tools,dev,io} MyDNS.jp MythicBeasts - Name.com + Name.com Namecheap Namesilo NearlyFreeSpeech.NET - Netcup + Netcup Netlify Nicmanager NIFCloud - Njalla + Njalla Nodion NS1 Open Telekom Cloud - Oracle Cloud + Oracle Cloud OVH plesk.com Porkbun - PowerDNS + PowerDNS Rackspace Rain Yun/雨云 RcodeZero - reg.ru + reg.ru Regfish RFC2136 RimuHosting - Sakura Cloud + Sakura Cloud Scaleway Selectel Selectel v2 - SelfHost.(de|eu) + SelfHost.(de|eu) Servercow Shellrent Simply.com - Sonic + Sonic Stackpath Technitium 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 - West.cn/西部数码 + West.cn/西部数码 Yandex 360 Yandex Cloud Yandex PDD - Zone.ee + Zone.ee Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 203cff36f..f121d6dd2 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -96,6 +96,7 @@ func allDNSCodes() string { "metaname", "mijnhost", "mittwald", + "myaddr", "mydnsjp", "mythicbeasts", "namecheap", @@ -1939,6 +1940,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/mittwald`) + case "myaddr": + // generated from: providers/dns/myaddr/myaddr.toml + ew.writeln(`Configuration for myaddr.{tools,dev,io}.`) + ew.writeln(`Code: 'myaddr'`) + ew.writeln(`Since: 'v4.22.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "MYADDR_PRIVATE_KEYS_MAPPING": Mapping between subdomains and private keys. The format is: ':,:,:'`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "MYADDR_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "MYADDR_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "MYADDR_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "MYADDR_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 2)`) + ew.writeln(` - "MYADDR_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/myaddr`) + case "mydnsjp": // generated from: providers/dns/mydnsjp/mydnsjp.toml ew.writeln(`Configuration for MyDNS.jp.`) diff --git a/docs/content/dns/zz_gen_myaddr.md b/docs/content/dns/zz_gen_myaddr.md new file mode 100644 index 000000000..277a0bf06 --- /dev/null +++ b/docs/content/dns/zz_gen_myaddr.md @@ -0,0 +1,68 @@ +--- +title: "myaddr.{tools,dev,io}" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: myaddr +dnsprovider: + since: "v4.22.0" + code: "myaddr" + url: "https://myaddr.tools/" +--- + + + + + + +Configuration for [myaddr.{tools,dev,io}](https://myaddr.tools/). + + + + +- Code: `myaddr` +- Since: v4.22.0 + + +Here is an example bash command using the myaddr.{tools,dev,io} provider: + +```bash +MYADDR_PRIVATE_KEYS_MAPPING="example:123,test:456" \ +lego --email you@example.com --dns myaddr -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `MYADDR_PRIVATE_KEYS_MAPPING` | Mapping between subdomains and private keys. The format is: `:,:,:` | + +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 | +|--------------------------------|-------------| +| `MYADDR_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `MYADDR_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `MYADDR_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `MYADDR_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 2) | +| `MYADDR_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://myaddr.tools/) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index f6674e5e5..11b48a0d5 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -22,7 +22,7 @@ GLOBAL OPTIONS: --domains value, -d value [ --domains value, -d value ] Add a domain to the process. Can be specified multiple times. --server value, -s value CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. (default: "https://acme-v02.api.letsencrypt.org/directory") [$LEGO_SERVER] --accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service. (default: false) - --email value, -m value Email used for registration and recovery contact. + --email value, -m value Email used for registration and recovery contact. [$LEGO_EMAIL] --csr value, -c value Certificate signing request filename, if an external CSR is to be used. --eab Use External Account Binding for account registration. Requires --kid and --hmac. (default: false) [$LEGO_EAB] --kid value Key identifier from External CA. Used for External Account Binding. [$LEGO_EAB_KID] @@ -145,7 +145,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, manageengine, manual, metaname, mijnhost, mittwald, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, 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, manageengine, manual, metaname, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/myaddr/internal/client.go b/providers/dns/myaddr/internal/client.go new file mode 100644 index 000000000..40f919c7d --- /dev/null +++ b/providers/dns/myaddr/internal/client.go @@ -0,0 +1,115 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "sync" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const defaultBaseURL = "https://myaddr.tools" + +// Client the myaddr.{tools,dev,io} API client. +type Client struct { + baseURL *url.URL + HTTPClient *http.Client + + credentials map[string]string + credMu sync.Mutex +} + +// NewClient creates a new Client. +func NewClient(credentials map[string]string) (*Client, error) { + if len(credentials) == 0 { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + credentials: credentials, + }, nil +} + +func (c *Client) AddTXTRecord(ctx context.Context, subdomain, value string) error { + c.credMu.Lock() + privateKey, ok := c.credentials[subdomain] + c.credMu.Unlock() + + if !ok { + return fmt.Errorf("subdomain %s not found in credentials, check your credentials map", subdomain) + } + + payload := ACMEChallenge{Key: privateKey, Data: value} + + req, err := newJSONRequest(ctx, http.MethodPost, c.baseURL.JoinPath("update"), payload) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) do(req *http.Request, result any) error { + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + raw, _ := io.ReadAll(resp.Body) + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} diff --git a/providers/dns/myaddr/internal/client_test.go b/providers/dns/myaddr/internal/client_test.go new file mode 100644 index 000000000..f74e42eb1 --- /dev/null +++ b/providers/dns/myaddr/internal/client_test.go @@ -0,0 +1,77 @@ +package internal + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func setupTest(t *testing.T, pattern string, status int, filename string) *Client { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { + if filename == "" { + rw.WriteHeader(status) + return + } + + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + defer func() { _ = file.Close() }() + + rw.WriteHeader(status) + _, err = io.Copy(rw, file) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + }) + + credentials := map[string]string{ + "example": "secret", + } + + client, err := NewClient(credentials) + require.NoError(t, err) + + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) + + return client +} + +func TestClient_AddTXTRecord(t *testing.T) { + client := setupTest(t, "POST /update", http.StatusOK, "") + + err := client.AddTXTRecord(context.Background(), "example", "txt") + require.NoError(t, err) +} + +func TestClient_AddTXTRecord_error(t *testing.T) { + client := setupTest(t, "POST /update", http.StatusBadRequest, "error.txt") + + err := client.AddTXTRecord(context.Background(), "example", "txt") + require.EqualError(t, err, `unexpected status code: [status code: 400] body: invalid value for "key"`) +} + +func TestClient_AddTXTRecord_error_credentials(t *testing.T) { + client := setupTest(t, "POST /update", http.StatusOK, "") + + err := client.AddTXTRecord(context.Background(), "nx", "txt") + require.EqualError(t, err, "subdomain nx not found in credentials, check your credentials map") +} diff --git a/providers/dns/myaddr/internal/fixtures/error.txt b/providers/dns/myaddr/internal/fixtures/error.txt new file mode 100644 index 000000000..64a417673 --- /dev/null +++ b/providers/dns/myaddr/internal/fixtures/error.txt @@ -0,0 +1 @@ +invalid value for "key" diff --git a/providers/dns/myaddr/internal/types.go b/providers/dns/myaddr/internal/types.go new file mode 100644 index 000000000..36f057497 --- /dev/null +++ b/providers/dns/myaddr/internal/types.go @@ -0,0 +1,6 @@ +package internal + +type ACMEChallenge struct { + Key string `json:"key"` + Data string `json:"acme_challenge"` +} diff --git a/providers/dns/myaddr/myaddr.go b/providers/dns/myaddr/myaddr.go new file mode 100644 index 000000000..57d46f514 --- /dev/null +++ b/providers/dns/myaddr/myaddr.go @@ -0,0 +1,160 @@ +// Package myaddr implements a DNS provider for solving the DNS-01 challenge using myaddr.{tools,dev,io}. +package myaddr + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/myaddr/internal" +) + +// Environment variables names. +const ( + envNamespace = "MYADDR_" + + EnvPrivateKeysMapping = envNamespace + "PRIVATE_KEYS_MAPPING" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvSequenceInterval = envNamespace + "SEQUENCE_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + Credentials map[string]string + + PropagationTimeout time.Duration + PollingInterval time.Duration + SequenceInterval 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), + SequenceInterval: env.GetOrDefaultSecond(EnvSequenceInterval, dns01.DefaultPollingInterval), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for myaddr.{tools,dev,io}. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvPrivateKeysMapping) + if err != nil { + return nil, fmt.Errorf("myaddr: %w", err) + } + + config := NewDefaultConfig() + + credentials, err := parseCredentials(values[EnvPrivateKeysMapping]) + if err != nil { + return nil, fmt.Errorf("myaddr: %w", err) + } + + config.Credentials = credentials + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for myaddr.{tools,dev,io}. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("myaddr: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.Credentials) + if err != nil { + return nil, fmt.Errorf("myaddr: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("myaddr: could not find zone for domain %q: %w", domain, err) + } + + fullSubdomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("myaddr: %w", err) + } + + _, after, found := strings.Cut(fullSubdomain, ".") + if !found { + return fmt.Errorf("myaddr: subdomain not found in: %q (%s)", fullSubdomain, info.EffectiveFQDN) + } + + err = d.client.AddTXTRecord(context.Background(), after, info.Value) + if err != nil { + return fmt.Errorf("myaddr: add TXT record: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + // There is no API endpoint to delete a TXT record: + // TXT records are automatically removed after a few minutes. + 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 +} + +// Sequential All DNS challenges for this provider will be resolved sequentially. +// Returns the interval between each iteration. +func (d *DNSProvider) Sequential() time.Duration { + return d.config.SequenceInterval +} + +func parseCredentials(raw string) (map[string]string, error) { + credentials := make(map[string]string) + + credStrings := strings.Split(strings.TrimSuffix(raw, ","), ",") + for _, credPair := range credStrings { + data := strings.Split(credPair, ":") + if len(data) != 2 { + return nil, fmt.Errorf("incorrect credential pair: %s", credPair) + } + + credentials[strings.TrimSpace(data[0])] = strings.TrimSpace(data[1]) + } + + return credentials, nil +} diff --git a/providers/dns/myaddr/myaddr.toml b/providers/dns/myaddr/myaddr.toml new file mode 100644 index 000000000..5ff306526 --- /dev/null +++ b/providers/dns/myaddr/myaddr.toml @@ -0,0 +1,23 @@ +Name = "myaddr.{tools,dev,io}" +Description = '''''' +URL = "https://myaddr.tools/" +Code = "myaddr" +Since = "v4.22.0" + +Example = ''' +MYADDR_PRIVATE_KEYS_MAPPING="example:123,test:456" \ +lego --email you@example.com --dns myaddr -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + MYADDR_PRIVATE_KEYS_MAPPING = "Mapping between subdomains and private keys. The format is: `:,:,:`" + [Configuration.Additional] + MYADDR_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + MYADDR_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + MYADDR_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 2)" + MYADDR_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + MYADDR_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://myaddr.tools/" diff --git a/providers/dns/myaddr/myaddr_test.go b/providers/dns/myaddr/myaddr_test.go new file mode 100644 index 000000000..d95a0cf5c --- /dev/null +++ b/providers/dns/myaddr/myaddr_test.go @@ -0,0 +1,113 @@ +package myaddr + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvPrivateKeysMapping).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvPrivateKeysMapping: "example:123", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "myaddr: some credentials information are missing: MYADDR_PRIVATE_KEYS_MAPPING", + }, + } + + 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 + credentials map[string]string + expected string + }{ + { + desc: "success", + credentials: map[string]string{"example": "123"}, + }, + { + desc: "missing credentials", + expected: "myaddr: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Credentials = test.credentials + + 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 053c3c4e7..762f69832 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -90,6 +90,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/metaname" "github.com/go-acme/lego/v4/providers/dns/mijnhost" "github.com/go-acme/lego/v4/providers/dns/mittwald" + "github.com/go-acme/lego/v4/providers/dns/myaddr" "github.com/go-acme/lego/v4/providers/dns/mydnsjp" "github.com/go-acme/lego/v4/providers/dns/mythicbeasts" "github.com/go-acme/lego/v4/providers/dns/namecheap" @@ -324,6 +325,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return mijnhost.NewDNSProvider() case "mittwald": return mittwald.NewDNSProvider() + case "myaddr": + return myaddr.NewDNSProvider() case "mydnsjp": return mydnsjp.NewDNSProvider() case "mythicbeasts": From 2e497ca928005b2f70d94d0b451791095faebf1f Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 28 Jan 2025 18:02:51 +0100 Subject: [PATCH 034/298] fix(cli): remove extra debug logs (#2412) --- cmd/setup.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/setup.go b/cmd/setup.go index 3fc05038e..571259c9e 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -69,6 +69,7 @@ func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyTy retryClient := retryablehttp.NewClient() retryClient.RetryMax = 5 retryClient.HTTPClient = config.HTTPClient + retryClient.Logger = nil config.HTTPClient = retryClient.StandardClient() From dc992b8d874af8a4fa83bc477a40a9f499bfb8d2 Mon Sep 17 00:00:00 2001 From: bllfr0g <39714379+bllfr0g@users.noreply.github.com> Date: Tue, 28 Jan 2025 23:55:36 -0800 Subject: [PATCH 035/298] feat: add support for Profiles Extension (#2415) Co-authored-by: Fernandez Ludovic --- .github/workflows/pr.yml | 4 +- README.md | 1 + acme/api/order.go | 10 +++ acme/commons.go | 11 ++++ certificate/certificates.go | 35 ++++++++--- cmd/cmd_renew.go | 6 ++ cmd/cmd_run.go | 7 +++ docs/content/_index.md | 3 +- e2e/challenges_test.go | 83 +++++++++++++++++++++++++ e2e/dnschallenge/dns_challenges_test.go | 55 ++++++++++++++++ e2e/fixtures/pebble-config-dns.json | 12 +++- e2e/fixtures/pebble-config.json | 12 +++- 12 files changed, 226 insertions(+), 13 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 891e261ed..a8f2ff452 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -46,10 +46,10 @@ jobs: golangci-lint --version - name: Install Pebble - run: go install github.com/letsencrypt/pebble/v2/cmd/pebble@3fe019bbc0a41ed16e2fee31592bb91751acaa47 + run: go install github.com/letsencrypt/pebble/v2/cmd/pebble@v2.7.0 - name: Install challtestsrv - run: go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@3fe019bbc0a41ed16e2fee31592bb91751acaa47 + run: go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@v2.7.0 - name: Set up a Memcached server uses: niden/actions-memcached@v7 diff --git a/README.md b/README.md index b779a5863..cdf1805f1 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Let's Encrypt client and ACME library written in Go. - Support [RFC 8737](https://www.rfc-editor.org/rfc/rfc8737.html): TLS Application‑Layer Protocol Negotiation (ALPN) Challenge Extension - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): certificates for IP addresses - Support [draft-ietf-acme-ari-03](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/): Renewal Information (ARI) Extension + - Support [draft-aaron-acme-profiles-00](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/): Profiles Extension - Register with CA - Obtain certificates, both from scratch or with an existing CSR - Renew certificates diff --git a/acme/api/order.go b/acme/api/order.go index 5179d061a..708c35632 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -13,6 +13,12 @@ import ( type OrderOptions struct { NotBefore time.Time NotAfter time.Time + + // A string uniquely identifying the profile + // which will be used to affect issuance of the certificate requested by this Order. + // - https://www.ietf.org/id/draft-aaron-acme-profiles-00.html#section-4 + Profile string + // A string uniquely identifying a previously-issued certificate which this // order is intended to replace. // - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5 @@ -53,6 +59,10 @@ func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acm if o.core.GetDirectory().RenewalInfo != "" { orderReq.Replaces = opts.ReplacesCertID } + + if opts.Profile != "" { + orderReq.Profile = opts.Profile + } } var order acme.Order diff --git a/acme/commons.go b/acme/commons.go index 39aa35ac8..918cc605f 100644 --- a/acme/commons.go +++ b/acme/commons.go @@ -74,6 +74,11 @@ type Meta struct { // then the CA requires that all new-account requests include an "externalAccountBinding" field // associating the new account with an external account. ExternalAccountRequired bool `json:"externalAccountRequired"` + + // profiles (optional, object): + // A map of profile names to human-readable descriptions of those profiles. + // https://www.ietf.org/id/draft-aaron-acme-profiles-00.html#section-3 + Profiles map[string]string `json:"profiles"` } // ExtendedAccount an extended Account. @@ -148,6 +153,12 @@ type Order struct { // An array of identifier objects that the order pertains to. Identifiers []Identifier `json:"identifiers"` + // profile (string, optional): + // A string uniquely identifying the profile + // which will be used to affect issuance of the certificate requested by this Order. + // https://www.ietf.org/id/draft-aaron-acme-profiles-00.html#section-4 + Profile string `json:"profile,omitempty"` + // notBefore (optional, string): // The requested value of the notBefore field in the certificate, // in the date format defined in [RFC3339]. diff --git a/certificate/certificates.go b/certificate/certificates.go index fc139937b..3d0483b1a 100644 --- a/certificate/certificates.go +++ b/certificate/certificates.go @@ -69,11 +69,18 @@ type ObtainRequest struct { PrivateKey crypto.PrivateKey MustStaple bool - NotBefore time.Time - NotAfter time.Time - Bundle bool - PreferredChain string + NotBefore time.Time + NotAfter time.Time + Bundle bool + PreferredChain string + + // A string uniquely identifying the profile + // which will be used to affect issuance of the certificate requested by this Order. + // - https://www.ietf.org/id/draft-aaron-acme-profiles-00.html#section-4 + Profile string + AlwaysDeactivateAuthorizations bool + // A string uniquely identifying a previously-issued certificate which this // order is intended to replace. // - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5 @@ -89,11 +96,18 @@ type ObtainRequest struct { type ObtainForCSRRequest struct { CSR *x509.CertificateRequest - NotBefore time.Time - NotAfter time.Time - Bundle bool - PreferredChain string + NotBefore time.Time + NotAfter time.Time + Bundle bool + PreferredChain string + + // A string uniquely identifying the profile + // which will be used to affect issuance of the certificate requested by this Order. + // - https://www.ietf.org/id/draft-aaron-acme-profiles-00.html#section-4 + Profile string + AlwaysDeactivateAuthorizations bool + // A string uniquely identifying a previously-issued certificate which this // order is intended to replace. // - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5 @@ -154,6 +168,7 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) { orderOpts := &api.OrderOptions{ NotBefore: request.NotBefore, NotAfter: request.NotAfter, + Profile: request.Profile, ReplacesCertID: request.ReplacesCertID, } @@ -220,6 +235,7 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error) orderOpts := &api.OrderOptions{ NotBefore: request.NotBefore, NotAfter: request.NotAfter, + Profile: request.Profile, ReplacesCertID: request.ReplacesCertID, } @@ -437,6 +453,7 @@ type RenewOptions struct { // If true, the []byte contains both the issuer certificate and your issued certificate as a bundle. Bundle bool PreferredChain string + Profile string AlwaysDeactivateAuthorizations bool // Not supported for CSR request. MustStaple bool @@ -505,6 +522,7 @@ func (c *Certifier) RenewWithOptions(certRes Resource, options *RenewOptions) (* request.NotAfter = options.NotAfter request.Bundle = options.Bundle request.PreferredChain = options.PreferredChain + request.Profile = options.Profile request.AlwaysDeactivateAuthorizations = options.AlwaysDeactivateAuthorizations } @@ -530,6 +548,7 @@ func (c *Certifier) RenewWithOptions(certRes Resource, options *RenewOptions) (* request.NotAfter = options.NotAfter request.Bundle = options.Bundle request.PreferredChain = options.PreferredChain + request.Profile = options.Profile request.AlwaysDeactivateAuthorizations = options.AlwaysDeactivateAuthorizations } diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index f75678ba6..8d2135022 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -92,6 +92,10 @@ func createRenew() *cli.Command { Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name." + " If no match, the default offered chain will be used.", }, + &cli.StringFlag{ + Name: flgProfile, + Usage: "If the CA offers multiple certificate profiles (draft-aaron-acme-profiles), choose this one.", + }, &cli.StringFlag{ Name: flgAlwaysDeactivateAuthorizations, Usage: "Force the authorizations to be relinquished even if the certificate request was successful.", @@ -234,6 +238,7 @@ func renewForDomains(ctx *cli.Context, account *Account, keyType certcrypto.KeyT NotAfter: getTime(ctx, flgNotAfter), Bundle: bundle, PreferredChain: ctx.String(flgPreferredChain), + Profile: ctx.String(flgProfile), AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations), } @@ -317,6 +322,7 @@ func renewForCSR(ctx *cli.Context, account *Account, keyType certcrypto.KeyType, NotAfter: getTime(ctx, flgNotAfter), Bundle: bundle, PreferredChain: ctx.String(flgPreferredChain), + Profile: ctx.String(flgProfile), AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations), } diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index 8b57317ec..13757ca2f 100644 --- a/cmd/cmd_run.go +++ b/cmd/cmd_run.go @@ -21,6 +21,7 @@ const ( flgNotBefore = "not-before" flgNotAfter = "not-after" flgPreferredChain = "preferred-chain" + flgProfile = "profile" flgAlwaysDeactivateAuthorizations = "always-deactivate-authorizations" flgRunHook = "run-hook" flgRunHookTimeout = "run-hook-timeout" @@ -68,6 +69,10 @@ func createRun() *cli.Command { Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name." + " If no match, the default offered chain will be used.", }, + &cli.StringFlag{ + Name: flgProfile, + Usage: "If the CA offers multiple certificate profiles (draft-aaron-acme-profiles), choose this one.", + }, &cli.StringFlag{ Name: flgAlwaysDeactivateAuthorizations, Usage: "Force the authorizations to be relinquished even if the certificate request was successful.", @@ -201,6 +206,7 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso Bundle: bundle, MustStaple: ctx.Bool(flgMustStaple), PreferredChain: ctx.String(flgPreferredChain), + Profile: ctx.String(flgProfile), AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations), } @@ -230,6 +236,7 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso NotAfter: getTime(ctx, flgNotAfter), Bundle: bundle, PreferredChain: ctx.String(flgPreferredChain), + Profile: ctx.String(flgProfile), AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations), } diff --git a/docs/content/_index.md b/docs/content/_index.md index 6d9fc3f1a..7cb903aac 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -12,7 +12,8 @@ Let's Encrypt client and ACME library written in Go. - ACME v2 [RFC 8555](https://www.rfc-editor.org/rfc/rfc8555.html) - Support [RFC 8737](https://www.rfc-editor.org/rfc/rfc8737.html): TLS Application‑Layer Protocol Negotiation (ALPN) Challenge Extension - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): issues certificates for IP addresses - - Support [draft-ietf-acme-ari-01](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/): Renewal Information (ARI) Extension + - Support [draft-ietf-acme-ari-03](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/): Renewal Information (ARI) Extension + - Support [draft-aaron-acme-profiles-00](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/): Profiles Extension - Register with CA - Obtain certificates, both from scratch or with an existing CSR - Renew certificates diff --git a/e2e/challenges_test.go b/e2e/challenges_test.go index cbf364c57..c54e6a723 100644 --- a/e2e/challenges_test.go +++ b/e2e/challenges_test.go @@ -257,6 +257,45 @@ func TestChallengeHTTP_Client_Obtain(t *testing.T) { assert.Empty(t, resource.CSR) } +func TestChallengeHTTP_Client_Obtain_profile(t *testing.T) { + err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") + require.NoError(t, err) + defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err, "Could not generate test key") + + user := &fakeUser{privateKey: privateKey} + config := lego.NewConfig(user) + config.CADirURL = load.PebbleOptions.HealthCheckURL + + client, err := lego.NewClient(config) + require.NoError(t, err) + + err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "5002")) + require.NoError(t, err) + + reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) + require.NoError(t, err) + user.registration = reg + + request := certificate.ObtainRequest{ + Domains: []string{"acme.wtf"}, + Bundle: true, + Profile: "shortlived", + } + resource, err := client.Certificate.Obtain(request) + require.NoError(t, err) + + require.NotNil(t, resource) + assert.Equal(t, "acme.wtf", resource.Domain) + assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) + assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) + assert.NotEmpty(t, resource.Certificate) + assert.NotEmpty(t, resource.IssuerCertificate) + assert.Empty(t, resource.CSR) +} + func TestChallengeHTTP_Client_Obtain_notBefore_notAfter(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) @@ -422,6 +461,50 @@ func TestChallengeTLS_Client_ObtainForCSR(t *testing.T) { assert.NotEmpty(t, resource.CSR) } +func TestChallengeTLS_Client_ObtainForCSR_profile(t *testing.T) { + err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") + require.NoError(t, err) + defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err, "Could not generate test key") + + user := &fakeUser{privateKey: privateKey} + config := lego.NewConfig(user) + config.CADirURL = load.PebbleOptions.HealthCheckURL + + client, err := lego.NewClient(config) + require.NoError(t, err) + + err = client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", "5001")) + require.NoError(t, err) + + reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) + require.NoError(t, err) + user.registration = reg + + csrRaw, err := os.ReadFile("./fixtures/csr.raw") + require.NoError(t, err) + + csr, err := x509.ParseCertificateRequest(csrRaw) + require.NoError(t, err) + + resource, err := client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{ + CSR: csr, + Bundle: true, + Profile: "shortlived", + }) + require.NoError(t, err) + + require.NotNil(t, resource) + assert.Equal(t, "acme.wtf", resource.Domain) + assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) + assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) + assert.NotEmpty(t, resource.Certificate) + assert.NotEmpty(t, resource.IssuerCertificate) + assert.NotEmpty(t, resource.CSR) +} + func TestRegistrar_UpdateAccount(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) diff --git a/e2e/dnschallenge/dns_challenges_test.go b/e2e/dnschallenge/dns_challenges_test.go index 605a77bd0..4efa9f95e 100644 --- a/e2e/dnschallenge/dns_challenges_test.go +++ b/e2e/dnschallenge/dns_challenges_test.go @@ -124,6 +124,61 @@ func TestChallengeDNS_Client_Obtain(t *testing.T) { assert.Empty(t, resource.CSR) } +func TestChallengeDNS_Client_Obtain_profile(t *testing.T) { + err := os.Setenv("LEGO_CA_CERTIFICATES", "../fixtures/certs/pebble.minica.pem") + require.NoError(t, err) + defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() + + err = os.Setenv("EXEC_PATH", "../fixtures/update-dns.sh") + require.NoError(t, err) + defer func() { _ = os.Unsetenv("EXEC_PATH") }() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err, "Could not generate test key") + + user := &fakeUser{privateKey: privateKey} + config := lego.NewConfig(user) + config.CADirURL = "https://localhost:15000/dir" + + client, err := lego.NewClient(config) + require.NoError(t, err) + + provider, err := dns.NewDNSChallengeProviderByName("exec") + require.NoError(t, err) + + err = client.Challenge.SetDNS01Provider(provider, + dns01.AddRecursiveNameservers([]string{":8053"}), + dns01.DisableAuthoritativeNssPropagationRequirement()) + require.NoError(t, err) + + reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) + require.NoError(t, err) + user.registration = reg + + domains := []string{"*.légo.acme", "légo.acme"} + + // https://github.com/letsencrypt/pebble/issues/285 + privateKeyCSR, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err, "Could not generate test key") + + request := certificate.ObtainRequest{ + Domains: domains, + Bundle: true, + PrivateKey: privateKeyCSR, + Profile: "shortlived", + } + resource, err := client.Certificate.Obtain(request) + require.NoError(t, err) + + require.NotNil(t, resource) + assert.Equal(t, "*.xn--lgo-bma.acme", resource.Domain) + assert.Regexp(t, `https://localhost:15000/certZ/[\w\d]{14,}`, resource.CertURL) + assert.Regexp(t, `https://localhost:15000/certZ/[\w\d]{14,}`, resource.CertStableURL) + assert.NotEmpty(t, resource.Certificate) + assert.NotEmpty(t, resource.IssuerCertificate) + assert.Empty(t, resource.CSR) +} + type fakeUser struct { email string privateKey crypto.PrivateKey diff --git a/e2e/fixtures/pebble-config-dns.json b/e2e/fixtures/pebble-config-dns.json index 4834825a4..dd5b63142 100644 --- a/e2e/fixtures/pebble-config-dns.json +++ b/e2e/fixtures/pebble-config-dns.json @@ -4,6 +4,16 @@ "certificate": "fixtures/certs/localhost/cert.pem", "privateKey": "fixtures/certs/localhost/key.pem", "httpPort": 5004, - "tlsPort": 5003 + "tlsPort": 5003, + "profiles": { + "default": { + "description": "The profile you know and love", + "validityPeriod": 7776000 + }, + "shortlived": { + "description": "A short-lived cert profile, without actual enforcement", + "validityPeriod": 518400 + } + } } } diff --git a/e2e/fixtures/pebble-config.json b/e2e/fixtures/pebble-config.json index f2abe6ab8..dcf659b4c 100644 --- a/e2e/fixtures/pebble-config.json +++ b/e2e/fixtures/pebble-config.json @@ -4,6 +4,16 @@ "certificate": "fixtures/certs/localhost/cert.pem", "privateKey": "fixtures/certs/localhost/key.pem", "httpPort": 5002, - "tlsPort": 5001 + "tlsPort": 5001, + "profiles": { + "default": { + "description": "The profile you know and love", + "validityPeriod": 7776000 + }, + "shortlived": { + "description": "A short-lived cert profile, without actual enforcement", + "validityPeriod": 518400 + } + } } } From 7cd008e80ad6b7623b80458096694a86e9429b1a Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 4 Feb 2025 13:43:51 +0100 Subject: [PATCH 036/298] feat(cli): add LEGO_DEBUG_ACME_HTTP_CLIENT to debug the calls to the ACME server (#2420) --- certificate/authorization.go | 2 +- cmd/cmd_run.go | 4 ++-- cmd/setup.go | 4 ++++ docs/content/usage/cli/Options.md | 10 ++++++++++ e2e/challenges_test.go | 1 + e2e/dnschallenge/dns_challenges_test.go | 1 + 6 files changed, 19 insertions(+), 3 deletions(-) diff --git a/certificate/authorization.go b/certificate/authorization.go index 5118912f8..c77bcbd5f 100644 --- a/certificate/authorization.go +++ b/certificate/authorization.go @@ -52,7 +52,7 @@ func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder, force boo for _, authzURL := range order.Authorizations { auth, err := c.core.Authorizations.Get(authzURL) if err != nil { - log.Infof("Unable to get the authorization for: %s", authzURL) + log.Infof("Unable to get the authorization for %s: %v", authzURL, err) continue } diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index 13757ca2f..6d76e7ad8 100644 --- a/cmd/cmd_run.go +++ b/cmd/cmd_run.go @@ -92,12 +92,12 @@ func createRun() *cli.Command { const rootPathWarningMessage = `!!!! HEADS UP !!!! -Your account credentials have been saved in your Let's Encrypt +Your account credentials have been saved in your configuration directory at "%s". You should make a secure backup of this folder now. This configuration directory will also contain certificates and -private keys obtained from Let's Encrypt so making regular +private keys obtained from the ACME server so making regular backups of this folder is ideal. ` diff --git a/cmd/setup.go b/cmd/setup.go index 571259c9e..28c2c8eef 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -71,6 +71,10 @@ func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyTy retryClient.HTTPClient = config.HTTPClient retryClient.Logger = nil + if _, v := os.LookupEnv("LEGO_DEBUG_ACME_HTTP_CLIENT"); v { + retryClient.Logger = log.Logger + } + config.HTTPClient = retryClient.StandardClient() client, err := lego.NewClient(config) diff --git a/docs/content/usage/cli/Options.md b/docs/content/usage/cli/Options.md index a6484de23..25ba7e593 100644 --- a/docs/content/usage/cli/Options.md +++ b/docs/content/usage/cli/Options.md @@ -142,3 +142,13 @@ Example: ```bash LEGO_DEBUG_CLIENT_VERBOSE_ERROR=true ``` + +### LEGO_DEBUG_ACME_HTTP_CLIENT + +The environment variable `LEGO_DEBUG_ACME_HTTP_CLIENT` allows debug the calls to the ACME server. + +Example: + +```bash +LEGO_DEBUG_ACME_HTTP_CLIENT=true +``` diff --git a/e2e/challenges_test.go b/e2e/challenges_test.go index c54e6a723..6f5de0d0f 100644 --- a/e2e/challenges_test.go +++ b/e2e/challenges_test.go @@ -29,6 +29,7 @@ var load = loader.EnvLoader{ }, LegoOptions: []string{ "LEGO_CA_CERTIFICATES=./fixtures/certs/pebble.minica.pem", + "LEGO_DEBUG_ACME_HTTP_CLIENT=1", }, } diff --git a/e2e/dnschallenge/dns_challenges_test.go b/e2e/dnschallenge/dns_challenges_test.go index 4efa9f95e..2c228230d 100644 --- a/e2e/dnschallenge/dns_challenges_test.go +++ b/e2e/dnschallenge/dns_challenges_test.go @@ -28,6 +28,7 @@ var load = loader.EnvLoader{ LegoOptions: []string{ "LEGO_CA_CERTIFICATES=../fixtures/certs/pebble.minica.pem", "EXEC_PATH=../fixtures/update-dns.sh", + "LEGO_DEBUG_ACME_HTTP_CLIENT=1", }, ChallSrv: &loader.CmdOption{ Args: []string{"-http01", ":5012", "-tlsalpn01", ":5011"}, From e644196bfc422de8590c4cd54a2ddf99bb46df0e Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 5 Feb 2025 23:52:48 +0100 Subject: [PATCH 037/298] feat: add delay option for HTTP challenge (#2422) --- challenge/http01/http_challenge.go | 29 ++++++++++++++++++++++++++-- challenge/resolver/solver_manager.go | 4 ++-- cmd/flags.go | 6 ++++++ cmd/setup_challenges.go | 2 +- docs/data/zz_cli_help.toml | 3 +++ 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/challenge/http01/http_challenge.go b/challenge/http01/http_challenge.go index f23e483cf..79dbfb4d0 100644 --- a/challenge/http01/http_challenge.go +++ b/challenge/http01/http_challenge.go @@ -2,6 +2,7 @@ package http01 import ( "fmt" + "time" "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api" @@ -11,6 +12,16 @@ import ( type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error +type ChallengeOption func(*Challenge) error + +// SetDelay sets a delay between the start of the HTTP server and the challenge validation. +func SetDelay(delay time.Duration) ChallengeOption { + return func(chlg *Challenge) error { + chlg.delay = delay + return nil + } +} + // ChallengePath returns the URL path for the `http-01` challenge. func ChallengePath(token string) string { return "/.well-known/acme-challenge/" + token @@ -20,14 +31,24 @@ type Challenge struct { core *api.Core validate ValidateFunc provider challenge.Provider + delay time.Duration } -func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge { - return &Challenge{ +func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider, opts ...ChallengeOption) *Challenge { + chlg := &Challenge{ core: core, validate: validate, provider: provider, } + + for _, opt := range opts { + err := opt(chlg) + if err != nil { + log.Infof("challenge option error: %v", err) + } + } + + return chlg } func (c *Challenge) SetProvider(provider challenge.Provider) { @@ -60,6 +81,10 @@ func (c *Challenge) Solve(authz acme.Authorization) error { } }() + if c.delay > 0 { + time.Sleep(c.delay) + } + chlng.KeyAuthorization = keyAuth return c.validate(c.core, domain, chlng) } diff --git a/challenge/resolver/solver_manager.go b/challenge/resolver/solver_manager.go index 138060bc7..ee6bafb49 100644 --- a/challenge/resolver/solver_manager.go +++ b/challenge/resolver/solver_manager.go @@ -36,8 +36,8 @@ func NewSolversManager(core *api.Core) *SolverManager { } // SetHTTP01Provider specifies a custom provider p that can solve the given HTTP-01 challenge. -func (c *SolverManager) SetHTTP01Provider(p challenge.Provider) error { - c.solvers[challenge.HTTP01] = http01.NewChallenge(c.core, validate, p) +func (c *SolverManager) SetHTTP01Provider(p challenge.Provider, opts ...http01.ChallengeOption) error { + c.solvers[challenge.HTTP01] = http01.NewChallenge(c.core, validate, p, opts...) return nil } diff --git a/cmd/flags.go b/cmd/flags.go index 48e63d172..51aa479b6 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -25,6 +25,7 @@ const ( flgPath = "path" flgHTTP = "http" flgHTTPPort = "http.port" + flgHTTPDelay = "http.delay" flgHTTPProxyHeader = "http.proxy-header" flgHTTPWebroot = "http.webroot" flgHTTPMemcachedHost = "http.memcached-host" @@ -131,6 +132,11 @@ func CreateFlags(defaultPath string) []cli.Flag { Usage: "Set the port and interface to use for HTTP-01 based challenges to listen on. Supported: interface:port or :port.", Value: ":80", }, + &cli.DurationFlag{ + Name: flgHTTPDelay, + Usage: "Delay between the starts of the HTTP server (use for HTTP-01 based challenges) and the validation of the challenge.", + Value: 0, + }, &cli.StringFlag{ Name: flgHTTPProxyHeader, Usage: "Validate against this HTTP header when solving HTTP-01 based challenges behind a reverse proxy.", diff --git a/cmd/setup_challenges.go b/cmd/setup_challenges.go index 0a59099a8..16a5b90fe 100644 --- a/cmd/setup_challenges.go +++ b/cmd/setup_challenges.go @@ -25,7 +25,7 @@ func setupChallenges(ctx *cli.Context, client *lego.Client) { } if ctx.Bool(flgHTTP) { - err := client.Challenge.SetHTTP01Provider(setupHTTPProvider(ctx)) + err := client.Challenge.SetHTTP01Provider(setupHTTPProvider(ctx), http01.SetDelay(ctx.Duration(flgHTTPDelay))) if err != nil { log.Fatal(err) } diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 11b48a0d5..f15eacda1 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -32,6 +32,7 @@ GLOBAL OPTIONS: --path value Directory to use for storing the data. (default: "./.lego") [$LEGO_PATH] --http Use the HTTP-01 challenge to solve challenges. Can be mixed with other types of challenges. (default: false) --http.port value Set the port and interface to use for HTTP-01 based challenges to listen on. Supported: interface:port or :port. (default: ":80") + --http.delay value Delay between the starts of the HTTP server (use for HTTP-01 based challenges) and the validation of the challenge. (default: 0s) --http.proxy-header value Validate against this HTTP header when solving HTTP-01 based challenges behind a reverse proxy. (default: "Host") --http.webroot value Set the webroot folder to use for HTTP-01 based challenges to write directly to the .well-known/acme-challenge file. This disables the built-in server and expects the given directory to be publicly served with access to .well-known/acme-challenge --http.memcached-host value [ --http.memcached-host value ] Set the memcached host(s) to use for HTTP-01 based challenges. Challenges will be written to all specified hosts. @@ -72,6 +73,7 @@ OPTIONS: --not-before value Set the notBefore field in the certificate (RFC3339 format) --not-after value Set the notAfter field in the certificate (RFC3339 format) --preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. + --profile value If the CA offers multiple certificate profiles (draft-aaron-acme-profiles), choose this one. --always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful. --run-hook value Define a hook. The hook is executed when the certificates are effectively created. --run-hook-timeout value Define the timeout for the hook execution. (default: 2m0s) @@ -97,6 +99,7 @@ OPTIONS: --not-before value Set the notBefore field in the certificate (RFC3339 format) --not-after value Set the notAfter field in the certificate (RFC3339 format) --preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. + --profile value If the CA offers multiple certificate profiles (draft-aaron-acme-profiles), choose this one. --always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful. --renew-hook value Define a hook. The hook is executed only when the certificates are effectively renewed. --renew-hook-timeout value Define the timeout for the hook execution. (default: 2m0s) From 4349dfc5eb05627798c7497405cda9bb2b2d61c1 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 6 Feb 2025 19:00:45 +0100 Subject: [PATCH 038/298] feat: option to set CSR emails (#2423) --- certcrypto/crypto.go | 27 +++++++++++--- certcrypto/crypto_test.go | 70 ++++++++++++++++++++++++------------- certificate/certificates.go | 37 +++++++++++++------- e2e/challenges_test.go | 39 +++++++++++++++++++++ 4 files changed, 131 insertions(+), 42 deletions(-) diff --git a/certcrypto/crypto.go b/certcrypto/crypto.go index 43fa774ae..c6551071e 100644 --- a/certcrypto/crypto.go +++ b/certcrypto/crypto.go @@ -135,10 +135,26 @@ func GeneratePrivateKey(keyType KeyType) (crypto.PrivateKey, error) { return nil, fmt.Errorf("invalid KeyType: %s", keyType) } +// Deprecated: uses [CreateCSR] instead. func GenerateCSR(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) { + return CreateCSR(privateKey, CSROptions{ + Domain: domain, + SAN: san, + MustStaple: mustStaple, + }) +} + +type CSROptions struct { + Domain string + SAN []string + MustStaple bool + EmailAddresses []string +} + +func CreateCSR(privateKey crypto.PrivateKey, opts CSROptions) ([]byte, error) { var dnsNames []string var ipAddresses []net.IP - for _, altname := range san { + for _, altname := range opts.SAN { if ip := net.ParseIP(altname); ip != nil { ipAddresses = append(ipAddresses, ip) } else { @@ -147,12 +163,13 @@ func GenerateCSR(privateKey crypto.PrivateKey, domain string, san []string, must } template := x509.CertificateRequest{ - Subject: pkix.Name{CommonName: domain}, - DNSNames: dnsNames, - IPAddresses: ipAddresses, + Subject: pkix.Name{CommonName: opts.Domain}, + DNSNames: dnsNames, + EmailAddresses: opts.EmailAddresses, + IPAddresses: ipAddresses, } - if mustStaple { + if opts.MustStaple { template.ExtraExtensions = append(template.ExtraExtensions, pkix.Extension{ Id: tlsFeatureExtensionOID, Value: ocspMustStapleFeature, diff --git a/certcrypto/crypto_test.go b/certcrypto/crypto_test.go index 7aba8b378..4768435d1 100644 --- a/certcrypto/crypto_test.go +++ b/certcrypto/crypto_test.go @@ -33,55 +33,75 @@ func TestGenerateCSR(t *testing.T) { testCases := []struct { desc string privateKey crypto.PrivateKey - domain string - san []string - mustStaple bool + opts CSROptions expected expected }{ { desc: "without SAN (nil)", privateKey: privateKey, - domain: "lego.acme", - mustStaple: true, - expected: expected{len: 245}, + opts: CSROptions{ + Domain: "lego.acme", + MustStaple: true, + }, + expected: expected{len: 245}, }, { desc: "without SAN (empty)", privateKey: privateKey, - domain: "lego.acme", - san: []string{}, - mustStaple: true, - expected: expected{len: 245}, + opts: CSROptions{ + Domain: "lego.acme", + SAN: []string{}, + MustStaple: true, + }, + expected: expected{len: 245}, }, { desc: "with SAN", privateKey: privateKey, - domain: "lego.acme", - san: []string{"a.lego.acme", "b.lego.acme", "c.lego.acme"}, - mustStaple: true, - expected: expected{len: 296}, + opts: CSROptions{ + Domain: "lego.acme", + SAN: []string{"a.lego.acme", "b.lego.acme", "c.lego.acme"}, + MustStaple: true, + }, + expected: expected{len: 296}, }, { desc: "no domain", privateKey: privateKey, - domain: "", - mustStaple: true, - expected: expected{len: 225}, + opts: CSROptions{ + Domain: "", + MustStaple: true, + }, + expected: expected{len: 225}, }, { desc: "no domain with SAN", privateKey: privateKey, - domain: "", - san: []string{"a.lego.acme", "b.lego.acme", "c.lego.acme"}, - mustStaple: true, - expected: expected{len: 276}, + opts: CSROptions{ + Domain: "", + SAN: []string{"a.lego.acme", "b.lego.acme", "c.lego.acme"}, + MustStaple: true, + }, + expected: expected{len: 276}, }, { desc: "private key nil", privateKey: nil, - domain: "fizz.buzz", - mustStaple: true, - expected: expected{error: true}, + opts: CSROptions{ + Domain: "fizz.buzz", + MustStaple: true, + }, + expected: expected{error: true}, + }, + { + desc: "with email addresses", + privateKey: privateKey, + opts: CSROptions{ + Domain: "example.com", + SAN: []string{"example.org"}, + EmailAddresses: []string{"foo@example.com", "bar@example.com"}, + }, + expected: expected{len: 287}, }, } @@ -89,7 +109,7 @@ func TestGenerateCSR(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - csr, err := GenerateCSR(test.privateKey, test.domain, test.san, test.mustStaple) + csr, err := CreateCSR(test.privateKey, test.opts) if test.expected.error { require.Error(t, err) diff --git a/certificate/certificates.go b/certificate/certificates.go index 3d0483b1a..56c081701 100644 --- a/certificate/certificates.go +++ b/certificate/certificates.go @@ -65,9 +65,10 @@ type Resource struct { // If `AlwaysDeactivateAuthorizations` is true, the authorizations are also relinquished if the obtain request was successful. // See https://datatracker.ietf.org/doc/html/rfc8555#section-7.5.2. type ObtainRequest struct { - Domains []string - PrivateKey crypto.PrivateKey - MustStaple bool + Domains []string + PrivateKey crypto.PrivateKey + MustStaple bool + EmailAddresses []string NotBefore time.Time NotAfter time.Time @@ -194,7 +195,7 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) { log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", ")) failures := newObtainError() - cert, err := c.getForOrder(domains, order, request.Bundle, request.PrivateKey, request.MustStaple, request.PreferredChain) + cert, err := c.getForOrder(domains, order, request) if err != nil { for _, auth := range authz { failures.Add(challenge.GetTargetedDomain(auth), err) @@ -280,7 +281,9 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error) return cert, failures.Join() } -func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bundle bool, privateKey crypto.PrivateKey, mustStaple bool, preferredChain string) (*Resource, error) { +func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, request ObtainRequest) (*Resource, error) { + privateKey := request.PrivateKey + if privateKey == nil { var err error privateKey, err = certcrypto.GeneratePrivateKey(c.options.KeyType) @@ -312,13 +315,19 @@ func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bund } } - // TODO: should the CSR be customizable? - csr, err := certcrypto.GenerateCSR(privateKey, commonName, san, mustStaple) + csrOptions := certcrypto.CSROptions{ + Domain: commonName, + SAN: san, + MustStaple: request.MustStaple, + EmailAddresses: request.EmailAddresses, + } + + csr, err := certcrypto.CreateCSR(privateKey, csrOptions) if err != nil { return nil, err } - return c.getForCSR(domains, order, bundle, csr, certcrypto.PEMEncode(privateKey), preferredChain) + return c.getForCSR(domains, order, request.Bundle, csr, certcrypto.PEMEncode(privateKey), request.PreferredChain) } func (c *Certifier) getForCSR(domains []string, order acme.ExtendedOrder, bundle bool, csr, privateKeyPem []byte, preferredChain string) (*Resource, error) { @@ -451,12 +460,15 @@ type RenewOptions struct { NotBefore time.Time NotAfter time.Time // If true, the []byte contains both the issuer certificate and your issued certificate as a bundle. - Bundle bool - PreferredChain string - Profile string + Bundle bool + PreferredChain string + + Profile string + AlwaysDeactivateAuthorizations bool // Not supported for CSR request. - MustStaple bool + MustStaple bool + EmailAddresses []string } // Renew takes a Resource and tries to renew the certificate. @@ -548,6 +560,7 @@ func (c *Certifier) RenewWithOptions(certRes Resource, options *RenewOptions) (* request.NotAfter = options.NotAfter request.Bundle = options.Bundle request.PreferredChain = options.PreferredChain + request.EmailAddresses = options.EmailAddresses request.Profile = options.Profile request.AlwaysDeactivateAuthorizations = options.AlwaysDeactivateAuthorizations } diff --git a/e2e/challenges_test.go b/e2e/challenges_test.go index 6f5de0d0f..9b3812ce5 100644 --- a/e2e/challenges_test.go +++ b/e2e/challenges_test.go @@ -297,6 +297,45 @@ func TestChallengeHTTP_Client_Obtain_profile(t *testing.T) { assert.Empty(t, resource.CSR) } +func TestChallengeHTTP_Client_Obtain_emails_csr(t *testing.T) { + err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") + require.NoError(t, err) + defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err, "Could not generate test key") + + user := &fakeUser{privateKey: privateKey} + config := lego.NewConfig(user) + config.CADirURL = load.PebbleOptions.HealthCheckURL + + client, err := lego.NewClient(config) + require.NoError(t, err) + + err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "5002")) + require.NoError(t, err) + + reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) + require.NoError(t, err) + user.registration = reg + + request := certificate.ObtainRequest{ + Domains: []string{"acme.wtf"}, + Bundle: true, + EmailAddresses: []string{"foo@example.com"}, + } + resource, err := client.Certificate.Obtain(request) + require.NoError(t, err) + + require.NotNil(t, resource) + assert.Equal(t, "acme.wtf", resource.Domain) + assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) + assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) + assert.NotEmpty(t, resource.Certificate) + assert.NotEmpty(t, resource.IssuerCertificate) + assert.Empty(t, resource.CSR) +} + func TestChallengeHTTP_Client_Obtain_notBefore_notAfter(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) From 4552d03a4dd386f3c4110de0a3c3314d570512c9 Mon Sep 17 00:00:00 2001 From: Ali Najmabadizadeh <110689763+A-Najmabadi@users.noreply.github.com> Date: Thu, 6 Feb 2025 22:39:00 +0330 Subject: [PATCH 039/298] chore: update links of liara provider API doc (#2424) --- docs/content/dns/zz_gen_liara.md | 2 +- providers/dns/liara/internal/client.go | 2 +- providers/dns/liara/liara.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/dns/zz_gen_liara.md b/docs/content/dns/zz_gen_liara.md index b9776fe35..2c3d59ae0 100644 --- a/docs/content/dns/zz_gen_liara.md +++ b/docs/content/dns/zz_gen_liara.md @@ -60,7 +60,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). ## More information -- [API documentation](https://dns-service.iran.liara.ir/swagger) +- [API documentation](https://openapi.liara.ir/?urls.primaryName=DNS) diff --git a/providers/dns/liara/internal/client.go b/providers/dns/liara/internal/client.go index 89794f04d..5faf9f1f2 100644 --- a/providers/dns/liara/internal/client.go +++ b/providers/dns/liara/internal/client.go @@ -34,7 +34,7 @@ func NewClient(hc *http.Client) *Client { } // GetRecords gets the records of a domain. -// https://dns-service.iran.liara.ir/swagger +// https://openapi.liara.ir/?urls.primaryName=DNS func (c Client) GetRecords(ctx context.Context, domainName string) ([]Record, error) { endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records") diff --git a/providers/dns/liara/liara.toml b/providers/dns/liara/liara.toml index cf0e08b18..1259999a2 100644 --- a/providers/dns/liara/liara.toml +++ b/providers/dns/liara/liara.toml @@ -19,4 +19,4 @@ lego --email you@example.com --dns liara -d '*.example.com' -d example.com run LIARA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] - API = "https://dns-service.iran.liara.ir/swagger" + API = "https://openapi.liara.ir/?urls.primaryName=DNS" From c0260c1d8aeeb78d4793b355b7a0249e24a9562c Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 9 Feb 2025 22:08:43 +0100 Subject: [PATCH 040/298] fix: rewrite status management (#2428) --- acme/api/order.go | 3 +- acme/commons.go | 16 +++ acme/errors.go | 18 +-- certificate/certificates.go | 2 +- certificate/certificates_test.go | 45 ++++++++ challenge/resolver/solver_manager.go | 12 +- challenge/resolver/solver_manager_test.go | 132 ++++++++++++++++++++-- registration/registar.go | 4 +- 8 files changed, 205 insertions(+), 27 deletions(-) diff --git a/acme/api/order.go b/acme/api/order.go index 708c35632..befcb541d 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -3,6 +3,7 @@ package api import ( "encoding/base64" "errors" + "fmt" "net" "time" @@ -105,7 +106,7 @@ func (o *OrderService) UpdateForCSR(orderURL string, csr []byte) (acme.ExtendedO } if order.Status == acme.StatusInvalid { - return acme.ExtendedOrder{}, order.Error + return acme.ExtendedOrder{}, fmt.Errorf("invalid order: %w", order.Err()) } return acme.ExtendedOrder{Order: order}, nil diff --git a/acme/commons.go b/acme/commons.go index 918cc605f..d22ea96ae 100644 --- a/acme/commons.go +++ b/acme/commons.go @@ -200,6 +200,14 @@ type Order struct { Replaces string `json:"replaces,omitempty"` } +func (r *Order) Err() error { + if r.Error != nil { + return r.Error + } + + return nil +} + // Authorization the ACME authorization object. // - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.4 type Authorization struct { @@ -285,6 +293,14 @@ type Challenge struct { KeyAuthorization string `json:"keyAuthorization"` } +func (c *Challenge) Err() error { + if c.Error != nil { + return c.Error + } + + return nil +} + // Identifier the ACME identifier object. // - https://www.rfc-editor.org/rfc/rfc8555.html#section-9.7.7 type Identifier struct { diff --git a/acme/errors.go b/acme/errors.go index acaea5f65..6cd74bfb8 100644 --- a/acme/errors.go +++ b/acme/errors.go @@ -25,15 +25,7 @@ type ProblemDetails struct { URL string `json:"url,omitempty"` } -// SubProblem a "subproblems". -// - https://www.rfc-editor.org/rfc/rfc8555.html#section-6.7.1 -type SubProblem struct { - Type string `json:"type,omitempty"` - Detail string `json:"detail,omitempty"` - Identifier Identifier `json:"identifier,omitempty"` -} - -func (p ProblemDetails) Error() string { +func (p *ProblemDetails) Error() string { msg := fmt.Sprintf("acme: error: %d", p.HTTPStatus) if p.Method != "" || p.URL != "" { msg += fmt.Sprintf(" :: %s :: %s", p.Method, p.URL) @@ -51,6 +43,14 @@ func (p ProblemDetails) Error() string { return msg } +// SubProblem a "subproblems". +// - https://www.rfc-editor.org/rfc/rfc8555.html#section-6.7.1 +type SubProblem struct { + Type string `json:"type,omitempty"` + Detail string `json:"detail,omitempty"` + Identifier Identifier `json:"identifier,omitempty"` +} + // NonceError represents the error which is returned // if the nonce sent by the client was not accepted by the server. type NonceError struct { diff --git a/certificate/certificates.go b/certificate/certificates.go index 56c081701..68972478f 100644 --- a/certificate/certificates.go +++ b/certificate/certificates.go @@ -700,7 +700,7 @@ func checkOrderStatus(order acme.ExtendedOrder) (bool, error) { case acme.StatusValid: return true, nil case acme.StatusInvalid: - return false, order.Error + return false, fmt.Errorf("invalid order: %w", order.Err()) default: return false, nil } diff --git a/certificate/certificates_test.go b/certificate/certificates_test.go index bff66429d..da35dbd1a 100644 --- a/certificate/certificates_test.go +++ b/certificate/certificates_test.go @@ -389,6 +389,51 @@ func Test_Get(t *testing.T) { assert.Equal(t, issuerMock, string(certRes.IssuerCertificate), "IssuerCertificate") } +func Test_checkOrderStatus(t *testing.T) { + testCases := []struct { + desc string + order acme.Order + requireErr require.ErrorAssertionFunc + expected bool + }{ + { + desc: "status valid", + order: acme.Order{Status: acme.StatusValid}, + requireErr: require.NoError, + expected: true, + }, + { + desc: "status invalid", + order: acme.Order{Status: acme.StatusInvalid}, + requireErr: require.Error, + expected: false, + }, + { + desc: "status invalid with error", + order: acme.Order{Status: acme.StatusInvalid, Error: &acme.ProblemDetails{}}, + requireErr: require.Error, + expected: false, + }, + { + desc: "unknown status", + order: acme.Order{Status: "foo"}, + requireErr: require.NoError, + expected: false, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + status, err := checkOrderStatus(acme.ExtendedOrder{Order: test.order}) + test.requireErr(t, err) + + assert.Equal(t, test.expected, status) + }) + } +} + type resolverMock struct { error error } diff --git a/challenge/resolver/solver_manager.go b/challenge/resolver/solver_manager.go index ee6bafb49..95db0af54 100644 --- a/challenge/resolver/solver_manager.go +++ b/challenge/resolver/solver_manager.go @@ -124,7 +124,7 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error { return nil } - return errors.New("the server didn't respond to our request") + return fmt.Errorf("the server didn't respond to our request (status=%s)", authz.Status) } return backoff.Retry(operation, bo) @@ -137,9 +137,9 @@ func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) { case acme.StatusPending, acme.StatusProcessing: return false, nil case acme.StatusInvalid: - return false, chlng.Error + return false, fmt.Errorf("invalid challenge: %w", chlng.Err()) default: - return false, errors.New("the server returned an unexpected state") + return false, fmt.Errorf("the server returned an unexpected challenge status: %s", chlng.Status) } } @@ -154,11 +154,11 @@ func checkAuthorizationStatus(authz acme.Authorization) (bool, error) { case acme.StatusInvalid: for _, chlg := range authz.Challenges { if chlg.Status == acme.StatusInvalid && chlg.Error != nil { - return false, chlg.Error + return false, fmt.Errorf("invalid authorization: %w", chlg.Err()) } } - return false, fmt.Errorf("the authorization state %s", authz.Status) + return false, errors.New("invalid authorization") default: - return false, errors.New("the server returned an unexpected state") + return false, fmt.Errorf("the server returned an unexpected authorization status: %s", authz.Status) } } diff --git a/challenge/resolver/solver_manager_test.go b/challenge/resolver/solver_manager_test.go index 9249beeba..113f4963e 100644 --- a/challenge/resolver/solver_manager_test.go +++ b/challenge/resolver/solver_manager_test.go @@ -55,9 +55,6 @@ func TestValidate(t *testing.T) { statuses = statuses[1:] chlg := &acme.Challenge{Type: "http-01", Status: st, URL: "http://example.com/", Token: "token"} - if st == acme.StatusInvalid { - chlg.Error = &acme.ProblemDetails{} - } err := tester.WriteJSONResponse(w, chlg) if err != nil { @@ -83,7 +80,6 @@ func TestValidate(t *testing.T) { if st == acme.StatusInvalid { chlg := acme.Challenge{ Status: acme.StatusInvalid, - Error: &acme.ProblemDetails{}, } authorization.Challenges = append(authorization.Challenges, chlg) } @@ -106,7 +102,7 @@ func TestValidate(t *testing.T) { { name: "POST-unexpected", statuses: []string{"weird"}, - want: "unexpected", + want: "the server returned an unexpected challenge status: weird", }, { name: "POST-valid", @@ -115,12 +111,12 @@ func TestValidate(t *testing.T) { { name: "POST-invalid", statuses: []string{acme.StatusInvalid}, - want: "error", + want: "invalid challenge:", }, { name: "POST-pending-unexpected", statuses: []string{acme.StatusPending, "weird"}, - want: "unexpected", + want: "the server returned an unexpected authorization status: weird", }, { name: "POST-pending-valid", @@ -129,7 +125,7 @@ func TestValidate(t *testing.T) { { name: "POST-pending-invalid", statuses: []string{acme.StatusPending, acme.StatusInvalid}, - want: "error", + want: "invalid authorization", }, } @@ -148,6 +144,126 @@ func TestValidate(t *testing.T) { } } +func Test_checkChallengeStatus(t *testing.T) { + testCases := []struct { + desc string + challenge acme.Challenge + requireErr require.ErrorAssertionFunc + expected bool + }{ + { + desc: "status valid", + challenge: acme.Challenge{Status: acme.StatusValid}, + requireErr: require.NoError, + expected: true, + }, + { + desc: "status invalid", + challenge: acme.Challenge{Status: acme.StatusInvalid}, + requireErr: require.Error, + expected: false, + }, + { + desc: "status invalid with error", + challenge: acme.Challenge{Status: acme.StatusInvalid, Error: &acme.ProblemDetails{}}, + requireErr: require.Error, + expected: false, + }, + { + desc: "status pending", + challenge: acme.Challenge{Status: acme.StatusPending}, + requireErr: require.NoError, + expected: false, + }, + { + desc: "status processing", + challenge: acme.Challenge{Status: acme.StatusProcessing}, + requireErr: require.NoError, + expected: false, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + status, err := checkChallengeStatus(acme.ExtendedChallenge{Challenge: test.challenge}) + test.requireErr(t, err) + + assert.Equal(t, test.expected, status) + }) + } +} + +func Test_checkAuthorizationStatus(t *testing.T) { + testCases := []struct { + desc string + authorization acme.Authorization + requireErr require.ErrorAssertionFunc + expected bool + }{ + { + desc: "status valid", + authorization: acme.Authorization{Status: acme.StatusValid}, + requireErr: require.NoError, + expected: true, + }, + { + desc: "status invalid", + authorization: acme.Authorization{Status: acme.StatusInvalid}, + requireErr: require.Error, + expected: false, + }, + { + desc: "status invalid with error", + authorization: acme.Authorization{Status: acme.StatusInvalid, Challenges: []acme.Challenge{{Error: &acme.ProblemDetails{}}}}, + requireErr: require.Error, + expected: false, + }, + { + desc: "status pending", + authorization: acme.Authorization{Status: acme.StatusPending}, + requireErr: require.NoError, + expected: false, + }, + { + desc: "status processing", + authorization: acme.Authorization{Status: acme.StatusProcessing}, + requireErr: require.NoError, + expected: false, + }, + { + desc: "status deactivated", + authorization: acme.Authorization{Status: acme.StatusDeactivated}, + requireErr: require.Error, + expected: false, + }, + { + desc: "status expired", + authorization: acme.Authorization{Status: acme.StatusExpired}, + requireErr: require.Error, + expected: false, + }, + { + desc: "status revoked", + authorization: acme.Authorization{Status: acme.StatusRevoked}, + requireErr: require.Error, + expected: false, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + status, err := checkAuthorizationStatus(test.authorization) + test.requireErr(t, err) + + assert.Equal(t, test.expected, status) + }) + } +} + // validateNoBody reads the http.Request POST body, parses the JWS and validates it to read the body. // If there is an error doing this, // or if the JWS body is not the empty JSON payload "{}" or a POST-as-GET payload "" an error is returned. diff --git a/registration/registar.go b/registration/registar.go index 78e0ce7d8..15c28bad6 100644 --- a/registration/registar.go +++ b/registration/registar.go @@ -60,7 +60,7 @@ func (r *Registrar) Register(options RegisterOptions) (*Resource, error) { account, err := r.core.Accounts.New(accMsg) if err != nil { // seems impossible - var errorDetails acme.ProblemDetails + errorDetails := &acme.ProblemDetails{} if !errors.As(err, &errorDetails) || errorDetails.HTTPStatus != http.StatusConflict { return nil, err } @@ -84,7 +84,7 @@ func (r *Registrar) RegisterWithExternalAccountBinding(options RegisterEABOption account, err := r.core.Accounts.NewEAB(accMsg, options.Kid, options.HmacEncoded) if err != nil { // seems impossible - var errorDetails acme.ProblemDetails + errorDetails := &acme.ProblemDetails{} if !errors.As(err, &errorDetails) || errorDetails.HTTPStatus != http.StatusConflict { return nil, err } From a25218dbb843667e0c9b81b8d258f45108e976ec Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 9 Feb 2025 22:12:26 +0100 Subject: [PATCH 041/298] Add DNS provider for Spaceship (#2406) --- README.md | 14 +- cmd/zz_gen_cmd_dnshelp.go | 22 +++ docs/content/dns/zz_gen_spaceship.md | 69 ++++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/spaceship/internal/client.go | 159 ++++++++++++++++++ .../dns/spaceship/internal/client_test.go | 125 ++++++++++++++ .../spaceship/internal/fixtures/error.json | 9 + .../internal/fixtures/get-records.json | 13 ++ providers/dns/spaceship/internal/types.go | 47 ++++++ providers/dns/spaceship/spaceship.go | 154 +++++++++++++++++ providers/dns/spaceship/spaceship.toml | 24 +++ providers/dns/spaceship/spaceship_test.go | 143 ++++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 13 files changed, 776 insertions(+), 8 deletions(-) create mode 100644 docs/content/dns/zz_gen_spaceship.md create mode 100644 providers/dns/spaceship/internal/client.go create mode 100644 providers/dns/spaceship/internal/client_test.go create mode 100644 providers/dns/spaceship/internal/fixtures/error.json create mode 100644 providers/dns/spaceship/internal/fixtures/get-records.json create mode 100644 providers/dns/spaceship/internal/types.go create mode 100644 providers/dns/spaceship/spaceship.go create mode 100644 providers/dns/spaceship/spaceship.toml create mode 100644 providers/dns/spaceship/spaceship_test.go diff --git a/README.md b/README.md index cdf1805f1..c5981675f 100644 --- a/README.md +++ b/README.md @@ -204,39 +204,39 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Simply.com Sonic + Spaceship Stackpath Technitium - Tencent Cloud DNS + Tencent Cloud DNS Timeweb Cloud TransIP UKFast SafeDNS - Ultradns + Ultradns Variomedia VegaDNS Vercel - Versio.[nl|eu|uk] + Versio.[nl|eu|uk] VinylDNS VK Cloud Volcano Engine/火山引擎 - Vscale + Vscale Vultr Webnames Websupport - WEDOS + WEDOS West.cn/西部数码 Yandex 360 Yandex Cloud - Yandex PDD + Yandex PDD Zone.ee Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index f121d6dd2..44a468eda 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -134,6 +134,7 @@ func allDNSCodes() string { "shellrent", "simply", "sonic", + "spaceship", "stackpath", "technitium", "tencentcloud", @@ -2770,6 +2771,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/sonic`) + case "spaceship": + // generated from: providers/dns/spaceship/spaceship.toml + ew.writeln(`Configuration for Spaceship.`) + ew.writeln(`Code: 'spaceship'`) + ew.writeln(`Since: 'v4.22.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "SPACESHIP_API_KEY": API key`) + ew.writeln(` - "SPACESHIP_API_SECRET": API secret`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "SPACESHIP_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "SPACESHIP_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "SPACESHIP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "SPACESHIP_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/spaceship`) + case "stackpath": // generated from: providers/dns/stackpath/stackpath.toml ew.writeln(`Configuration for Stackpath.`) diff --git a/docs/content/dns/zz_gen_spaceship.md b/docs/content/dns/zz_gen_spaceship.md new file mode 100644 index 000000000..4594fe217 --- /dev/null +++ b/docs/content/dns/zz_gen_spaceship.md @@ -0,0 +1,69 @@ +--- +title: "Spaceship" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: spaceship +dnsprovider: + since: "v4.22.0" + code: "spaceship" + url: "https://www.spaceship.com/" +--- + + + + + + +Configuration for [Spaceship](https://www.spaceship.com/). + + + + +- Code: `spaceship` +- Since: v4.22.0 + + +Here is an example bash command using the Spaceship provider: + +```bash +SPACESHIP_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ +SPACESHIP_API_SECRET="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns spaceship -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `SPACESHIP_API_KEY` | API key | +| `SPACESHIP_API_SECRET` | API secret | + +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 | +|--------------------------------|-------------| +| `SPACESHIP_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `SPACESHIP_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `SPACESHIP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `SPACESHIP_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://docs.spaceship.dev/#tag/DNS-records) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index f15eacda1..84073a2c0 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -148,7 +148,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, manageengine, manual, metaname, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, 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, manageengine, manual, metaname, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/spaceship/internal/client.go b/providers/dns/spaceship/internal/client.go new file mode 100644 index 000000000..f739e01ba --- /dev/null +++ b/providers/dns/spaceship/internal/client.go @@ -0,0 +1,159 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const defaultBaseURL = "https://spaceship.dev/api/v1/" + +// Client the Spaceship API client. +type Client struct { + apiKey string + apiSecret string + + baseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(apiKey, apiSecret string) (*Client, error) { + if apiKey == "" || apiSecret == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + apiKey: apiKey, + apiSecret: apiSecret, + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) do(req *http.Request, result any) error { + req.Header.Add("X-Api-Secret", c.apiSecret) + req.Header.Add("X-Api-Key", c.apiKey) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func (c *Client) AddRecord(ctx context.Context, domain string, record Record) error { + endpoint := c.baseURL.JoinPath("dns", "records", domain) + + req, err := newJSONRequest(ctx, http.MethodPut, endpoint, Foo{Items: []Record{record}}) + if err != nil { + return err + } + + err = c.do(req, nil) + if err != nil { + return err + } + + return nil +} + +func (c *Client) DeleteRecord(ctx context.Context, domain string, record Record) error { + endpoint := c.baseURL.JoinPath("dns", "records", domain) + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, []Record{record}) + if err != nil { + return err + } + + err = c.do(req, nil) + if err != nil { + return err + } + + return nil +} + +func (c *Client) GetRecords(ctx context.Context, domain string) ([]Record, error) { + endpoint := c.baseURL.JoinPath("dns", "records", domain) + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var result GetRecordsResponse + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result.Items, nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/spaceship/internal/client_test.go b/providers/dns/spaceship/internal/client_test.go new file mode 100644 index 000000000..19c90d5f8 --- /dev/null +++ b/providers/dns/spaceship/internal/client_test.go @@ -0,0 +1,125 @@ +package internal + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupTest(t *testing.T, pattern string, status int, filename string) *Client { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { + if filename == "" { + rw.WriteHeader(status) + return + } + + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + defer func() { _ = file.Close() }() + + rw.WriteHeader(status) + _, err = io.Copy(rw, file) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + }) + + client, err := NewClient("key", "secret") + require.NoError(t, err) + + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) + + return client +} + +func TestClient_AddRecord(t *testing.T) { + client := setupTest(t, "PUT /dns/records/example.com", http.StatusOK, "") + + record := Record{ + Type: "TXT", + Name: "@", + TTL: 60, + } + + err := client.AddRecord(context.Background(), "example.com", record) + require.NoError(t, err) +} + +func TestClient_AddRecord_error(t *testing.T) { + client := setupTest(t, "PUT /dns/records/example.com", http.StatusUnprocessableEntity, "error.json") + + record := Record{ + Type: "TXT", + Name: "@", + TTL: 60, + } + + err := client.AddRecord(context.Background(), "example.com", record) + require.EqualError(t, err, "^$, name: The domain name contains invalid characters") +} + +func TestClient_DeleteRecord(t *testing.T) { + client := setupTest(t, "DELETE /dns/records/example.com", http.StatusOK, "") + + record := Record{ + Type: "TXT", + Name: "@", + TTL: 60, + } + + err := client.DeleteRecord(context.Background(), "example.com", record) + require.NoError(t, err) +} + +func TestClient_DeleteRecord_error(t *testing.T) { + client := setupTest(t, "DELETE /dns/records/example.com", http.StatusUnprocessableEntity, "error.json") + + record := Record{ + Type: "TXT", + Name: "@", + TTL: 60, + } + + err := client.DeleteRecord(context.Background(), "example.com", record) + require.EqualError(t, err, "^$, name: The domain name contains invalid characters") +} + +func TestClient_GetRecords(t *testing.T) { + client := setupTest(t, "GET /dns/records/example.com", http.StatusOK, "get-records.json") + + records, err := client.GetRecords(context.Background(), "example.com") + require.NoError(t, err) + + expected := []Record{ + {Type: "A", Name: "@", TTL: 3600}, + } + + assert.Equal(t, expected, records) +} + +func TestClient_GetRecords_error(t *testing.T) { + client := setupTest(t, "GET /dns/records/example.com", http.StatusUnprocessableEntity, "error.json") + + _, err := client.GetRecords(context.Background(), "example.com") + require.EqualError(t, err, "^$, name: The domain name contains invalid characters") +} diff --git a/providers/dns/spaceship/internal/fixtures/error.json b/providers/dns/spaceship/internal/fixtures/error.json new file mode 100644 index 000000000..facf97e58 --- /dev/null +++ b/providers/dns/spaceship/internal/fixtures/error.json @@ -0,0 +1,9 @@ +{ + "detail": "^$", + "data": [ + { + "field": "name", + "details": "The domain name contains invalid characters" + } + ] +} diff --git a/providers/dns/spaceship/internal/fixtures/get-records.json b/providers/dns/spaceship/internal/fixtures/get-records.json new file mode 100644 index 000000000..cea2a895a --- /dev/null +++ b/providers/dns/spaceship/internal/fixtures/get-records.json @@ -0,0 +1,13 @@ +{ + "items": [ + { + "type": "A", + "name": "@", + "ttl": 3600, + "group": { + "type": "custom" + } + } + ], + "total": 100 +} diff --git a/providers/dns/spaceship/internal/types.go b/providers/dns/spaceship/internal/types.go new file mode 100644 index 000000000..bd318bb87 --- /dev/null +++ b/providers/dns/spaceship/internal/types.go @@ -0,0 +1,47 @@ +package internal + +import ( + "fmt" + "strings" +) + +type APIError struct { + Detail string `json:"detail"` + Data []struct { + Field string `json:"field"` + Details string `json:"details"` + } `json:"data"` +} + +func (a *APIError) Error() string { + msg := []string{a.Detail} + + for _, datum := range a.Data { + msg = append(msg, fmt.Sprintf("%s: %s", datum.Field, datum.Details)) + } + + return strings.Join(msg, ", ") +} + +type Foo struct { + Force bool `json:"force,omitempty"` + Items []Record `json:"items,omitempty"` +} + +type Record struct { + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` + Address string `json:"address,omitempty"` + Nameserver string `json:"nameserver,omitempty"` + AliasName string `json:"aliasName,omitempty"` + Pointer string `json:"pointer,omitempty"` + CName string `json:"cname,omitempty"` + Exchange string `json:"exchange,omitempty"` + TTL int `json:"ttl,omitempty"` +} + +type GetRecordsResponse struct { + Items []Record `json:"items"` + Total int `json:"total"` +} diff --git a/providers/dns/spaceship/spaceship.go b/providers/dns/spaceship/spaceship.go new file mode 100644 index 000000000..9e8f0158e --- /dev/null +++ b/providers/dns/spaceship/spaceship.go @@ -0,0 +1,154 @@ +// Package spaceship implements a DNS provider for solving the DNS-01 challenge using Spaceship. +package spaceship + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/spaceship/internal" +) + +// Environment variables names. +const ( + envNamespace = "SPACESHIP_" + + EnvAPIKey = envNamespace + "API_KEY" + EnvAPISecret = envNamespace + "API_SECRET" + + 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 + APISecret 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for Spaceship. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIKey, EnvAPISecret) + if err != nil { + return nil, fmt.Errorf("spaceship: %w", err) + } + + config := NewDefaultConfig() + config.APIKey = values[EnvAPIKey] + config.APISecret = values[EnvAPISecret] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Spaceship. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("spaceship: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.APIKey, config.APISecret) + if err != nil { + return nil, fmt.Errorf("spaceship: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("spaceship: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("spaceship: %w", err) + } + + record := internal.Record{ + Type: "TXT", + Name: subDomain, + Value: info.Value, + TTL: d.config.TTL, + } + + err = d.client.AddRecord(context.Background(), dns01.UnFqdn(authZone), record) + if err != nil { + return fmt.Errorf("spaceship: %w", err) + } + + 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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("spaceship: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("spaceship: %w", err) + } + + record := internal.Record{ + Type: "TXT", + Name: subDomain, + Value: info.Value, + } + + err = d.client.DeleteRecord(context.Background(), dns01.UnFqdn(authZone), record) + if err != nil { + return fmt.Errorf("spaceship: %w", err) + } + + 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/spaceship/spaceship.toml b/providers/dns/spaceship/spaceship.toml new file mode 100644 index 000000000..645abd171 --- /dev/null +++ b/providers/dns/spaceship/spaceship.toml @@ -0,0 +1,24 @@ +Name = "Spaceship" +Description = '''''' +URL = "https://www.spaceship.com/" +Code = "spaceship" +Since = "v4.22.0" + +Example = ''' +SPACESHIP_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ +SPACESHIP_API_SECRET="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns spaceship -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + SPACESHIP_API_KEY = "API key" + SPACESHIP_API_SECRET = "API secret" + [Configuration.Additional] + SPACESHIP_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + SPACESHIP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + SPACESHIP_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + SPACESHIP_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://docs.spaceship.dev/#tag/DNS-records" diff --git a/providers/dns/spaceship/spaceship_test.go b/providers/dns/spaceship/spaceship_test.go new file mode 100644 index 000000000..ba60cfa5e --- /dev/null +++ b/providers/dns/spaceship/spaceship_test.go @@ -0,0 +1,143 @@ +package spaceship + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvAPIKey, EnvAPISecret).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvAPIKey: "key", + EnvAPISecret: "secret", + }, + }, + { + desc: "missing API key", + envVars: map[string]string{ + EnvAPIKey: "", + EnvAPISecret: "secret", + }, + expected: "spaceship: some credentials information are missing: SPACESHIP_API_KEY", + }, + { + desc: "missing API secret", + envVars: map[string]string{ + EnvAPIKey: "key", + EnvAPISecret: "", + }, + expected: "spaceship: some credentials information are missing: SPACESHIP_API_SECRET", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "spaceship: some credentials information are missing: SPACESHIP_API_KEY,SPACESHIP_API_SECRET", + }, + } + + 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 + apiSecret string + expected string + }{ + { + desc: "success", + apiKey: "key", + apiSecret: "secret", + }, + { + desc: "missing API key", + apiSecret: "secret", + expected: "spaceship: credentials missing", + }, + { + desc: "missing API secret", + apiKey: "key", + expected: "spaceship: credentials missing", + }, + { + desc: "missing credentials", + expected: "spaceship: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIKey = test.apiKey + config.APISecret = test.apiSecret + + 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 762f69832..0b041183b 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -128,6 +128,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/shellrent" "github.com/go-acme/lego/v4/providers/dns/simply" "github.com/go-acme/lego/v4/providers/dns/sonic" + "github.com/go-acme/lego/v4/providers/dns/spaceship" "github.com/go-acme/lego/v4/providers/dns/stackpath" "github.com/go-acme/lego/v4/providers/dns/technitium" "github.com/go-acme/lego/v4/providers/dns/tencentcloud" @@ -401,6 +402,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return simply.NewDNSProvider() case "sonic": return sonic.NewDNSProvider() + case "spaceship": + return spaceship.NewDNSProvider() case "stackpath": return stackpath.NewDNSProvider() case "technitium": From c96a165aa916bd2b9181d94402133fae1b78124f Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 10 Feb 2025 12:54:02 +0100 Subject: [PATCH 042/298] feat(cli): add an option to set the private key (#2431) --- certificate/certificates.go | 10 +++++++++- cmd/cmd_run.go | 31 ++++++++++++++++++++++--------- docs/data/zz_cli_help.toml | 1 + 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/certificate/certificates.go b/certificate/certificates.go index 68972478f..d03e99c43 100644 --- a/certificate/certificates.go +++ b/certificate/certificates.go @@ -97,6 +97,8 @@ type ObtainRequest struct { type ObtainForCSRRequest struct { CSR *x509.CertificateRequest + PrivateKey crypto.PrivateKey + NotBefore time.Time NotAfter time.Time Bundle bool @@ -262,7 +264,13 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error) log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", ")) failures := newObtainError() - cert, err := c.getForCSR(domains, order, request.Bundle, request.CSR.Raw, nil, request.PreferredChain) + + var privateKey []byte + if request.PrivateKey != nil { + privateKey = certcrypto.PEMEncode(request.PrivateKey) + } + + cert, err := c.getForCSR(domains, order, request.Bundle, request.CSR.Raw, privateKey, request.PreferredChain) if err != nil { for _, auth := range authz { failures.Add(challenge.GetTargetedDomain(auth), err) diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index 6d76e7ad8..8135e3546 100644 --- a/cmd/cmd_run.go +++ b/cmd/cmd_run.go @@ -20,6 +20,7 @@ const ( flgMustStaple = "must-staple" flgNotBefore = "not-before" flgNotAfter = "not-after" + flgPrivateKey = "private-key" flgPreferredChain = "preferred-chain" flgProfile = "profile" flgAlwaysDeactivateAuthorizations = "always-deactivate-authorizations" @@ -64,6 +65,10 @@ func createRun() *cli.Command { Usage: "Set the notAfter field in the certificate (RFC3339 format)", Layout: time.RFC3339, }, + &cli.StringFlag{ + Name: flgPrivateKey, + Usage: "Path to private key (in PEM encoding) for the certificate. By default, the private key is generated.", + }, &cli.StringFlag{ Name: flgPreferredChain, Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name." + @@ -203,21 +208,21 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso // obtain a certificate, generating a new private key request := certificate.ObtainRequest{ Domains: domains, - Bundle: bundle, MustStaple: ctx.Bool(flgMustStaple), + NotBefore: getTime(ctx, flgNotBefore), + NotAfter: getTime(ctx, flgNotAfter), + Bundle: bundle, PreferredChain: ctx.String(flgPreferredChain), Profile: ctx.String(flgProfile), AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations), } - notBefore := ctx.Timestamp(flgNotBefore) - if notBefore != nil { - request.NotBefore = *notBefore - } - - notAfter := ctx.Timestamp(flgNotAfter) - if notAfter != nil { - request.NotAfter = *notAfter + if ctx.IsSet(flgPrivateKey) { + var err error + request.PrivateKey, err = loadPrivateKey(ctx.String(flgPrivateKey)) + if err != nil { + return nil, fmt.Errorf("load private key: %w", err) + } } return client.Certificate.Obtain(request) @@ -240,5 +245,13 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso AlwaysDeactivateAuthorizations: ctx.Bool(flgAlwaysDeactivateAuthorizations), } + if ctx.IsSet(flgPrivateKey) { + var err error + request.PrivateKey, err = loadPrivateKey(ctx.String(flgPrivateKey)) + if err != nil { + return nil, fmt.Errorf("load private key: %w", err) + } + } + return client.Certificate.ObtainForCSR(request) } diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 84073a2c0..995de5ced 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -72,6 +72,7 @@ OPTIONS: --must-staple Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego. (default: false) --not-before value Set the notBefore field in the certificate (RFC3339 format) --not-after value Set the notAfter field in the certificate (RFC3339 format) + --private-key value Path to private key (in PEM encoding) for the certificate. By default, the private key is generated. --preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. --profile value If the CA offers multiple certificate profiles (draft-aaron-acme-profiles), choose this one. --always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful. From 5d7fd6621ea2b91e0813092ff072dfb2bf01a5b4 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 12 Feb 2025 12:58:46 +0100 Subject: [PATCH 043/298] chore: update linter (#2434) --- .github/workflows/pr.yml | 2 +- .golangci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index a8f2ff452..4381c7fbd 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: stable - GOLANGCI_LINT_VERSION: v1.63.3 + GOLANGCI_LINT_VERSION: v1.64.2 HUGO_VERSION: 0.131.0 CGO_ENABLED: 0 LEGO_E2E_TESTS: CI diff --git a/.golangci.yml b/.golangci.yml index 9beeea718..2a3868ff4 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -35,7 +35,6 @@ linters: - nonamedreturns - musttag # false-positive https://github.com/junk1tm/musttag/issues/17 - gosmopolitan # not relevant - - exportloopref # Useless with go1.22 - canonicalheader # Can create side effects in the context of API clients - usestdlibvars # false-positive https://github.com/sashamelentyev/usestdlibvars/issues/96 @@ -138,6 +137,7 @@ linters-settings: run: timeout: 10m + relative-path-mode: cfg output: show-stats: true From b16da88eb70006ca32e1fd62a583fa53bed31890 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 16 Feb 2025 16:12:47 +0100 Subject: [PATCH 044/298] acme-dns: allow the HTTP storage server to create the CNAME (#2437) --- providers/dns/acmedns/acmedns.go | 12 +++++++++++- providers/dns/acmedns/internal/http_storage.go | 8 ++++++++ .../dns/acmedns/internal/http_storage_test.go | 15 +++++++++++++++ providers/dns/acmedns/internal/readme.md | 4 +++- 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/providers/dns/acmedns/acmedns.go b/providers/dns/acmedns/acmedns.go index 8aabaa14c..9dd63d0a2 100644 --- a/providers/dns/acmedns/acmedns.go +++ b/providers/dns/acmedns/acmedns.go @@ -178,16 +178,26 @@ func (d *DNSProvider) register(ctx context.Context, domain, fqdn string) error { return err } + var cnameCreated bool + // Store the new account in the storage and call save to persist the data. err = d.storage.Put(ctx, domain, newAcct) if err != nil { - return err + cnameCreated = errors.Is(err, internal.ErrCNAMEAlreadyCreated) + if !cnameCreated { + return err + } } + err = d.storage.Save(ctx) if err != nil { return err } + if cnameCreated { + return nil + } + // Stop issuance by returning an error. // The user needs to perform a manual one-time CNAME setup in their DNS zone // to complete the setup of the new account we created. diff --git a/providers/dns/acmedns/internal/http_storage.go b/providers/dns/acmedns/internal/http_storage.go index 1a1e8e2ee..7a535eb20 100644 --- a/providers/dns/acmedns/internal/http_storage.go +++ b/providers/dns/acmedns/internal/http_storage.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -17,6 +18,8 @@ import ( var _ goacmedns.Storage = (*HTTPStorage)(nil) +var ErrCNAMEAlreadyCreated = errors.New("the CNAME has already been created") + // HTTPStorage is an implementation of [acmedns.Storage] over HTTP. type HTTPStorage struct { client *http.Client @@ -98,6 +101,11 @@ func (s *HTTPStorage) do(req *http.Request, result any) error { } if result == nil { + // Hack related to `Put`. + if resp.StatusCode == http.StatusCreated { + return ErrCNAMEAlreadyCreated + } + return nil } diff --git a/providers/dns/acmedns/internal/http_storage_test.go b/providers/dns/acmedns/internal/http_storage_test.go index a628f9a6d..7f9367722 100644 --- a/providers/dns/acmedns/internal/http_storage_test.go +++ b/providers/dns/acmedns/internal/http_storage_test.go @@ -137,3 +137,18 @@ func TestHTTPStorage_Put_error(t *testing.T) { err := storage.Put(context.Background(), "example.com", account) require.Error(t, err) } + +func TestHTTPStorage_Put_CNAME_created(t *testing.T) { + storage := setupTest(t, "POST /example.com", "", http.StatusCreated) + + account := goacmedns.Account{ + FullDomain: "foo.example.com", + SubDomain: "foo", + Username: "user", + Password: "secret", + ServerURL: "https://example.com", + } + + err := storage.Put(context.Background(), "example.com", account) + require.ErrorIs(t, err, ErrCNAMEAlreadyCreated) +} diff --git a/providers/dns/acmedns/internal/readme.md b/providers/dns/acmedns/internal/readme.md index bccdc5388..b667d3d23 100644 --- a/providers/dns/acmedns/internal/readme.md +++ b/providers/dns/acmedns/internal/readme.md @@ -61,7 +61,9 @@ Endpoint: `POST /` ### Response -Response status code 200. +Response status code: +- 200: the process will be stopped to allow the user to create the CNAME. +- 201: the process will continue without error (the CNAME should be created by the server) No expected body. From 29cf89ea4945f7f8422e866cfc50aae572a34de7 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 16 Feb 2025 22:39:44 +0100 Subject: [PATCH 045/298] acme-dns: fix file path (#2439) --- README.md | 4 +- cmd/zz_gen_cmd_dnshelp.go | 3 + docs/content/_index.md | 4 +- docs/content/dns/zz_gen_acme-dns.md | 8 ++ providers/dns/acmedns/acmedns.go | 111 ++++++++++++++++++-------- providers/dns/acmedns/acmedns.toml | 2 + providers/dns/acmedns/acmedns_test.go | 22 +++-- 7 files changed, 110 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index c5981675f..2a8dae5c1 100644 --- a/README.md +++ b/README.md @@ -18,17 +18,17 @@ Let's Encrypt client and ACME library written in Go. - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): certificates for IP addresses - Support [draft-ietf-acme-ari-03](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/): Renewal Information (ARI) Extension - Support [draft-aaron-acme-profiles-00](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/): Profiles Extension +- Comes with about [150 DNS providers](https://go-acme.github.io/lego/dns) - Register with CA - Obtain certificates, both from scratch or with an existing CSR - Renew certificates - Revoke certificates -- Robust implementation of all ACME challenges +- Robust implementation of ACME challenges: - HTTP (http-01) - DNS (dns-01) - TLS (tls-alpn-01) - SAN certificate support - [CNAME support](https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme.html) by default -- Comes with multiple optional [DNS providers](https://go-acme.github.io/lego/dns) - [Custom challenge solvers](https://go-acme.github.io/lego/usage/library/writing-a-challenge-solver/) - Certificate bundling - OCSP helper function diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 44a468eda..d2150d07e 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -182,6 +182,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(` - "ACME_DNS_STORAGE_PATH": The ACME-DNS JSON account data file. A per-domain account will be registered/persisted to this file and used for TXT updates.`) ew.writeln() + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "ACME_DNS_ALLOWLIST": Source networks using CIDR notation (multiple values should be separated with a comma).`) + ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/acme-dns`) diff --git a/docs/content/_index.md b/docs/content/_index.md index 7cb903aac..497e7e168 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -14,17 +14,17 @@ Let's Encrypt client and ACME library written in Go. - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): issues certificates for IP addresses - Support [draft-ietf-acme-ari-03](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/): Renewal Information (ARI) Extension - Support [draft-aaron-acme-profiles-00](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/): Profiles Extension +- Comes with about [150 DNS providers]({{% ref "dns" %}}) - Register with CA - Obtain certificates, both from scratch or with an existing CSR - Renew certificates - Revoke certificates -- Robust implementation of all ACME challenges +- Robust implementation of ACME challenges: - HTTP (http-01) - DNS (dns-01) - TLS (tls-alpn-01) - SAN certificate support - [CNAME support](https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme.html) by default -- Comes with multiple optional [DNS providers]({{% ref "dns" %}}) - [Custom challenge solvers]({{% ref "usage/library/Writing-a-Challenge-Solver" %}}) - Certificate bundling - OCSP helper function diff --git a/docs/content/dns/zz_gen_acme-dns.md b/docs/content/dns/zz_gen_acme-dns.md index be901c512..cb3d24016 100644 --- a/docs/content/dns/zz_gen_acme-dns.md +++ b/docs/content/dns/zz_gen_acme-dns.md @@ -52,6 +52,14 @@ The environment variable names can be suffixed by `_FILE` to reference a file in More information [here]({{% ref "dns#configuration-and-credentials" %}}). +## Additional Configuration + +| Environment Variable Name | Description | +|--------------------------------|-------------| +| `ACME_DNS_ALLOWLIST` | Source networks using CIDR notation (multiple values should be separated with a comma). | + +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" %}}). diff --git a/providers/dns/acmedns/acmedns.go b/providers/dns/acmedns/acmedns.go index 9dd63d0a2..8dedeb128 100644 --- a/providers/dns/acmedns/acmedns.go +++ b/providers/dns/acmedns/acmedns.go @@ -6,6 +6,7 @@ import ( "context" "errors" "fmt" + "strings" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" @@ -23,6 +24,10 @@ const ( // (e.g. https://acmedns.your-domain.com). EnvAPIBase = envNamespace + "API_BASE" + // EnvAllowList are source networks using CIDR notation, + // e.g. "192.168.100.1/24,1.2.3.4/32,2002:c0a8:2a00::0/40". + EnvAllowList = envNamespace + "ALLOWLIST" + // EnvStoragePath is the environment variable name for the ACME-DNS JSON account data file. // A per-domain account will be registered/persisted to this file and used for TXT updates. EnvStoragePath = envNamespace + "STORAGE_PATH" @@ -34,6 +39,19 @@ const ( var _ challenge.Provider = (*DNSProvider)(nil) +// Config is used to configure the creation of the DNSProvider. +type Config struct { + APIBase string + AllowList []string + StoragePath string + StorageBaseURL string +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{} +} + // acmeDNSClient is an interface describing the goacmedns.Client functions the DNSProvider uses. // It makes it easier for tests to shim a mock Client into the DNSProvider. type acmeDNSClient interface { @@ -47,58 +65,67 @@ type acmeDNSClient interface { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { + config *Config client acmeDNSClient storage goacmedns.Storage } -// NewDNSProvider creates an ACME-DNS provider using file based account storage. -// Its configuration is loaded from the environment by reading EnvAPIBase and EnvStoragePath. +// NewDNSProvider returns a DNSProvider instance configured for Joohoi's acme-dns. func NewDNSProvider() (*DNSProvider, error) { values, err := env.Get(EnvAPIBase) if err != nil { return nil, fmt.Errorf("acme-dns: %w", err) } - storagePath := env.GetOrFile(EnvStoragePath) - storageBaseURL := env.GetOrFile(EnvStorageBaseURL) + config := NewDefaultConfig() + config.APIBase = values[EnvAPIBase] + config.StoragePath = env.GetOrFile(EnvStoragePath) + config.StorageBaseURL = env.GetOrFile(EnvStorageBaseURL) - if storagePath == "" && storageBaseURL == "" { - return nil, fmt.Errorf("acme-dns: %s or %s environment variables not set", EnvStoragePath, EnvStorageBaseURL) + allowList := env.GetOrFile(EnvAllowList) + if allowList != "" { + config.AllowList = strings.Split(allowList, ",") } - if storagePath != "" && storageBaseURL != "" { - return nil, fmt.Errorf("acme-dns: %s or %s environment variables cannot be used at the same time", EnvStoragePath, EnvStorageBaseURL) + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Joohoi's acme-dns. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("acme-dns: the configuration of the DNS provider is nil") } - var st goacmedns.Storage - if storagePath != "" { - st = storage.NewFile(values[EnvStoragePath], 0o600) - } else { - st, err = internal.NewHTTPStorage(storageBaseURL) - if err != nil { - return nil, fmt.Errorf("acme-dns: new HTTP storage: %w", err) - } - } - - client, err := goacmedns.NewClient(values[EnvAPIBase]) + st, err := getStorage(config) if err != nil { return nil, fmt.Errorf("acme-dns: %w", err) } - return NewDNSProviderClient(client, st) -} - -// NewDNSProviderClient creates an ACME-DNS DNSProvider with the given acmeDNSClient and [goacmedns.Storage]. -func NewDNSProviderClient(client acmeDNSClient, storage goacmedns.Storage) (*DNSProvider, error) { - if client == nil { - return nil, errors.New("ACME-DNS Client must be not nil") - } - - if storage == nil { - return nil, errors.New("ACME-DNS Storage must be not nil") + client, err := goacmedns.NewClient(config.APIBase) + if err != nil { + return nil, fmt.Errorf("acme-dns: new client: %w", err) } return &DNSProvider{ + config: config, + client: client, + storage: st, + }, nil +} + +// NewDNSProviderClient creates an ACME-DNS DNSProvider with the given acmeDNSClient and [goacmedns.Storage]. +// Deprecated: use [NewDNSProviderConfig] instead. +func NewDNSProviderClient(client acmeDNSClient, storage goacmedns.Storage) (*DNSProvider, error) { + if client == nil { + return nil, errors.New("acme-dns: Client must be not nil") + } + + if storage == nil { + return nil, errors.New("acme-dns: Storage must be not nil") + } + + return &DNSProvider{ + config: NewDefaultConfig(), client: client, storage: storage, }, nil @@ -172,8 +199,7 @@ func (d *DNSProvider) CleanUp(_, _, _ string) error { // the one-time manual CNAME setup required to complete setup of the ACME-DNS hook for the domain. // If any other error occurs it is returned as-is. func (d *DNSProvider) register(ctx context.Context, domain, fqdn string) error { - // TODO(@cpu): Read CIDR whitelists from the environment - newAcct, err := d.client.RegisterAccount(ctx, nil) + newAcct, err := d.client.RegisterAccount(ctx, d.config.AllowList) if err != nil { return err } @@ -207,3 +233,24 @@ func (d *DNSProvider) register(ctx context.Context, domain, fqdn string) error { Target: newAcct.FullDomain, } } + +func getStorage(config *Config) (goacmedns.Storage, error) { + if config.StoragePath == "" && config.StorageBaseURL == "" { + return nil, errors.New("storagePath or storageBaseURL is not set") + } + + if config.StoragePath != "" && config.StorageBaseURL != "" { + return nil, errors.New("storagePath and storageBaseURL cannot be used at the same time") + } + + if config.StoragePath != "" { + return storage.NewFile(config.StoragePath, 0o600), nil + } + + st, err := internal.NewHTTPStorage(config.StorageBaseURL) + if err != nil { + return nil, fmt.Errorf("new HTTP storage: %w", err) + } + + return st, nil +} diff --git a/providers/dns/acmedns/acmedns.toml b/providers/dns/acmedns/acmedns.toml index 12d690414..6d68a013d 100644 --- a/providers/dns/acmedns/acmedns.toml +++ b/providers/dns/acmedns/acmedns.toml @@ -22,6 +22,8 @@ lego --email you@example.com --dns "acme-dns" -d '*.example.com' -d example.com ACME_DNS_API_BASE = "The ACME-DNS API address" ACME_DNS_STORAGE_PATH = "The ACME-DNS JSON account data file. A per-domain account will be registered/persisted to this file and used for TXT updates." ACME_DNS_STORAGE_BASE_URL = "The ACME-DNS JSON account data server." + [Configuration.Additional] + ACME_DNS_ALLOWLIST = "Source networks using CIDR notation (multiple values should be separated with a comma)." [Links] API = "https://github.com/joohoi/acme-dns#api" diff --git a/providers/dns/acmedns/acmedns_test.go b/providers/dns/acmedns/acmedns_test.go index e21a10522..76aa097f5 100644 --- a/providers/dns/acmedns/acmedns_test.go +++ b/providers/dns/acmedns/acmedns_test.go @@ -68,17 +68,20 @@ func TestPresent(t *testing.T) { for _, test := range testCases { t.Run(test.Name, func(t *testing.T) { - dp, err := NewDNSProviderClient(test.Client, mockStorage{make(map[string]goacmedns.Account)}) - require.NoError(t, err) + p := &DNSProvider{ + config: NewDefaultConfig(), + client: test.Client, + storage: mockStorage{make(map[string]goacmedns.Account)}, + } // override the storage mock if required by the test case. if test.Storage != nil { - dp.storage = test.Storage + p.storage = test.Storage } // call Present. The token argument can be garbage because the ACME-DNS // provider does not use it. - err = dp.Present(egDomain, "foo", egKeyAuth) + err := p.Present(egDomain, "foo", egKeyAuth) if test.ExpectedError != nil { assert.Equal(t, test.ExpectedError, err) } else { @@ -134,16 +137,19 @@ func TestRegister(t *testing.T) { for _, test := range testCases { t.Run(test.Name, func(t *testing.T) { - dp, err := NewDNSProviderClient(test.Client, mockStorage{make(map[string]goacmedns.Account)}) - require.NoError(t, err) + p := &DNSProvider{ + config: NewDefaultConfig(), + client: test.Client, + storage: mockStorage{make(map[string]goacmedns.Account)}, + } // override the storage mock if required by the testcase. if test.Storage != nil { - dp.storage = test.Storage + p.storage = test.Storage } // Call register for the example domain/fqdn. - err = dp.register(context.Background(), egDomain, egFQDN) + err := p.register(context.Background(), egDomain, egFQDN) if test.ExpectedError != nil { assert.Equal(t, test.ExpectedError, err) } else { From 9b7343cb425523843ee9bbbfe27f572271246752 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 12 Feb 2025 04:32:58 +0100 Subject: [PATCH 046/298] Prepare release v4.22.0 --- CHANGELOG.md | 28 +++++++++++++++++++ acme/api/internal/sender/useragent.go | 4 +-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 +-- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdd07ac56..ddbe73be0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## [v4.22.0](https://github.com/go-acme/lego/releases/tag/v4.22.0) (2025-02-16) + +### Added + +- **[cli]** Add `--private-key` flag to set the private key. +- **[cli]** Add `LEGO_DEBUG_ACME_HTTP_CLIENT` environment variable to debug the calls to the ACME server. +- **[cli]** Add `LEGO_EMAIL` environment variable for specifying email. +- **[cli]** Add `--hook-timeout` flag to run and renew commands. +- **[dnsprovider]** Add DNS provider for myaddr.{tools,dev,io} +- **[dnsprovider]** Add DNS provider for Spaceship +- **[dnsprovider]** acme-dns: add HTTP storage +- **[lib,cli,httpprovider]** Add `--http.delay` option for HTTP challenge. +- **[lib,cli,profiles]** Add support for Profiles Extension. +- **[lib]** Add an option to set CSR email addresses + +### Changed + +- **[lib]** rewrite status management +- **[dnsprovider]** docs: improve units and default values + +### Removed + +- **[dnsprovider]** netcup: remove TTL option + +### Fixed + +- **[cli,log]** remove extra debug logs + ## [v4.21.0](https://github.com/go-acme/lego/releases/tag/v4.21.0) (2024-12-20) ### Added diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index e2d5700ff..0c765e654 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.21.0" + ourUserAgent = "xenolf-acme/4.22.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 8705ebbdf..011eaa890 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.21.0+dev-detach" +const defaultVersion = "v4.22.0+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 572fd5012..f315d0ddb 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.21.0" + ourUserAgent = "goacme-lego/4.22.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From c34138845e28df4acb7e4f42426c539c44086f57 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 12 Feb 2025 04:34:03 +0100 Subject: [PATCH 047/298] Detach v4.22.0 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 0c765e654..0ebaa55e0 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 011eaa890..db3d9f438 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.22.0+dev-release" +const defaultVersion = "v4.22.0+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index f315d0ddb..21bfb74e8 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From f9c1e241f3ab4210642aeb47bed72899ef115cc8 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 17 Feb 2025 16:00:22 +0100 Subject: [PATCH 048/298] acme-dns: continue the process when the CNAME is handled by the storage (#2443) --- providers/dns/acmedns/acmedns.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/providers/dns/acmedns/acmedns.go b/providers/dns/acmedns/acmedns.go index 8dedeb128..15e0a1da7 100644 --- a/providers/dns/acmedns/acmedns.go +++ b/providers/dns/acmedns/acmedns.go @@ -172,14 +172,16 @@ func (d *DNSProvider) Present(domain, _, keyAuth string) error { // Check if credentials were previously saved for this domain. account, err := d.storage.Fetch(ctx, domain) if err != nil { - if errors.Is(err, storage.ErrDomainNotFound) { - // The account did not exist. - // Create a new one and return an error indicating the required one-time manual CNAME setup. - return d.register(ctx, domain, info.FQDN) + if !errors.Is(err, storage.ErrDomainNotFound) { + return err } - // Errors other than goacmedns.ErrDomainNotFound are unexpected. - return err + // The account did not exist. + // Create a new one and return an error indicating the required one-time manual CNAME setup. + err = d.register(ctx, domain, info.FQDN) + if err != nil { + return err + } } // Update the acme-dns TXT record. From 5a023462262caad8a6390760e81f75e02eac0a02 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 17 Feb 2025 15:59:23 +0100 Subject: [PATCH 049/298] Prepare release v4.22.1 --- CHANGELOG.md | 10 +++++++++- acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddbe73be0..25a20afe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Changelog -## [v4.22.0](https://github.com/go-acme/lego/releases/tag/v4.22.0) (2025-02-16) +## [v4.22.1](https://github.com/go-acme/lego/releases/tag/v4.22.1) (2025-02-17) + +### Fixed + +- **[dnsprovider]** acme-dns: continue the process when the CNAME is handled by the storage + +### Added + +## [v4.22.0](https://github.com/go-acme/lego/releases/tag/v4.22.0) (2025-02-17) ### Added diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 0ebaa55e0..7aecc86af 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.22.0" + ourUserAgent = "xenolf-acme/4.22.1" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index db3d9f438..a840159cd 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.22.0+dev-detach" +const defaultVersion = "v4.22.1+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 21bfb74e8..ed876a6dd 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.22.0" + ourUserAgent = "goacme-lego/4.22.1" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From d183572e93cd0e8951c0d653d1854d5d0f021587 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 17 Feb 2025 15:59:39 +0100 Subject: [PATCH 050/298] Detach v4.22.1 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 7aecc86af..7a3132c60 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index a840159cd..15d4c7689 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.22.1+dev-release" +const defaultVersion = "v4.22.1+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index ed876a6dd..b53960cb0 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From 584d3747146193ddc2b7ee37f503e2f101d1fae1 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 17 Feb 2025 20:37:45 +0100 Subject: [PATCH 051/298] acme-dns: use new registred account (#2445) --- providers/dns/acmedns/acmedns.go | 14 +- providers/dns/acmedns/acmedns_test.go | 164 +++++++++++++++++----- providers/dns/acmedns/mock_test.go | 195 ++++++++++++++------------ 3 files changed, 242 insertions(+), 131 deletions(-) diff --git a/providers/dns/acmedns/acmedns.go b/providers/dns/acmedns/acmedns.go index 15e0a1da7..a5e5a4078 100644 --- a/providers/dns/acmedns/acmedns.go +++ b/providers/dns/acmedns/acmedns.go @@ -178,7 +178,7 @@ func (d *DNSProvider) Present(domain, _, keyAuth string) error { // The account did not exist. // Create a new one and return an error indicating the required one-time manual CNAME setup. - err = d.register(ctx, domain, info.FQDN) + account, err = d.register(ctx, domain, info.FQDN) if err != nil { return err } @@ -200,10 +200,10 @@ func (d *DNSProvider) CleanUp(_, _, _ string) error { // If account creation works as expected a ErrCNAMERequired error is returned describing // the one-time manual CNAME setup required to complete setup of the ACME-DNS hook for the domain. // If any other error occurs it is returned as-is. -func (d *DNSProvider) register(ctx context.Context, domain, fqdn string) error { +func (d *DNSProvider) register(ctx context.Context, domain, fqdn string) (goacmedns.Account, error) { newAcct, err := d.client.RegisterAccount(ctx, d.config.AllowList) if err != nil { - return err + return goacmedns.Account{}, err } var cnameCreated bool @@ -213,23 +213,23 @@ func (d *DNSProvider) register(ctx context.Context, domain, fqdn string) error { if err != nil { cnameCreated = errors.Is(err, internal.ErrCNAMEAlreadyCreated) if !cnameCreated { - return err + return goacmedns.Account{}, err } } err = d.storage.Save(ctx) if err != nil { - return err + return goacmedns.Account{}, err } if cnameCreated { - return nil + return newAcct, nil } // Stop issuance by returning an error. // The user needs to perform a manual one-time CNAME setup in their DNS zone // to complete the setup of the new account we created. - return ErrCNAMERequired{ + return goacmedns.Account{}, ErrCNAMERequired{ Domain: domain, FQDN: fqdn, Target: newAcct.FullDomain, diff --git a/providers/dns/acmedns/acmedns_test.go b/providers/dns/acmedns/acmedns_test.go index 76aa097f5..081080d21 100644 --- a/providers/dns/acmedns/acmedns_test.go +++ b/providers/dns/acmedns/acmedns_test.go @@ -2,6 +2,8 @@ package acmedns import ( "context" + "net/http" + "net/http/httptest" "testing" "github.com/nrdcg/goacmedns" @@ -10,27 +12,17 @@ import ( ) const ( - // Fixed test data for unit tests. egDomain = "example.com" egFQDN = "_acme-challenge." + egDomain + "." egKeyAuth = "⚷" ) -// TestPresent tests that the ACME-DNS Present function for updating a DNS-01 -// challenge response TXT record works as expected. func TestPresent(t *testing.T) { // validAccountStorage is a mockStorage configured to return the egTestAccount. - validAccountStorage := mockStorage{ - map[string]goacmedns.Account{ - egDomain: egTestAccount, - }, - } - // validUpdateClient is a mockClient configured with the egTestAccount that will - // track TXT updates in a map. - validUpdateClient := mockUpdateClient{ - mockClient{egTestAccount}, - make(map[goacmedns.Account]string), - } + validAccountStorage := newMockStorage().WithAccount(egDomain, egTestAccount) + + // validUpdateClient is a mockClient configured with the egTestAccount that will track TXT updates in a map. + validUpdateClient := newMockClient() testCases := []struct { Name string @@ -40,13 +32,13 @@ func TestPresent(t *testing.T) { }{ { Name: "present when client storage returns unexpected error", - Client: mockClient{egTestAccount}, - Storage: errorFetchStorage{}, + Client: newMockClient().WithRegisterAccount(egTestAccount), + Storage: newMockStorage().WithFetchError(errorStorageErr), ExpectedError: errorStorageErr, }, { Name: "present when client storage returns ErrDomainNotFound", - Client: mockClient{egTestAccount}, + Client: newMockClient().WithRegisterAccount(egTestAccount), ExpectedError: ErrCNAMERequired{ Domain: egDomain, FQDN: egFQDN, @@ -55,7 +47,7 @@ func TestPresent(t *testing.T) { }, { Name: "present when client UpdateTXTRecord returns unexpected error", - Client: errorUpdateClient{}, + Client: newMockClient().WithUpdateTXTRecordError(errorClientErr), Storage: validAccountStorage, ExpectedError: errorClientErr, }, @@ -71,16 +63,13 @@ func TestPresent(t *testing.T) { p := &DNSProvider{ config: NewDefaultConfig(), client: test.Client, - storage: mockStorage{make(map[string]goacmedns.Account)}, + storage: newMockStorage(), } - // override the storage mock if required by the test case. if test.Storage != nil { p.storage = test.Storage } - // call Present. The token argument can be garbage because the ACME-DNS - // provider does not use it. err := p.Present(egDomain, "foo", egKeyAuth) if test.ExpectedError != nil { assert.Equal(t, test.ExpectedError, err) @@ -97,36 +86,33 @@ func TestPresent(t *testing.T) { assert.Len(t, validUpdateClient.records[egTestAccount], 43) } -// TestRegister tests that the ACME-DNS register function works correctly. func TestRegister(t *testing.T) { testCases := []struct { Name string Client acmeDNSClient Storage goacmedns.Storage - Domain string - FQDN string ExpectedError error }{ { Name: "register when acme-dns client returns an error", - Client: errorRegisterClient{}, + Client: newMockClient().WithRegisterAccountError(errorClientErr), ExpectedError: errorClientErr, }, { Name: "register when acme-dns storage put returns an error", - Client: mockClient{egTestAccount}, - Storage: errorPutStorage{mockStorage{make(map[string]goacmedns.Account)}}, + Client: newMockClient().WithRegisterAccount(egTestAccount), + Storage: newMockStorage().WithPutError(errorStorageErr), ExpectedError: errorStorageErr, }, { Name: "register when acme-dns storage save returns an error", - Client: mockClient{egTestAccount}, - Storage: errorSaveStorage{mockStorage{make(map[string]goacmedns.Account)}}, + Client: newMockClient().WithRegisterAccount(egTestAccount), + Storage: newMockStorage().WithSaveError(errorStorageErr), ExpectedError: errorStorageErr, }, { Name: "register when everything works", - Client: mockClient{egTestAccount}, + Client: newMockClient().WithRegisterAccount(egTestAccount), ExpectedError: ErrCNAMERequired{ Domain: egDomain, FQDN: egFQDN, @@ -140,21 +126,129 @@ func TestRegister(t *testing.T) { p := &DNSProvider{ config: NewDefaultConfig(), client: test.Client, - storage: mockStorage{make(map[string]goacmedns.Account)}, + storage: newMockStorage(), } - // override the storage mock if required by the testcase. if test.Storage != nil { p.storage = test.Storage } - // Call register for the example domain/fqdn. - err := p.register(context.Background(), egDomain, egFQDN) + acc, err := p.register(context.Background(), egDomain, egFQDN) if test.ExpectedError != nil { assert.Equal(t, test.ExpectedError, err) } else { + assert.Equal(t, goacmedns.Account{}, acc) require.NoError(t, err) } }) } } + +func TestPresent_httpStorage(t *testing.T) { + testCases := []struct { + desc string + StatusCode int + ExpectedError error + }{ + { + desc: "the CNAME is not handled by the storage", + StatusCode: http.StatusOK, + ExpectedError: ErrCNAMERequired{ + Domain: egDomain, + FQDN: egFQDN, + Target: egTestAccount.FullDomain, + }, + }, + { + desc: "the CNAME is handled by the storage", + StatusCode: http.StatusCreated, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + + config := NewDefaultConfig() + config.StorageBaseURL = server.URL + + p, err := NewDNSProviderConfig(config) + require.NoError(t, err) + + client := newMockClient().WithRegisterAccount(egTestAccount) + p.client = client + + // Fetch + mux.HandleFunc("GET /example.com", func(rw http.ResponseWriter, reg *http.Request) { + rw.WriteHeader(http.StatusNotFound) + }) + + // Put + mux.HandleFunc("POST /example.com", func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(test.StatusCode) + }) + + err = p.Present(egDomain, "foo", egKeyAuth) + if test.ExpectedError != nil { + assert.Equal(t, test.ExpectedError, err) + assert.True(t, client.registerAccountCalled) + assert.False(t, client.updateTXTRecordCalled) + } else { + require.NoError(t, err) + assert.True(t, client.registerAccountCalled) + assert.True(t, client.updateTXTRecordCalled) + } + }) + } +} + +func TestRegister_httpStorage(t *testing.T) { + testCases := []struct { + Name string + StatusCode int + ExpectedError error + }{ + { + Name: "status code 200", + StatusCode: http.StatusOK, + ExpectedError: ErrCNAMERequired{ + Domain: egDomain, + FQDN: egFQDN, + Target: egTestAccount.FullDomain, + }, + }, + { + Name: "status code 201", + StatusCode: http.StatusCreated, + }, + } + + for _, test := range testCases { + t.Run(test.Name, func(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + + config := NewDefaultConfig() + config.StorageBaseURL = server.URL + + p, err := NewDNSProviderConfig(config) + require.NoError(t, err) + + p.client = newMockClient().WithRegisterAccount(egTestAccount) + + // Put + mux.HandleFunc("POST /example.com", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(test.StatusCode) + }) + + acc, err := p.register(context.Background(), egDomain, egFQDN) + if test.ExpectedError != nil { + assert.Equal(t, test.ExpectedError, err) + } else { + require.NoError(t, err) + assert.Equal(t, egTestAccount, acc) + } + }) + } +} diff --git a/providers/dns/acmedns/mock_test.go b/providers/dns/acmedns/mock_test.go index 0d188ecc4..629f25a0c 100644 --- a/providers/dns/acmedns/mock_test.go +++ b/providers/dns/acmedns/mock_test.go @@ -22,122 +22,139 @@ var egTestAccount = goacmedns.Account{ Password: "trustno1", } -// mockClient is a mock implementing the acmeDNSClient interface that always -// returns a fixed goacmedns.Account from calls to Register. type mockClient struct { - mockAccount goacmedns.Account -} - -// UpdateTXTRecord does nothing. -func (c mockClient) UpdateTXTRecord(_ context.Context, _ goacmedns.Account, _ string) error { - return nil -} - -// RegisterAccount returns c.mockAccount and no errors. -func (c mockClient) RegisterAccount(_ context.Context, _ []string) (goacmedns.Account, error) { - return c.mockAccount, nil -} - -// mockUpdateClient is a mock implementing the acmeDNSClient interface that -// tracks the calls to UpdateTXTRecord in the records map. -type mockUpdateClient struct { - mockClient records map[goacmedns.Account]string + + updateTXTRecordCalled bool + updateTXTRecord func(ctx context.Context, acct goacmedns.Account, value string) error + + registerAccountCalled bool + registerAccount func(ctx context.Context, allowFrom []string) (goacmedns.Account, error) } -// UpdateTXTRecord saves a record value to c.records for the given acct. -func (c mockUpdateClient) UpdateTXTRecord(_ context.Context, acct goacmedns.Account, value string) error { +func newMockClient() *mockClient { + return &mockClient{ + records: make(map[goacmedns.Account]string), + updateTXTRecord: func(_ context.Context, _ goacmedns.Account, _ string) error { + return nil + }, + registerAccount: func(_ context.Context, _ []string) (goacmedns.Account, error) { + return goacmedns.Account{}, nil + }, + } +} + +func (c *mockClient) UpdateTXTRecord(ctx context.Context, acct goacmedns.Account, value string) error { + c.updateTXTRecordCalled = true c.records[acct] = value - return nil + + return c.updateTXTRecord(ctx, acct, value) } -// errorUpdateClient is a mock implementing the acmeDNSClient interface that always -// returns errors from errorUpdateClient. -type errorUpdateClient struct { - mockClient +func (c *mockClient) RegisterAccount(ctx context.Context, allowFrom []string) (goacmedns.Account, error) { + c.registerAccountCalled = true + return c.registerAccount(ctx, allowFrom) } -// UpdateTXTRecord always returns an error. -func (c errorUpdateClient) UpdateTXTRecord(_ context.Context, _ goacmedns.Account, _ string) error { - return errorClientErr +func (c *mockClient) WithUpdateTXTRecordError(err error) *mockClient { + c.updateTXTRecord = func(_ context.Context, _ goacmedns.Account, _ string) error { + return err + } + + return c } -// errorRegisterClient is a mock implementing the acmeDNSClient interface that always -// returns errors from RegisterAccount. -type errorRegisterClient struct { - mockClient -} - -// RegisterAccount always returns an error. -func (c errorRegisterClient) RegisterAccount(_ context.Context, _ []string) (goacmedns.Account, error) { - return goacmedns.Account{}, errorClientErr -} - -// mockStorage is a mock implementing the goacmedns.Storage interface that -// returns static account data and ignores Save. -type mockStorage struct { - accounts map[string]goacmedns.Account -} - -// Save does nothing. -func (m mockStorage) Save(_ context.Context) error { - return nil -} - -// Put stores an account for the given domain in m.accounts. -func (m mockStorage) Put(_ context.Context, domain string, acct goacmedns.Account) error { - m.accounts[domain] = acct - return nil -} - -// Fetch retrieves an account for the given domain from m.accounts or returns -// goacmedns.ErrDomainNotFound. -func (m mockStorage) Fetch(_ context.Context, domain string) (goacmedns.Account, error) { - if acct, ok := m.accounts[domain]; ok { +func (c *mockClient) WithRegisterAccount(acct goacmedns.Account) *mockClient { + c.registerAccount = func(_ context.Context, _ []string) (goacmedns.Account, error) { return acct, nil } - return goacmedns.Account{}, storage.ErrDomainNotFound + + return c } -// FetchAll returns all of m.accounts. -func (m mockStorage) FetchAll(_ context.Context) (map[string]goacmedns.Account, error) { - return m.accounts, nil +func (c *mockClient) WithRegisterAccountError(err error) *mockClient { + c.registerAccount = func(_ context.Context, _ []string) (goacmedns.Account, error) { + return goacmedns.Account{}, err + } + + return c } -// errorPutStorage is a mock implementing the goacmedns.Storage interface that -// always returns errors from Put. -type errorPutStorage struct { - mockStorage +type mockStorage struct { + accounts map[string]goacmedns.Account + fetchAll func(ctx context.Context) (map[string]goacmedns.Account, error) + fetch func(ctx context.Context, domain string) (goacmedns.Account, error) + put func(ctx context.Context, domain string, acct goacmedns.Account) error + save func(ctx context.Context) error } -// Put always errors. -func (e errorPutStorage) Put(_ context.Context, _ string, _ goacmedns.Account) error { - return errorStorageErr +func newMockStorage() *mockStorage { + m := &mockStorage{ + accounts: make(map[string]goacmedns.Account), + put: func(_ context.Context, _ string, _ goacmedns.Account) error { + return nil + }, + save: func(_ context.Context) error { + return nil + }, + } + + m.fetchAll = func(ctx context.Context) (map[string]goacmedns.Account, error) { + return m.accounts, nil + } + + m.fetch = func(_ context.Context, domain string) (goacmedns.Account, error) { + if acct, ok := m.accounts[domain]; ok { + return acct, nil + } + return goacmedns.Account{}, storage.ErrDomainNotFound + } + + return m } -// errorSaveStorage is a mock implementing the goacmedns.Storage interface that -// always returns errors from Save. -type errorSaveStorage struct { - mockStorage +func (m *mockStorage) FetchAll(ctx context.Context) (map[string]goacmedns.Account, error) { + return m.fetchAll(ctx) } -// Save always errors. -func (e errorSaveStorage) Save(_ context.Context) error { - return errorStorageErr +func (m *mockStorage) Fetch(ctx context.Context, domain string) (goacmedns.Account, error) { + return m.fetch(ctx, domain) } -// errorFetchStorage is a mock implementing the goacmedns.Storage interface that -// always returns errors from Fetch. -type errorFetchStorage struct { - mockStorage +func (m *mockStorage) Put(ctx context.Context, domain string, account goacmedns.Account) error { + return m.put(ctx, domain, account) } -// Fetch always errors. -func (e errorFetchStorage) Fetch(_ context.Context, _ string) (goacmedns.Account, error) { - return goacmedns.Account{}, errorStorageErr +func (m *mockStorage) Save(ctx context.Context) error { + return m.save(ctx) } -// FetchAll is a nop for errorFetchStorage. -func (e errorFetchStorage) FetchAll(_ context.Context) (map[string]goacmedns.Account, error) { - return nil, nil +func (m *mockStorage) WithAccount(domain string, acct goacmedns.Account) *mockStorage { + m.accounts[domain] = acct + + return m +} + +func (m *mockStorage) WithFetchError(err error) *mockStorage { + m.fetch = func(_ context.Context, _ string) (goacmedns.Account, error) { + return goacmedns.Account{}, err + } + + return m +} + +func (m *mockStorage) WithPutError(err error) *mockStorage { + m.put = func(_ context.Context, _ string, _ goacmedns.Account) error { + return err + } + + return m +} + +func (m *mockStorage) WithSaveError(err error) *mockStorage { + m.save = func(ctx context.Context) error { + return err + } + + return m } From c0c8bef7837fd3e2778a0036fdc10c03dc20973e Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 17 Feb 2025 20:38:48 +0100 Subject: [PATCH 052/298] chore: update goreleaser configuration --- .goreleaser.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index c3812ec01..9bf101420 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -54,10 +54,10 @@ changelog: archives: - id: lego name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}' - format: tar.gz + formats: ['tar.gz'] format_overrides: - goos: windows - format: zip + formats: ['zip'] files: - LICENSE - CHANGELOG.md From dca1090bb5a3c991bc48da5b267c8082763bf103 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 17 Feb 2025 20:39:37 +0100 Subject: [PATCH 053/298] Prepare release v4.22.2 --- CHANGELOG.md | 6 ++++++ acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25a20afe6..cf51b1fea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [v4.22.2](https://github.com/go-acme/lego/releases/tag/v4.22.2) (2025-02-17) + +### Fixed + +- **[dnsprovider]** acme-dns: use new registred account + ## [v4.22.1](https://github.com/go-acme/lego/releases/tag/v4.22.1) (2025-02-17) ### Fixed diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 7a3132c60..f55a0b0cb 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.22.1" + ourUserAgent = "xenolf-acme/4.22.2" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 15d4c7689..76a4e2a87 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.22.1+dev-detach" +const defaultVersion = "v4.22.2+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index b53960cb0..feb0649f8 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.22.1" + ourUserAgent = "goacme-lego/4.22.2" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From 526ac35e5c9bf55c3bb8d13ed06e0d59c578acdf Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 17 Feb 2025 20:39:48 +0100 Subject: [PATCH 054/298] Detach v4.22.2 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index f55a0b0cb..ec6426456 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 76a4e2a87..b03a531a4 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.22.2+dev-release" +const defaultVersion = "v4.22.2+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index feb0649f8..4fb495ca7 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From 0ab907c183d7b9371c7cf35336a54eb3cfd27634 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 18 Feb 2025 20:10:57 +0100 Subject: [PATCH 055/298] chore: use go1.23 (#2446) --- acme/api/order_test.go | 2 +- certcrypto/crypto_test.go | 35 +++++++------------ challenge/dns01/dns_challenge_test.go | 6 ++-- challenge/http01/http_challenge_test.go | 8 ++--- challenge/resolver/solver_manager_test.go | 2 +- .../tlsalpn01/tls_alpn_challenge_test.go | 6 ++-- go.mod | 2 +- lego/client_test.go | 3 +- providers/dns/hyperone/internal/token_test.go | 24 ++++--------- providers/dns/oraclecloud/oraclecloud_test.go | 2 +- registration/registar_test.go | 2 +- 11 files changed, 36 insertions(+), 56 deletions(-) diff --git a/acme/api/order_test.go b/acme/api/order_test.go index 26aaa3713..3cfe79a76 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -20,7 +20,7 @@ func TestOrderService_NewWithOptions(t *testing.T) { mux, apiURL := tester.SetupFakeAPI(t) // small value keeps test fast - privateKey, errK := rsa.GenerateKey(rand.Reader, 512) + privateKey, errK := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, errK, "Could not generate test key") mux.HandleFunc("/newOrder", func(w http.ResponseWriter, r *http.Request) { diff --git a/certcrypto/crypto_test.go b/certcrypto/crypto_test.go index 4768435d1..118c19389 100644 --- a/certcrypto/crypto_test.go +++ b/certcrypto/crypto_test.go @@ -6,7 +6,6 @@ import ( "crypto/rand" "crypto/rsa" "encoding/pem" - "regexp" "testing" "time" @@ -22,7 +21,7 @@ func TestGeneratePrivateKey(t *testing.T) { } func TestGenerateCSR(t *testing.T) { - privateKey, err := rsa.GenerateKey(rand.Reader, 512) + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Error generating private key") type expected struct { @@ -43,7 +42,7 @@ func TestGenerateCSR(t *testing.T) { Domain: "lego.acme", MustStaple: true, }, - expected: expected{len: 245}, + expected: expected{len: 379}, }, { desc: "without SAN (empty)", @@ -53,7 +52,7 @@ func TestGenerateCSR(t *testing.T) { SAN: []string{}, MustStaple: true, }, - expected: expected{len: 245}, + expected: expected{len: 379}, }, { desc: "with SAN", @@ -63,7 +62,7 @@ func TestGenerateCSR(t *testing.T) { SAN: []string{"a.lego.acme", "b.lego.acme", "c.lego.acme"}, MustStaple: true, }, - expected: expected{len: 296}, + expected: expected{len: 430}, }, { desc: "no domain", @@ -72,7 +71,7 @@ func TestGenerateCSR(t *testing.T) { Domain: "", MustStaple: true, }, - expected: expected{len: 225}, + expected: expected{len: 359}, }, { desc: "no domain with SAN", @@ -82,7 +81,7 @@ func TestGenerateCSR(t *testing.T) { SAN: []string{"a.lego.acme", "b.lego.acme", "c.lego.acme"}, MustStaple: true, }, - expected: expected{len: 276}, + expected: expected{len: 409}, }, { desc: "private key nil", @@ -101,7 +100,7 @@ func TestGenerateCSR(t *testing.T) { SAN: []string{"example.org"}, EmailAddresses: []string{"foo@example.com", "bar@example.com"}, }, - expected: expected{len: 287}, + expected: expected{len: 421}, }, } @@ -124,17 +123,17 @@ func TestGenerateCSR(t *testing.T) { } func TestPEMEncode(t *testing.T) { - buf := bytes.NewBufferString("TestingRSAIsSoMuchFun") - - reader := MockRandReader{b: buf} - key, err := rsa.GenerateKey(reader, 32) + key, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Error generating private key") data := PEMEncode(key) require.NotNil(t, data) - exp := regexp.MustCompile(`^-----BEGIN RSA PRIVATE KEY-----\s+\S{60,}\s+-----END RSA PRIVATE KEY-----\s+`) - assert.Regexp(t, exp, string(data)) + p, rest := pem.Decode(data) + + assert.Equal(t, "RSA PRIVATE KEY", p.Type) + assert.Empty(t, rest) + assert.Empty(t, p.Headers) } func TestParsePEMCertificate(t *testing.T) { @@ -186,11 +185,3 @@ func TestParsePEMPrivateKey(t *testing.T) { _, err = ParsePEMPrivateKey([]byte("This is not PEM")) require.Errorf(t, err, "Expected to return an error for non-PEM input") } - -type MockRandReader struct { - b *bytes.Buffer -} - -func (r MockRandReader) Read(p []byte) (int, error) { - return r.b.Read(p) -} diff --git a/challenge/dns01/dns_challenge_test.go b/challenge/dns01/dns_challenge_test.go index 953180326..c09273c2a 100644 --- a/challenge/dns01/dns_challenge_test.go +++ b/challenge/dns01/dns_challenge_test.go @@ -34,7 +34,7 @@ func (p *providerTimeoutMock) Timeout() (time.Duration, time.Duration) { ret func TestChallenge_PreSolve(t *testing.T) { _, apiURL := tester.SetupFakeAPI(t) - privateKey, err := rsa.GenerateKey(rand.Reader, 512) + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) @@ -116,7 +116,7 @@ func TestChallenge_PreSolve(t *testing.T) { func TestChallenge_Solve(t *testing.T) { _, apiURL := tester.SetupFakeAPI(t) - privateKey, err := rsa.GenerateKey(rand.Reader, 512) + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) @@ -203,7 +203,7 @@ func TestChallenge_Solve(t *testing.T) { func TestChallenge_CleanUp(t *testing.T) { _, apiURL := tester.SetupFakeAPI(t) - privateKey, err := rsa.GenerateKey(rand.Reader, 512) + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) diff --git a/challenge/http01/http_challenge_test.go b/challenge/http01/http_challenge_test.go index 3a5aa6bbe..c29c49068 100644 --- a/challenge/http01/http_challenge_test.go +++ b/challenge/http01/http_challenge_test.go @@ -97,7 +97,7 @@ func TestChallenge(t *testing.T) { return nil } - privateKey, err := rsa.GenerateKey(rand.Reader, 512) + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) @@ -166,7 +166,7 @@ func TestChallengeUnix(t *testing.T) { return nil } - privateKey, err := rsa.GenerateKey(rand.Reader, 512) + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) @@ -190,7 +190,7 @@ func TestChallengeUnix(t *testing.T) { func TestChallengeInvalidPort(t *testing.T) { _, apiURL := tester.SetupFakeAPI(t) - privateKey, err := rsa.GenerateKey(rand.Reader, 128) + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) @@ -411,7 +411,7 @@ func testServeWithProxy(t *testing.T, header, extra *testProxyHeader, expectErro return nil } - privateKey, err := rsa.GenerateKey(rand.Reader, 512) + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) diff --git a/challenge/resolver/solver_manager_test.go b/challenge/resolver/solver_manager_test.go index 113f4963e..b1e198d3c 100644 --- a/challenge/resolver/solver_manager_test.go +++ b/challenge/resolver/solver_manager_test.go @@ -36,7 +36,7 @@ func TestValidate(t *testing.T) { var statuses []string - privateKey, _ := rsa.GenerateKey(rand.Reader, 512) + privateKey, _ := rsa.GenerateKey(rand.Reader, 1024) mux.HandleFunc("/chlg", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { diff --git a/challenge/tlsalpn01/tls_alpn_challenge_test.go b/challenge/tlsalpn01/tls_alpn_challenge_test.go index 8725a1360..55ef4e23d 100644 --- a/challenge/tlsalpn01/tls_alpn_challenge_test.go +++ b/challenge/tlsalpn01/tls_alpn_challenge_test.go @@ -66,7 +66,7 @@ func TestChallenge(t *testing.T) { return nil } - privateKey, err := rsa.GenerateKey(rand.Reader, 512) + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) @@ -95,7 +95,7 @@ func TestChallenge(t *testing.T) { func TestChallengeInvalidPort(t *testing.T) { _, apiURL := tester.SetupFakeAPI(t) - privateKey, err := rsa.GenerateKey(rand.Reader, 128) + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) @@ -167,7 +167,7 @@ func TestChallengeIPaddress(t *testing.T) { return nil } - privateKey, err := rsa.GenerateKey(rand.Reader, 512) + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) diff --git a/go.mod b/go.mod index 171e71e5b..7ff02829a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/go-acme/lego/v4 -go 1.22.0 +go 1.23.0 require ( cloud.google.com/go/compute/metadata v0.6.0 diff --git a/lego/client_test.go b/lego/client_test.go index 7d2f514dc..4f07eb1ea 100644 --- a/lego/client_test.go +++ b/lego/client_test.go @@ -15,8 +15,7 @@ import ( func TestNewClient(t *testing.T) { _, apiURL := tester.SetupFakeAPI(t) - keyBits := 32 // small value keeps test fast - key, err := rsa.GenerateKey(rand.Reader, keyBits) + key, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") user := mockUser{ diff --git a/providers/dns/hyperone/internal/token_test.go b/providers/dns/hyperone/internal/token_test.go index 243e015e8..315d0896f 100644 --- a/providers/dns/hyperone/internal/token_test.go +++ b/providers/dns/hyperone/internal/token_test.go @@ -1,31 +1,18 @@ package internal import ( + "crypto/rand" + "crypto/rsa" "encoding/base64" "encoding/json" "strings" "testing" + "github.com/go-acme/lego/v4/certcrypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -const privateKey = `-----BEGIN RSA PRIVATE KEY----- -MIICWgIBAAKBgGFfgMY+DuO8l0RYrMLhcl6U/NigNIiOVhoo/xnYyoQALpWxBaBR -+iVJiBUYunQjKA33yAiY0AasCfSn1JB6asayQvGGn73xztLjkeCVLT+9e4nJ0A/o -dK8SOKBg9FFe70KJrWjJd626el0aVDJjtCE+QxJExA0UZbQp+XIyveQXAgMBAAEC -gYBHcL1XNWLRPaWx9GlUVfoGYMMd4HSKl/ueF+QKP59dt5B2LTnWhS7FOqzH5auu -17hkfx3ZCNzfeEuZn6T6F4bMtsQ6A5iT/DeRlG8tOPiCVZ/L0j6IFM78iIUT8XyA -miwnSy1xGSBA67yUmsLxFg2DtGCjamAkY0C5pccadaB7oQJBAKsIPpMXMni+Oo1I -kVxRyoIZgDxsMJiihG2YLVqo8rPtdErl+Lyg3ziVyg9KR6lFMaTBkYBTLoCPof3E -AB/jyucCQQCRv1cVnYNx+bfnXsBlcsCFDV2HkEuLTpxj7hauD4P3GcyLidSsUkn1 -PiPunZqKpsQaIoxc/BzTOCcP19ifgqdRAkBJ8Cp9FE4xfKt7YJ/WtVVCoRubA3qO -wdNWPa99vgQOXN0lc/3wLevSXo8XxRjtyIgJndT1EQDNe0qglhcnsiaJAkBziAcR -/VAq0tZys2szf6kYTyXqxfj8Lo5NsHeN9oKXJ346xkEtb/VsT5vQFGJishsU1HoL -Y1W+IO7l4iW3G6xhAkACNwtqxSRRbVsNCUMENpKmYhsyN8QXJ8V+o2A9s+pl21Kz -HIIm179mUYCgO6iAHmkqxlFHFwprUBKdPrmP8qF9 ------END RSA PRIVATE KEY-----` - type Header struct { Algorithm string `json:"alg"` Type string `json:"typ"` @@ -33,7 +20,10 @@ type Header struct { } func TestPayload_buildToken(t *testing.T) { - signer, err := getRSASigner(privateKey, "sampleKeyId") + key, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(t, err) + + signer, err := getRSASigner(string(certcrypto.PEMEncode(key)), "sampleKeyId") require.NoError(t, err) payload := Payload{IssuedAt: 1234, Expiry: 4321, Audience: "api.url", Issuer: "issuer", Subject: "subject"} diff --git a/providers/dns/oraclecloud/oraclecloud_test.go b/providers/dns/oraclecloud/oraclecloud_test.go index 9fff79ea1..34a0ed6da 100644 --- a/providers/dns/oraclecloud/oraclecloud_test.go +++ b/providers/dns/oraclecloud/oraclecloud_test.go @@ -320,7 +320,7 @@ func mustGeneratePrivateKeyFile(pwd string) string { } func generatePrivateKey(pwd string) (*pem.Block, error) { - key, err := rsa.GenerateKey(rand.Reader, 512) + key, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { return nil, err } diff --git a/registration/registar_test.go b/registration/registar_test.go index efbc4f6f7..45fe33e82 100644 --- a/registration/registar_test.go +++ b/registration/registar_test.go @@ -27,7 +27,7 @@ func TestRegistrar_ResolveAccountByKey(t *testing.T) { } }) - key, err := rsa.GenerateKey(rand.Reader, 512) + key, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") user := mockUser{ From f1afe5225115eb71e4aed5f6c9e67268eabc0c95 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 23 Feb 2025 21:02:11 +0100 Subject: [PATCH 056/298] fix: malformed log messages (#2452) --- .golangci.yml | 14 ++++++++++++-- cmd/cmd_renew.go | 6 +++--- providers/dns/cloudflare/cloudflare.go | 2 +- providers/dns/netcup/netcup.go | 4 ++-- providers/dns/shellrent/shellrent.go | 4 +--- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 2a3868ff4..80d9c2b49 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -40,8 +40,18 @@ linters: linters-settings: govet: - enable: - - shadow + enable-all: true + disable: + - fieldalignment + settings: + printf: + funcs: + - Print + - Printf + - Warn + - Warnf + - Fatal + - Fatalf gocyclo: min-complexity: 12 goconst: diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index 8d2135022..6589fbbdd 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -40,13 +40,13 @@ func createRenew() *cli.Command { hasDomains := len(ctx.StringSlice(flgDomains)) > 0 hasCsr := ctx.String(flgCSR) != "" if hasDomains && hasCsr { - log.Fatal("Please specify either --%s/-d or --%s/-c, but not both", flgDomains, flgCSR) + log.Fatalf("Please specify either --%s/-d or --%s/-c, but not both", flgDomains, flgCSR) } if !hasDomains && !hasCsr { - log.Fatal("Please specify --%s/-d (or --%s/-c if you already have a CSR)", flgDomains, flgCSR) + log.Fatalf("Please specify --%s/-d (or --%s/-c if you already have a CSR)", flgDomains, flgCSR) } if ctx.Bool(flgForceCertDomains) && hasCsr { - log.Fatal("--%s only works with --%s/-d, --%s/-c doesn't support this option.", flgForceCertDomains, flgDomains, flgCSR) + log.Fatalf("--%s only works with --%s/-d, --%s/-c doesn't support this option.", flgForceCertDomains, flgDomains, flgCSR) } return nil }, diff --git a/providers/dns/cloudflare/cloudflare.go b/providers/dns/cloudflare/cloudflare.go index d65926f81..ce63c86bf 100644 --- a/providers/dns/cloudflare/cloudflare.go +++ b/providers/dns/cloudflare/cloudflare.go @@ -205,7 +205,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { err = d.client.DeleteDNSRecord(context.Background(), zoneID, recordID) if err != nil { - log.Printf("cloudflare: failed to delete TXT record: %w", err) + log.Printf("cloudflare: failed to delete TXT record: %v", err) } // Delete record ID from map diff --git a/providers/dns/netcup/netcup.go b/providers/dns/netcup/netcup.go index 4f4500e90..f0544bbcd 100644 --- a/providers/dns/netcup/netcup.go +++ b/providers/dns/netcup/netcup.go @@ -114,7 +114,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { defer func() { err = d.client.Logout(ctx) if err != nil { - log.Print("netcup: %v", err) + log.Printf("netcup: %v", err) } }() @@ -160,7 +160,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { defer func() { err = d.client.Logout(ctx) if err != nil { - log.Print("netcup: %v", err) + log.Printf("netcup: %v", err) } }() diff --git a/providers/dns/shellrent/shellrent.go b/providers/dns/shellrent/shellrent.go index dec1540c8..488509a84 100644 --- a/providers/dns/shellrent/shellrent.go +++ b/providers/dns/shellrent/shellrent.go @@ -123,9 +123,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { zone, err := d.findZone(ctx, dns01.UnFqdn(info.EffectiveFQDN)) if err != nil { - if err != nil { - return fmt.Errorf("shellrent: could not find zone for domain %q: %w", domain, err) - } + return fmt.Errorf("shellrent: could not find zone for domain %q: %w", domain, err) } subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone.DomainName) From d8c11a8cf5e00f49468eab5d88f72fd31b417b4f Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 23 Feb 2025 21:48:19 +0100 Subject: [PATCH 057/298] Add DNS provider for BookMyName (#2316) --- README.md | 68 ++++----- cmd/zz_gen_cmd_dnshelp.go | 22 +++ docs/content/dns/zz_gen_bookmyname.md | 69 +++++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/bookmyname/bookmyname.go | 138 +++++++++++++++++ providers/dns/bookmyname/bookmyname.toml | 24 +++ providers/dns/bookmyname/bookmyname_test.go | 143 ++++++++++++++++++ providers/dns/bookmyname/internal/client.go | 118 +++++++++++++++ .../dns/bookmyname/internal/client_test.go | 113 ++++++++++++++ .../internal/fixtures/add_success.txt | 1 + .../bookmyname/internal/fixtures/error.txt | 1 + .../internal/fixtures/remove_success.txt | 1 + providers/dns/bookmyname/internal/types.go | 8 + providers/dns/zz_gen_dns_providers.go | 3 + 14 files changed, 676 insertions(+), 35 deletions(-) create mode 100644 docs/content/dns/zz_gen_bookmyname.md create mode 100644 providers/dns/bookmyname/bookmyname.go create mode 100644 providers/dns/bookmyname/bookmyname.toml create mode 100644 providers/dns/bookmyname/bookmyname_test.go create mode 100644 providers/dns/bookmyname/internal/client.go create mode 100644 providers/dns/bookmyname/internal/client_test.go create mode 100644 providers/dns/bookmyname/internal/fixtures/add_success.txt create mode 100644 providers/dns/bookmyname/internal/fixtures/error.txt create mode 100644 providers/dns/bookmyname/internal/fixtures/remove_success.txt create mode 100644 providers/dns/bookmyname/internal/types.go diff --git a/README.md b/README.md index 2a8dae5c1..05c5a9b72 100644 --- a/README.md +++ b/README.md @@ -68,175 +68,175 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Bindman Bluecat + BookMyName Brandit (deprecated) Bunny Checkdomain - Civo + Civo Cloud.ru CloudDNS Cloudflare - ClouDNS + ClouDNS CloudXNS (Deprecated) ConoHa Constellix - Core-Networks + Core-Networks CPanel/WHM Derak Cloud deSEC.io - Designate DNSaaS for Openstack + Designate DNSaaS for Openstack Digital Ocean DirectAdmin DNS Made Easy - dnsHome.de + dnsHome.de DNSimple DNSPod (deprecated) Domain Offensive (do.de) - Domeneshop + Domeneshop DreamHost Duck DNS Dyn - Dynu + Dynu EasyDNS Efficient IP Epik - Exoscale + Exoscale External program freemyip.com G-Core - Gandi + Gandi Gandi Live DNS (v5) Glesys Go Daddy - Google Cloud + Google Cloud Google Domains Hetzner Hosting.de - Hosttech + Hosttech HTTP request http.net Huawei Cloud - Hurricane Electric DNS + Hurricane Electric DNS HyperOne IBM Cloud (SoftLayer) IIJ DNS Platform Service - Infoblox + Infoblox Infomaniak Internet Initiative Japan Internet.bs - INWX + INWX Ionos IPv64 iwantmyname - Joker + Joker Joohoi's ACME-DNS Liara Lima-City - Linode (v4) + Linode (v4) Liquid Web Loopia LuaDNS - Mail-in-a-Box + Mail-in-a-Box ManageEngine CloudDNS Manual Metaname - mijn.host + mijn.host Mittwald myaddr.{tools,dev,io} MyDNS.jp - MythicBeasts + MythicBeasts Name.com Namecheap Namesilo - NearlyFreeSpeech.NET + NearlyFreeSpeech.NET Netcup Netlify Nicmanager - NIFCloud + NIFCloud Njalla Nodion NS1 - Open Telekom Cloud + Open Telekom Cloud Oracle Cloud OVH plesk.com - Porkbun + Porkbun PowerDNS Rackspace Rain Yun/雨云 - RcodeZero + 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 Spaceship Stackpath - Technitium + Technitium Tencent Cloud DNS Timeweb Cloud TransIP - UKFast SafeDNS + UKFast SafeDNS Ultradns Variomedia VegaDNS - Vercel + Vercel Versio.[nl|eu|uk] VinylDNS VK Cloud - Volcano Engine/火山引擎 + Volcano Engine/火山引擎 Vscale Vultr Webnames - Websupport + Websupport WEDOS West.cn/西部数码 Yandex 360 - Yandex Cloud + Yandex Cloud Yandex PDD Zone.ee Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index d2150d07e..4f02a7879 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -23,6 +23,7 @@ func allDNSCodes() string { "azuredns", "bindman", "bluecat", + "bookmyname", "brandit", "bunny", "checkdomain", @@ -397,6 +398,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/bluecat`) + case "bookmyname": + // generated from: providers/dns/bookmyname/bookmyname.toml + ew.writeln(`Configuration for BookMyName.`) + ew.writeln(`Code: 'bookmyname'`) + ew.writeln(`Since: 'v4.23.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "BOOKMYNAME_PASSWORD": Password`) + ew.writeln(` - "BOOKMYNAME_USERNAME": Username`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "BOOKMYNAME_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "BOOKMYNAME_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "BOOKMYNAME_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "BOOKMYNAME_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/bookmyname`) + case "brandit": // generated from: providers/dns/brandit/brandit.toml ew.writeln(`Configuration for Brandit (deprecated).`) diff --git a/docs/content/dns/zz_gen_bookmyname.md b/docs/content/dns/zz_gen_bookmyname.md new file mode 100644 index 000000000..3f5d1f2c3 --- /dev/null +++ b/docs/content/dns/zz_gen_bookmyname.md @@ -0,0 +1,69 @@ +--- +title: "BookMyName" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: bookmyname +dnsprovider: + since: "v4.23.0" + code: "bookmyname" + url: "https://www.bookmyname.com/" +--- + + + + + + +Configuration for [BookMyName](https://www.bookmyname.com/). + + + + +- Code: `bookmyname` +- Since: v4.23.0 + + +Here is an example bash command using the BookMyName provider: + +```bash +BOOKMYNAME_USERNAME="xxx" \ +BOOKMYNAME_PASSWORD="yyy" \ +lego --email you@example.com --dns bookmyname -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `BOOKMYNAME_PASSWORD` | Password | +| `BOOKMYNAME_USERNAME` | Username | + +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 | +|--------------------------------|-------------| +| `BOOKMYNAME_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `BOOKMYNAME_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `BOOKMYNAME_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `BOOKMYNAME_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://fr.faqs.bookmyname.com/frfaqs/dyndns) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 995de5ced..2f02d4da7 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -149,7 +149,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, manageengine, manual, metaname, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi + acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, bookmyname, 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, manageengine, manual, metaname, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/bookmyname/bookmyname.go b/providers/dns/bookmyname/bookmyname.go new file mode 100644 index 000000000..991420619 --- /dev/null +++ b/providers/dns/bookmyname/bookmyname.go @@ -0,0 +1,138 @@ +// Package bookmyname implements a DNS provider for solving the DNS-01 challenge using BookMyName. +package bookmyname + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/bookmyname/internal" +) + +// Environment variables names. +const ( + envNamespace = "BOOKMYNAME_" + + EnvUsername = envNamespace + "USERNAME" + EnvPassword = envNamespace + "PASSWORD" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + Username string + Password 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for BookMyName. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvUsername, EnvPassword) + if err != nil { + return nil, fmt.Errorf("bookmyname: %w", err) + } + + config := NewDefaultConfig() + config.Username = values[EnvUsername] + config.Password = values[EnvPassword] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for BookMyName. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("bookmyname: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.Username, config.Password) + if err != nil { + return nil, fmt.Errorf("bookmyname: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + }, 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 := internal.Record{ + Hostname: dns01.UnFqdn(info.EffectiveFQDN), + Type: "txt", + TTL: d.config.TTL, + Value: info.Value, + } + + err := d.client.AddRecord(context.Background(), record) + if err != nil { + return fmt.Errorf("bookmyname: add record: %w", err) + } + + 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) + + record := internal.Record{ + Hostname: dns01.UnFqdn(info.EffectiveFQDN), + Type: "txt", + TTL: d.config.TTL, + Value: info.Value, + } + + err := d.client.RemoveRecord(context.Background(), record) + if err != nil { + return fmt.Errorf("bookmyname: add record: %w", err) + } + + 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/bookmyname/bookmyname.toml b/providers/dns/bookmyname/bookmyname.toml new file mode 100644 index 000000000..5111c4fbd --- /dev/null +++ b/providers/dns/bookmyname/bookmyname.toml @@ -0,0 +1,24 @@ +Name = "BookMyName" +Description = '''''' +URL = "https://www.bookmyname.com/" +Code = "bookmyname" +Since = "v4.23.0" + +Example = ''' +BOOKMYNAME_USERNAME="xxx" \ +BOOKMYNAME_PASSWORD="yyy" \ +lego --email you@example.com --dns bookmyname -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + BOOKMYNAME_USERNAME = "Username" + BOOKMYNAME_PASSWORD = "Password" + [Configuration.Additional] + BOOKMYNAME_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + BOOKMYNAME_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + BOOKMYNAME_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + BOOKMYNAME_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://fr.faqs.bookmyname.com/frfaqs/dyndns" diff --git a/providers/dns/bookmyname/bookmyname_test.go b/providers/dns/bookmyname/bookmyname_test.go new file mode 100644 index 000000000..dd02d63d7 --- /dev/null +++ b/providers/dns/bookmyname/bookmyname_test.go @@ -0,0 +1,143 @@ +package bookmyname + +import ( + "testing" + + "github.com/go-acme/lego/v4/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: "user", + EnvPassword: "secret", + }, + }, + { + desc: "missing username", + envVars: map[string]string{ + EnvUsername: "", + EnvPassword: "secret", + }, + expected: "bookmyname: some credentials information are missing: BOOKMYNAME_USERNAME", + }, + { + desc: "missing paswword", + envVars: map[string]string{ + EnvUsername: "user", + EnvPassword: "", + }, + expected: "bookmyname: some credentials information are missing: BOOKMYNAME_PASSWORD", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "bookmyname: some credentials information are missing: BOOKMYNAME_USERNAME,BOOKMYNAME_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 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 + username string + password string + expected string + }{ + { + desc: "success", + username: "user", + password: "secret", + }, + { + desc: "missing username", + password: "secret", + expected: "bookmyname: credentials missing", + }, + { + desc: "missing password", + username: "user", + expected: "bookmyname: credentials missing", + }, + { + desc: "missing credentials", + expected: "bookmyname: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Username = test.username + config.Password = test.password + + 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/bookmyname/internal/client.go b/providers/dns/bookmyname/internal/client.go new file mode 100644 index 000000000..08d4cccce --- /dev/null +++ b/providers/dns/bookmyname/internal/client.go @@ -0,0 +1,118 @@ +package internal + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + querystring "github.com/google/go-querystring/query" +) + +const defaultBaseURL = "https://www.bookmyname.com/dyndns/" + +// Client the BookMyName API client. +type Client struct { + username string + password string + + baseURL string + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(username, password string) (*Client, error) { + if username == "" || password == "" { + return nil, errors.New("credentials missing") + } + + return &Client{ + username: username, + password: password, + baseURL: defaultBaseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) AddRecord(ctx context.Context, record Record) error { + endpoint, err := c.createEndpoint(record, "add") + if err != nil { + return err + } + + err = c.do(ctx, endpoint) + if err != nil { + return err + } + + return nil +} + +func (c *Client) RemoveRecord(ctx context.Context, record Record) error { + endpoint, err := c.createEndpoint(record, "remove") + if err != nil { + return err + } + + err = c.do(ctx, endpoint) + if err != nil { + return err + } + + return nil +} + +func (c *Client) createEndpoint(record Record, action string) (*url.URL, error) { + endpoint, err := url.Parse(c.baseURL) + if err != nil { + return nil, fmt.Errorf("parse URL: %w", err) + } + + values, err := querystring.Values(record) + if err != nil { + return nil, fmt.Errorf("query parameters: %w", err) + } + + values.Set("do", action) + + endpoint.RawQuery = values.Encode() + + return endpoint, nil +} + +func (c *Client) do(ctx context.Context, endpoint *url.URL) error { + endpoint.User = url.UserPassword(c.username, c.password) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), http.NoBody) + if err != nil { + return fmt.Errorf("unable to create request: %w", err) + } + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + if resp.StatusCode/100 != 2 { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + if !strings.HasPrefix(string(raw), "good: update done") && !strings.HasPrefix(string(raw), "good: remove done") { + return fmt.Errorf("unexpected response: %s", string(bytes.TrimSpace(raw))) + } + + return nil +} diff --git a/providers/dns/bookmyname/internal/client_test.go b/providers/dns/bookmyname/internal/client_test.go new file mode 100644 index 000000000..dab559cee --- /dev/null +++ b/providers/dns/bookmyname/internal/client_test.go @@ -0,0 +1,113 @@ +package internal + +import ( + "context" + "fmt" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func setupTest(t *testing.T, filename string) *Client { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc("GET /", func(rw http.ResponseWriter, req *http.Request) { + username, password, ok := req.BasicAuth() + if username != "user" || password != "secret" || !ok { + http.Error(rw, fmt.Sprintf("auth: user %s, password %s, malformed", username, password), http.StatusOK) + return + } + + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + defer func() { _ = file.Close() }() + + rw.WriteHeader(http.StatusOK) + _, err = io.Copy(rw, file) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + }) + + client, err := NewClient("user", "secret") + require.NoError(t, err) + + client.HTTPClient = server.Client() + client.baseURL = server.URL + + return client +} + +func TestClient_AddRecord(t *testing.T) { + client := setupTest(t, "add_success.txt") + + record := Record{ + Hostname: "_acme-challenge.sub.example.com.", + Type: "txt", + TTL: 300, + Value: "test", + } + + err := client.AddRecord(context.Background(), record) + require.NoError(t, err) +} + +func TestClient_AddRecord_error(t *testing.T) { + client := setupTest(t, "error.txt") + + record := Record{ + Hostname: "_acme-challenge.sub.example.com.", + Type: "txt", + TTL: 300, + Value: "test", + } + + err := client.AddRecord(context.Background(), record) + require.Error(t, err) + + require.EqualError(t, err, "unexpected response: notfqdn: Host _acme-challenge.sub.example.com. malformed / vhn") +} + +func TestClient_RemoveRecord(t *testing.T) { + client := setupTest(t, "remove_success.txt") + + record := Record{ + Hostname: "_acme-challenge.sub.example.com.", + Type: "txt", + TTL: 300, + Value: "test", + } + + err := client.RemoveRecord(context.Background(), record) + require.NoError(t, err) +} + +func TestClient_RemoveRecord_error(t *testing.T) { + client := setupTest(t, "error.txt") + + record := Record{ + Hostname: "_acme-challenge.sub.example.com.", + Type: "txt", + TTL: 300, + Value: "test", + } + + err := client.RemoveRecord(context.Background(), record) + require.Error(t, err) + + require.EqualError(t, err, "unexpected response: notfqdn: Host _acme-challenge.sub.example.com. malformed / vhn") +} diff --git a/providers/dns/bookmyname/internal/fixtures/add_success.txt b/providers/dns/bookmyname/internal/fixtures/add_success.txt new file mode 100644 index 000000000..76304fc24 --- /dev/null +++ b/providers/dns/bookmyname/internal/fixtures/add_success.txt @@ -0,0 +1 @@ +good: update done, cid 123, domain id 456, type txt, ip xxx diff --git a/providers/dns/bookmyname/internal/fixtures/error.txt b/providers/dns/bookmyname/internal/fixtures/error.txt new file mode 100644 index 000000000..3c62ede60 --- /dev/null +++ b/providers/dns/bookmyname/internal/fixtures/error.txt @@ -0,0 +1 @@ +notfqdn: Host _acme-challenge.sub.example.com. malformed / vhn diff --git a/providers/dns/bookmyname/internal/fixtures/remove_success.txt b/providers/dns/bookmyname/internal/fixtures/remove_success.txt new file mode 100644 index 000000000..1e83c6dcc --- /dev/null +++ b/providers/dns/bookmyname/internal/fixtures/remove_success.txt @@ -0,0 +1 @@ +good: remove done 1, cid 123, domain id 456, ttl 300, type txt, ip xxx diff --git a/providers/dns/bookmyname/internal/types.go b/providers/dns/bookmyname/internal/types.go new file mode 100644 index 000000000..96dab064a --- /dev/null +++ b/providers/dns/bookmyname/internal/types.go @@ -0,0 +1,8 @@ +package internal + +type Record struct { + Hostname string `url:"hostname"` + Type string `url:"type"` + TTL int `url:"ttl"` + Value string `url:"value"` +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 0b041183b..7988aca11 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -17,6 +17,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/azuredns" "github.com/go-acme/lego/v4/providers/dns/bindman" "github.com/go-acme/lego/v4/providers/dns/bluecat" + "github.com/go-acme/lego/v4/providers/dns/bookmyname" "github.com/go-acme/lego/v4/providers/dns/brandit" "github.com/go-acme/lego/v4/providers/dns/bunny" "github.com/go-acme/lego/v4/providers/dns/checkdomain" @@ -180,6 +181,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return bindman.NewDNSProvider() case "bluecat": return bluecat.NewDNSProvider() + case "bookmyname": + return bookmyname.NewDNSProvider() case "brandit": return brandit.NewDNSProvider() case "bunny": From 9d7e2a8c44237ec57939448ca966ca8f1b952f36 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 24 Feb 2025 15:24:28 +0100 Subject: [PATCH 058/298] chore: update issue templates --- .github/ISSUE_TEMPLATE/bug_report.yml | 4 +++- .github/ISSUE_TEMPLATE/feature_request.yml | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index d08b48777..f8591e2a6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -45,6 +45,7 @@ body: - Through Bitnami - Through 1Panel - Through Zoraxy + - go install - Other validations: required: true @@ -65,8 +66,9 @@ body: - type: textarea id: version attributes: - label: Version of lego + label: Effective version of lego description: |- + `latest` or `dev` are not effective versions. ```console $ lego --version ``` diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 36afb00ef..78b52dbf0 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -24,10 +24,19 @@ body: - Through Bitnami - Through 1Panel - Through Zoraxy + - go install - Other validations: required: true + - type: input + id: version + attributes: + label: Effective version of lego + description: `latest` or `dev` are not effective versions. + validations: + required: true + - type: textarea id: description attributes: From 55b012ba067b565580cf12a7d995ebfc0cbb7a52 Mon Sep 17 00:00:00 2001 From: Martijn van Hoof Date: Mon, 24 Feb 2025 21:15:16 +0100 Subject: [PATCH 059/298] Add DNS provider for Metaregistrar (#2455) Co-authored-by: Fernandez Ludovic --- README.md | 35 +++-- cmd/zz_gen_cmd_dnshelp.go | 21 +++ docs/content/dns/zz_gen_metaregistrar.md | 67 ++++++++ docs/data/zz_cli_help.toml | 2 +- .../dns/metaregistrar/internal/client.go | 128 +++++++++++++++ .../dns/metaregistrar/internal/client_test.go | 114 ++++++++++++++ .../internal/fixtures/error-response.json | 6 + .../internal/fixtures/error.json | 4 + .../internal/fixtures/update-dns-zone.json | 5 + providers/dns/metaregistrar/internal/types.go | 67 ++++++++ providers/dns/metaregistrar/metaregistrar.go | 147 ++++++++++++++++++ .../dns/metaregistrar/metaregistrar.toml | 22 +++ .../dns/metaregistrar/metaregistrar_test.go | 113 ++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 14 files changed, 718 insertions(+), 16 deletions(-) create mode 100644 docs/content/dns/zz_gen_metaregistrar.md create mode 100644 providers/dns/metaregistrar/internal/client.go create mode 100644 providers/dns/metaregistrar/internal/client_test.go create mode 100644 providers/dns/metaregistrar/internal/fixtures/error-response.json create mode 100644 providers/dns/metaregistrar/internal/fixtures/error.json create mode 100644 providers/dns/metaregistrar/internal/fixtures/update-dns-zone.json create mode 100644 providers/dns/metaregistrar/internal/types.go create mode 100644 providers/dns/metaregistrar/metaregistrar.go create mode 100644 providers/dns/metaregistrar/metaregistrar.toml create mode 100644 providers/dns/metaregistrar/metaregistrar_test.go diff --git a/README.md b/README.md index 05c5a9b72..844a0fa66 100644 --- a/README.md +++ b/README.md @@ -158,85 +158,90 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Manual Metaname + Metaregistrar mijn.host Mittwald myaddr.{tools,dev,io} - MyDNS.jp + MyDNS.jp MythicBeasts Name.com Namecheap - Namesilo + Namesilo NearlyFreeSpeech.NET Netcup Netlify - Nicmanager + Nicmanager NIFCloud Njalla Nodion - NS1 + NS1 Open Telekom Cloud Oracle Cloud OVH - plesk.com + plesk.com Porkbun PowerDNS Rackspace - Rain Yun/雨云 + Rain Yun/雨云 RcodeZero reg.ru Regfish - RFC2136 + RFC2136 RimuHosting Sakura Cloud Scaleway - Selectel + Selectel Selectel v2 SelfHost.(de|eu) Servercow - Shellrent + Shellrent Simply.com Sonic Spaceship - Stackpath + Stackpath Technitium Tencent Cloud DNS Timeweb Cloud - TransIP + TransIP UKFast SafeDNS Ultradns Variomedia - VegaDNS + VegaDNS Vercel Versio.[nl|eu|uk] VinylDNS - VK Cloud + VK Cloud Volcano Engine/火山引擎 Vscale Vultr - Webnames + Webnames Websupport WEDOS West.cn/西部数码 - 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 4f02a7879..cb4c53646 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -95,6 +95,7 @@ func allDNSCodes() string { "mailinabox", "manageengine", "metaname", + "metaregistrar", "mijnhost", "mittwald", "myaddr", @@ -1924,6 +1925,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/metaname`) + case "metaregistrar": + // generated from: providers/dns/metaregistrar/metaregistrar.toml + ew.writeln(`Configuration for Metaregistrar.`) + ew.writeln(`Code: 'metaregistrar'`) + ew.writeln(`Since: 'v4.23.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "METAREGISTRAR_API_TOKEN": The API token`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "METAREGISTRAR_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "METAREGISTRAR_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "METAREGISTRAR_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "METAREGISTRAR_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/metaregistrar`) + case "mijnhost": // generated from: providers/dns/mijnhost/mijnhost.toml ew.writeln(`Configuration for mijn.host.`) diff --git a/docs/content/dns/zz_gen_metaregistrar.md b/docs/content/dns/zz_gen_metaregistrar.md new file mode 100644 index 000000000..63cc2bebc --- /dev/null +++ b/docs/content/dns/zz_gen_metaregistrar.md @@ -0,0 +1,67 @@ +--- +title: "Metaregistrar" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: metaregistrar +dnsprovider: + since: "v4.23.0" + code: "metaregistrar" + url: "https://metaregistrar.com/" +--- + + + + + + +Configuration for [Metaregistrar](https://metaregistrar.com/). + + + + +- Code: `metaregistrar` +- Since: v4.23.0 + + +Here is an example bash command using the Metaregistrar provider: + +```bash +METAREGISTRAR_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns metaregistrar -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `METAREGISTRAR_API_TOKEN` | The API token | + +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 | +|--------------------------------|-------------| +| `METAREGISTRAR_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `METAREGISTRAR_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `METAREGISTRAR_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `METAREGISTRAR_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://metaregistrar.dev/docu/metaapi/) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 2f02d4da7..aff8766a2 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -149,7 +149,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, bookmyname, 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, manageengine, manual, metaname, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi + acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, bookmyname, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/metaregistrar/internal/client.go b/providers/dns/metaregistrar/internal/client.go new file mode 100644 index 000000000..f2838c532 --- /dev/null +++ b/providers/dns/metaregistrar/internal/client.go @@ -0,0 +1,128 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const defaultBaseURL = "https://api.metaregistrar.com" + +// Client is a client to interact with the Metaregistrar API. +type Client struct { + token string + + baseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(token string) (*Client, error) { + if token == "" { + return nil, errors.New("token missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + token: token, + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +// UpdateDNSZone updates the DNS zone for a domain. +// To add or remove a TXT record we make a PATCH request. +// https://metaregistrar.dev/docu/metaapi/requests/patch_Update_dns_zone.html +func (c Client) UpdateDNSZone(ctx context.Context, domain string, updateRequest DNSZoneUpdateRequest) (*DNSZoneUpdateResponse, error) { + endpoint := c.baseURL.JoinPath("dnszone", domain) + + req, err := newJSONRequest(ctx, http.MethodPatch, endpoint, updateRequest) + if err != nil { + return nil, err + } + + result := &DNSZoneUpdateResponse{} + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result, nil +} + +func (c Client) do(req *http.Request, result any) error { + req.Header.Add("token", c.token) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/metaregistrar/internal/client_test.go b/providers/dns/metaregistrar/internal/client_test.go new file mode 100644 index 000000000..1015ba8c8 --- /dev/null +++ b/providers/dns/metaregistrar/internal/client_test.go @@ -0,0 +1,114 @@ +package internal + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupTest(t *testing.T, pattern string, status int, filename string) *Client { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { + if filename == "" { + rw.WriteHeader(status) + return + } + + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + defer func() { _ = file.Close() }() + + rw.WriteHeader(status) + _, err = io.Copy(rw, file) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + }) + + client, err := NewClient("token") + require.NoError(t, err) + + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) + + return client +} + +func TestClient_UpdateDNSZone(t *testing.T) { + client := setupTest(t, "PATCH /dnszone/example.com", http.StatusOK, "update-dns-zone.json") + + updateRequest := DNSZoneUpdateRequest{ + Add: []Record{{ + Name: "@", + Type: "TXT", + TTL: 60, + Content: "value", + }}, + } + + response, err := client.UpdateDNSZone(context.Background(), "example.com", updateRequest) + require.NoError(t, err) + + expected := &DNSZoneUpdateResponse{ + ResponseID: "mapi1_cb46ad8790b62b76535bd3102bd282aec83b894c", + Status: "ok", + Message: "Command completed successfully", + } + + assert.Equal(t, expected, response) +} + +func TestClient_UpdateDNSZone_error(t *testing.T) { + testCases := []struct { + desc string + filename string + expected string + }{ + { + desc: "authentication error", + filename: "error.json", + expected: "invalid_token: the supplied token is invalid", + }, + { + desc: "API error", + filename: "error-response.json", + expected: "error: does_not_exist: This server does not exist", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + client := setupTest(t, "PATCH /dnszone/example.com", http.StatusUnprocessableEntity, test.filename) + + updateRequest := DNSZoneUpdateRequest{ + Add: []Record{{ + Name: "@", + Type: "TXT", + TTL: 60, + Content: "value", + }}, + } + + _, err := client.UpdateDNSZone(context.Background(), "example.com", updateRequest) + require.EqualError(t, err, test.expected) + }) + } +} diff --git a/providers/dns/metaregistrar/internal/fixtures/error-response.json b/providers/dns/metaregistrar/internal/fixtures/error-response.json new file mode 100644 index 000000000..8fa5a5ff3 --- /dev/null +++ b/providers/dns/metaregistrar/internal/fixtures/error-response.json @@ -0,0 +1,6 @@ +{ + "responseId": "1_0a407cb0634a56374ba80f863fda53ae37fd0042", + "status": "error", + "errorCode": "does_not_exist", + "errorMessage": "This server does not exist" +} diff --git a/providers/dns/metaregistrar/internal/fixtures/error.json b/providers/dns/metaregistrar/internal/fixtures/error.json new file mode 100644 index 000000000..c76a32fc8 --- /dev/null +++ b/providers/dns/metaregistrar/internal/fixtures/error.json @@ -0,0 +1,4 @@ +{ + "error": "invalid_token", + "message": "the supplied token is invalid" +} \ No newline at end of file diff --git a/providers/dns/metaregistrar/internal/fixtures/update-dns-zone.json b/providers/dns/metaregistrar/internal/fixtures/update-dns-zone.json new file mode 100644 index 000000000..b4977272a --- /dev/null +++ b/providers/dns/metaregistrar/internal/fixtures/update-dns-zone.json @@ -0,0 +1,5 @@ +{ + "responseId": "mapi1_cb46ad8790b62b76535bd3102bd282aec83b894c", + "status": "ok", + "message": "Command completed successfully" +} diff --git a/providers/dns/metaregistrar/internal/types.go b/providers/dns/metaregistrar/internal/types.go new file mode 100644 index 000000000..d8b6b3f87 --- /dev/null +++ b/providers/dns/metaregistrar/internal/types.go @@ -0,0 +1,67 @@ +package internal + +import ( + "strings" +) + +// APIError It's a mix of documented and undocumented fields. +// Note: the documentation is inconsistent: the names of property are not the same as the JSON sample. +// https://metaregistrar.dev/docu/metaapi/requests/response_ErrorResponse.html +type APIError struct { + ResponseID string `json:"responseId,omitempty"` + Status string `json:"status,omitempty"` + Message string `json:"message,omitempty"` + Err string `json:"error,omitempty"` + ErrorCode string `json:"errorCode,omitempty"` + ErrorMessage string `json:"errorMessage,omitempty"` +} + +func (e *APIError) Error() string { + var msg []string + + if e.Status != "" { + msg = append(msg, e.Status) + } + + if e.Err != "" { + msg = append(msg, e.Err) + } + + if e.ErrorCode != "" { + msg = append(msg, e.ErrorCode) + } + + if e.Message != "" { + msg = append(msg, e.Message) + } + + if e.ErrorMessage != "" { + msg = append(msg, e.ErrorMessage) + } + + return strings.Join(msg, ": ") +} + +type Record struct { + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + TTL int `json:"ttl,omitempty"` + Content string `json:"content,omitempty"` + Priority int `json:"priority,omitempty"` + Disabled bool `json:"disabled,omitempty"` +} + +// DNSZoneUpdateRequest is the representation of DnszoneUpdateRequest object. +// https://metaregistrar.dev/docu/metaapi/requests/request_DnszoneUpdateRequest.html +type DNSZoneUpdateRequest struct { + Add []Record `json:"add,omitempty"` + Remove []Record `json:"rem,omitempty"` +} + +// DNSZoneUpdateResponse is the representation of DnszoneUpdateResponse object. +// https://metaregistrar.dev/docu/metaapi/requests/response_DnszoneUpdateResponse.html +type DNSZoneUpdateResponse struct { + ResponseID string `json:"responseId,omitempty"` + Status string `json:"status,omitempty"` + Message string `json:"message,omitempty"` +} diff --git a/providers/dns/metaregistrar/metaregistrar.go b/providers/dns/metaregistrar/metaregistrar.go new file mode 100644 index 000000000..28526fcb4 --- /dev/null +++ b/providers/dns/metaregistrar/metaregistrar.go @@ -0,0 +1,147 @@ +// Package metaregistrar implements a DNS provider for solving the DNS-01 challenge using Metaregistrar. +package metaregistrar + +import ( + "context" + "errors" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/metaregistrar/internal" +) + +// Environment variables names. +const ( + envNamespace = "METAREGISTRAR_" + + EnvToken = envNamespace + "API_TOKEN" + + 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 { + APIToken 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for Metaregistrar. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvToken) + if err != nil { + return nil, fmt.Errorf("metaregistrar: %w", err) + } + + config := NewDefaultConfig() + config.APIToken = values[EnvToken] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Metaregistrar. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("metaregistrar: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.APIToken) + if err != nil { + return nil, fmt.Errorf("metaregistrar: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("metaregistrar: could not find zone for domain %q: %w", domain, err) + } + + updateRequest := internal.DNSZoneUpdateRequest{ + Add: []internal.Record{{ + Name: dns01.UnFqdn(info.EffectiveFQDN), + Type: "TXT", + TTL: d.config.TTL, + Content: info.Value, + }}, + } + + _, err = d.client.UpdateDNSZone(context.Background(), dns01.UnFqdn(authZone), updateRequest) + if err != nil { + return fmt.Errorf("metaregistrar: %w", err) + } + + 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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("metaregistrar: could not find zone for domain %q: %w", domain, err) + } + + updateRequest := internal.DNSZoneUpdateRequest{ + Remove: []internal.Record{{ + Name: dns01.UnFqdn(info.EffectiveFQDN), + Type: "TXT", + TTL: d.config.TTL, + Content: strconv.Quote(info.Value), + }}, + } + + _, err = d.client.UpdateDNSZone(context.Background(), dns01.UnFqdn(authZone), updateRequest) + if err != nil { + return fmt.Errorf("metaregistrar: %w", err) + } + + 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/metaregistrar/metaregistrar.toml b/providers/dns/metaregistrar/metaregistrar.toml new file mode 100644 index 000000000..952c7ea61 --- /dev/null +++ b/providers/dns/metaregistrar/metaregistrar.toml @@ -0,0 +1,22 @@ +Name = "Metaregistrar" +Description = '''''' +URL = "https://metaregistrar.com/" +Code = "metaregistrar" +Since = "v4.23.0" + +Example = ''' +METAREGISTRAR_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns metaregistrar -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + METAREGISTRAR_API_TOKEN = "The API token" + [Configuration.Additional] + METAREGISTRAR_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + METAREGISTRAR_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + METAREGISTRAR_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + METAREGISTRAR_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://metaregistrar.dev/docu/metaapi/" diff --git a/providers/dns/metaregistrar/metaregistrar_test.go b/providers/dns/metaregistrar/metaregistrar_test.go new file mode 100644 index 000000000..ffd7965d9 --- /dev/null +++ b/providers/dns/metaregistrar/metaregistrar_test.go @@ -0,0 +1,113 @@ +package metaregistrar + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvToken).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvToken: "token", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "metaregistrar: some credentials information are missing: METAREGISTRAR_API_TOKEN", + }, + } + + 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 + apiToken string + expected string + }{ + { + desc: "success", + apiToken: "token", + }, + { + desc: "missing credentials", + expected: "metaregistrar: token missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIToken = test.apiToken + + 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 7988aca11..f3afca153 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -89,6 +89,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/mailinabox" "github.com/go-acme/lego/v4/providers/dns/manageengine" "github.com/go-acme/lego/v4/providers/dns/metaname" + "github.com/go-acme/lego/v4/providers/dns/metaregistrar" "github.com/go-acme/lego/v4/providers/dns/mijnhost" "github.com/go-acme/lego/v4/providers/dns/mittwald" "github.com/go-acme/lego/v4/providers/dns/myaddr" @@ -325,6 +326,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return manageengine.NewDNSProvider() case "metaname": return metaname.NewDNSProvider() + case "metaregistrar": + return metaregistrar.NewDNSProvider() case "mijnhost": return mijnhost.NewDNSProvider() case "mittwald": From b31c6ce79bcd7843aedc5860c0c21040af36b73b Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 26 Feb 2025 20:48:08 +0100 Subject: [PATCH 060/298] docs: this is not the API URL for Porkbun --- providers/dns/porkbun/porkbun.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/providers/dns/porkbun/porkbun.toml b/providers/dns/porkbun/porkbun.toml index 610702cee..d7ed3aedc 100644 --- a/providers/dns/porkbun/porkbun.toml +++ b/providers/dns/porkbun/porkbun.toml @@ -1,5 +1,6 @@ Name = "Porkbun" Description = '''''' +# This URL is NOT the API URL. URL = "https://porkbun.com/" Code = "porkbun" Since = "v4.4.0" From da260e45b0196cc8eb1f060acfda7f342b15432f Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 26 Feb 2025 23:41:29 +0100 Subject: [PATCH 061/298] feat: add INFOBLOX_CA_CERTIFICATE option (#2458) --- cmd/zz_gen_cmd_dnshelp.go | 1 + docs/content/dns/zz_gen_infoblox.md | 1 + providers/dns/infoblox/infoblox.go | 36 ++++++++++++++++++---------- providers/dns/infoblox/infoblox.toml | 1 + 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index cb4c53646..1352441a0 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -1557,6 +1557,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) + ew.writeln(` - "INFOBLOX_CA_CERTIFICATE": The path to the CA certificate (PEM encoded)`) ew.writeln(` - "INFOBLOX_DNS_VIEW": The view for the TXT records (Default: External)`) ew.writeln(` - "INFOBLOX_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "INFOBLOX_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) diff --git a/docs/content/dns/zz_gen_infoblox.md b/docs/content/dns/zz_gen_infoblox.md index f710e2e18..2d07628f3 100644 --- a/docs/content/dns/zz_gen_infoblox.md +++ b/docs/content/dns/zz_gen_infoblox.md @@ -51,6 +51,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| +| `INFOBLOX_CA_CERTIFICATE` | The path to the CA certificate (PEM encoded) | | `INFOBLOX_DNS_VIEW` | The view for the TXT records (Default: External) | | `INFOBLOX_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `INFOBLOX_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | diff --git a/providers/dns/infoblox/infoblox.go b/providers/dns/infoblox/infoblox.go index 6aefd0bc1..4ff0552e9 100644 --- a/providers/dns/infoblox/infoblox.go +++ b/providers/dns/infoblox/infoblox.go @@ -19,13 +19,14 @@ import ( const ( envNamespace = "INFOBLOX_" - EnvHost = envNamespace + "HOST" - EnvPort = envNamespace + "PORT" - EnvUsername = envNamespace + "USERNAME" - EnvPassword = envNamespace + "PASSWORD" - EnvDNSView = envNamespace + "DNS_VIEW" - EnvWApiVersion = envNamespace + "WAPI_VERSION" - EnvSSLVerify = envNamespace + "SSL_VERIFY" + EnvHost = envNamespace + "HOST" + EnvPort = envNamespace + "PORT" + EnvUsername = envNamespace + "USERNAME" + EnvPassword = envNamespace + "PASSWORD" + EnvDNSView = envNamespace + "DNS_VIEW" + EnvWApiVersion = envNamespace + "WAPI_VERSION" + EnvSSLVerify = envNamespace + "SSL_VERIFY" + EnvCACertificate = envNamespace + "CA_CERTIFICATE" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -57,6 +58,9 @@ type Config struct { // SSLVerify is whether or not to verify the ssl of the server being hit. SSLVerify bool + // CACertificate is the path to the CA certificate (PEM encoded). + CACertificate string + PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -66,10 +70,11 @@ type Config struct { // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - DNSView: env.GetOrDefaultString(EnvDNSView, "External"), - WapiVersion: env.GetOrDefaultString(EnvWApiVersion, "2.11"), - Port: env.GetOrDefaultString(EnvPort, "443"), - SSLVerify: env.GetOrDefaultBool(EnvSSLVerify, true), + DNSView: env.GetOrDefaultString(EnvDNSView, "External"), + WapiVersion: env.GetOrDefaultString(EnvWApiVersion, "2.11"), + Port: env.GetOrDefaultString(EnvPort, "443"), + SSLVerify: env.GetOrDefaultBool(EnvSSLVerify, true), + CACertificate: env.GetOrDefaultString(EnvCACertificate, ""), TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), @@ -122,9 +127,16 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("infoblox: missing credentials") } + var sslVerify string + if config.CACertificate != "" { + sslVerify = config.CACertificate + } else { + sslVerify = strconv.FormatBool(config.SSLVerify) + } + return &DNSProvider{ config: config, - transportConfig: infoblox.NewTransportConfig(strconv.FormatBool(config.SSLVerify), config.HTTPTimeout, defaultPoolConnections), + transportConfig: infoblox.NewTransportConfig(sslVerify, config.HTTPTimeout, defaultPoolConnections), ibConfig: infoblox.HostConfig{ Host: config.Host, Version: config.WapiVersion, diff --git a/providers/dns/infoblox/infoblox.toml b/providers/dns/infoblox/infoblox.toml index 5cd355c1a..3c2632042 100644 --- a/providers/dns/infoblox/infoblox.toml +++ b/providers/dns/infoblox/infoblox.toml @@ -25,6 +25,7 @@ When creating an API's user ensure it has the proper permissions for the view yo INFOBLOX_WAPI_VERSION = "The version of WAPI being used (Default: 2.11)" INFOBLOX_PORT = "The port for the infoblox grid manager (Default: 443)" INFOBLOX_SSL_VERIFY = "Whether or not to verify the TLS certificate (Default: true)" + INFOBLOX_CA_CERTIFICATE = "The path to the CA certificate (PEM encoded)" INFOBLOX_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" INFOBLOX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" INFOBLOX_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" From fe10c3ab3c905a6cfc1c0d4685adb8f38ac7d7b5 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 27 Feb 2025 12:30:13 +0100 Subject: [PATCH 062/298] infoblox: update API client to v2 (#2459) --- go.mod | 2 +- go.sum | 18 ++++++++++-------- providers/dns/infoblox/infoblox.go | 17 ++++++++++------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 7ff02829a..2ec442f0e 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/hashicorp/go-version v1.7.0 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df - github.com/infobloxopen/infoblox-go-client v1.1.1 + github.com/infobloxopen/infoblox-go-client/v2 v2.9.0 github.com/labbsr0x/bindman-dns-webhook v1.0.2 github.com/linode/linodego v1.44.0 github.com/liquidweb/liquidweb-go v1.6.4 diff --git a/go.sum b/go.sum index c7de23dd5..eea7bf9fe 100644 --- a/go.sum +++ b/go.sum @@ -301,7 +301,8 @@ github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWa github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= @@ -389,8 +390,9 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= @@ -482,8 +484,8 @@ github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU= -github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI= +github.com/infobloxopen/infoblox-go-client/v2 v2.9.0 h1:wS8kTlQVeVbrepeY83s9X+XdSa6Qah5KO+tdW+zRQXU= +github.com/infobloxopen/infoblox-go-client/v2 v2.9.0/go.mod h1:NeNJpz09efw/edzqkVivGv1bWqBXTomqYBRFbP+XBqg= github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= @@ -662,8 +664,8 @@ github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= -github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo= +github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= @@ -671,8 +673,8 @@ github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je4 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= -github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= diff --git a/providers/dns/infoblox/infoblox.go b/providers/dns/infoblox/infoblox.go index 4ff0552e9..37e119e85 100644 --- a/providers/dns/infoblox/infoblox.go +++ b/providers/dns/infoblox/infoblox.go @@ -12,7 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" - infoblox "github.com/infobloxopen/infoblox-go-client" + infoblox "github.com/infobloxopen/infoblox-go-client/v2" ) // Environment variables names. @@ -88,6 +88,7 @@ type DNSProvider struct { config *Config transportConfig infoblox.TransportConfig ibConfig infoblox.HostConfig + ibAuth infoblox.AuthConfig recordRefs map[string]string recordRefsMu sync.Mutex @@ -138,9 +139,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { config: config, transportConfig: infoblox.NewTransportConfig(sslVerify, config.HTTPTimeout, defaultPoolConnections), ibConfig: infoblox.HostConfig{ - Host: config.Host, - Version: config.WapiVersion, - Port: config.Port, + Host: config.Host, + Version: config.WapiVersion, + Port: config.Port, + }, + ibAuth: infoblox.AuthConfig{ Username: config.Username, Password: config.Password, }, @@ -157,7 +160,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - connector, err := infoblox.NewConnector(d.ibConfig, d.transportConfig, &infoblox.WapiRequestBuilder{}, &infoblox.WapiHttpRequestor{}) + connector, err := infoblox.NewConnector(d.ibConfig, d.ibAuth, d.transportConfig, &infoblox.WapiRequestBuilder{}, &infoblox.WapiHttpRequestor{}) if err != nil { return fmt.Errorf("infoblox: %w", err) } @@ -166,7 +169,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { objectManager := infoblox.NewObjectManager(connector, useragent.Get(), "") - record, err := objectManager.CreateTXTRecord(dns01.UnFqdn(info.EffectiveFQDN), info.Value, uint(d.config.TTL), d.config.DNSView) + record, err := objectManager.CreateTXTRecord(d.config.DNSView, dns01.UnFqdn(info.EffectiveFQDN), info.Value, uint32(d.config.TTL), true, "lego", nil) if err != nil { return fmt.Errorf("infoblox: could not create TXT record for %s: %w", domain, err) } @@ -182,7 +185,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - connector, err := infoblox.NewConnector(d.ibConfig, d.transportConfig, &infoblox.WapiRequestBuilder{}, &infoblox.WapiHttpRequestor{}) + connector, err := infoblox.NewConnector(d.ibConfig, d.ibAuth, d.transportConfig, &infoblox.WapiRequestBuilder{}, &infoblox.WapiHttpRequestor{}) if err != nil { return fmt.Errorf("infoblox: %w", err) } From 5b06dd7874c1a187016c6146e5ff2027400de2d3 Mon Sep 17 00:00:00 2001 From: Hideki Okamoto Date: Mon, 3 Mar 2025 10:57:26 +0900 Subject: [PATCH 063/298] edgedns: add account switch key option (#2460) Co-authored-by: Fernandez Ludovic --- cmd/zz_gen_cmd_dnshelp.go | 1 + docs/content/dns/zz_gen_edgedns.md | 2 ++ providers/dns/edgedns/edgedns.go | 7 +++++++ providers/dns/edgedns/edgedns.toml | 2 ++ providers/dns/edgedns/edgedns_test.go | 19 +++++++++++++++++++ 5 files changed, 31 insertions(+) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 1352441a0..3668f661c 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -1064,6 +1064,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) + ew.writeln(` - "AKAMAI_ACCOUNT_SWITCH_KEY": Target account ID when the DNS zone and credentials belong to different accounts`) ew.writeln(` - "AKAMAI_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 15)`) ew.writeln(` - "AKAMAI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 180)`) ew.writeln(` - "AKAMAI_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) diff --git a/docs/content/dns/zz_gen_edgedns.md b/docs/content/dns/zz_gen_edgedns.md index eb94a8a47..21d819d2c 100644 --- a/docs/content/dns/zz_gen_edgedns.md +++ b/docs/content/dns/zz_gen_edgedns.md @@ -55,6 +55,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| +| `AKAMAI_ACCOUNT_SWITCH_KEY` | Target account ID when the DNS zone and credentials belong to different accounts | | `AKAMAI_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 15) | | `AKAMAI_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 180) | | `AKAMAI_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | @@ -88,6 +89,7 @@ See also: - [.edgerc Format](https://developer.akamai.com/legacy/introduction/Conf_Client.html#edgercformat) - [API Client Authentication](https://developer.akamai.com/legacy/introduction/Client_Auth.html) - [Config from Env](https://github.com/akamai/AkamaiOPEN-edgegrid-golang/blob/master/pkg/edgegrid/config.go#L118) +- [Manage many accounts](https://techdocs.akamai.com/developer/docs/manage-many-accounts-with-one-api-client) diff --git a/providers/dns/edgedns/edgedns.go b/providers/dns/edgedns/edgedns.go index d44d2eaf5..10898006a 100644 --- a/providers/dns/edgedns/edgedns.go +++ b/providers/dns/edgedns/edgedns.go @@ -28,6 +28,8 @@ const ( EnvClientSecret = envNamespace + "CLIENT_SECRET" EnvAccessToken = envNamespace + "ACCESS_TOKEN" + EnvAccountSwitchKey = envNamespace + "ACCOUNT_SWITCH_KEY" + EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" @@ -79,6 +81,7 @@ func NewDNSProvider() (*DNSProvider, error) { rcPath := env.GetOrDefaultString(EnvEdgeRc, "") rcSection := env.GetOrDefaultString(EnvEdgeRcSection, "") + accountSwitchKey := env.GetOrDefaultString(EnvAccountSwitchKey, "") conf, err := edgegrid.Init(rcPath, rcSection) if err != nil { @@ -87,6 +90,10 @@ func NewDNSProvider() (*DNSProvider, error) { conf.MaxBody = maxBody + if accountSwitchKey != "" { + conf.AccountKey = accountSwitchKey + } + config.Config = conf return NewDNSProviderConfig(config) diff --git a/providers/dns/edgedns/edgedns.toml b/providers/dns/edgedns/edgedns.toml index e925c4aa6..d40d5cc03 100644 --- a/providers/dns/edgedns/edgedns.toml +++ b/providers/dns/edgedns/edgedns.toml @@ -42,6 +42,7 @@ See also: - [.edgerc Format](https://developer.akamai.com/legacy/introduction/Conf_Client.html#edgercformat) - [API Client Authentication](https://developer.akamai.com/legacy/introduction/Client_Auth.html) - [Config from Env](https://github.com/akamai/AkamaiOPEN-edgegrid-golang/blob/master/pkg/edgegrid/config.go#L118) +- [Manage many accounts](https://techdocs.akamai.com/developer/docs/manage-many-accounts-with-one-api-client) ''' [Configuration] @@ -53,6 +54,7 @@ See also: AKAMAI_EDGERC = "Path to the .edgerc file, managed by the Akamai EdgeGrid client" AKAMAI_EDGERC_SECTION = "Configuration section, managed by the Akamai EdgeGrid client" [Configuration.Additional] + AKAMAI_ACCOUNT_SWITCH_KEY = "Target account ID when the DNS zone and credentials belong to different accounts" AKAMAI_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 15)" AKAMAI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 180)" AKAMAI_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" diff --git a/providers/dns/edgedns/edgedns_test.go b/providers/dns/edgedns/edgedns_test.go index 9bb76580b..3ac55a4a4 100644 --- a/providers/dns/edgedns/edgedns_test.go +++ b/providers/dns/edgedns/edgedns_test.go @@ -25,6 +25,7 @@ var envTest = tester.NewEnvTest( EnvClientToken, EnvClientSecret, EnvAccessToken, + EnvAccountSwitchKey, EnvEdgeRc, EnvEdgeRcSection, envTestHost, @@ -57,6 +58,24 @@ func TestNewDNSProvider_FromEnv(t *testing.T) { MaxBody: maxBody, }, }, + { + desc: "with account switch key", + envVars: map[string]string{ + EnvHost: "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net", + EnvClientToken: "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", + EnvClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + EnvAccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", + EnvAccountSwitchKey: "F-AC-1234", + }, + expectedConfig: &edgegrid.Config{ + Host: "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net", + ClientToken: "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", + ClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + AccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", + MaxBody: maxBody, + AccountKey: "F-AC-1234", + }, + }, { desc: "with section", envVars: map[string]string{ From 4675ef7d9afb1a5d508755d3e6f946d654b23e3c Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 3 Mar 2025 18:01:11 +0100 Subject: [PATCH 064/298] Add DNS provider for F5 XC (#2409) --- README.md | 54 ++--- cmd/zz_gen_cmd_dnshelp.go | 23 ++ docs/content/dns/zz_gen_f5xc.md | 71 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/f5xc/f5xc.go | 180 ++++++++++++++ providers/dns/f5xc/f5xc.toml | 27 +++ providers/dns/f5xc/f5xc_test.go | 166 +++++++++++++ providers/dns/f5xc/internal/client.go | 210 +++++++++++++++++ providers/dns/f5xc/internal/client_test.go | 219 ++++++++++++++++++ .../dns/f5xc/internal/fixtures/create.json | 204 ++++++++++++++++ .../dns/f5xc/internal/fixtures/delete.json | 207 +++++++++++++++++ .../dns/f5xc/internal/fixtures/error_404.json | 5 + .../dns/f5xc/internal/fixtures/error_503.json | 5 + providers/dns/f5xc/internal/fixtures/get.json | 207 +++++++++++++++++ .../dns/f5xc/internal/fixtures/replace.json | 206 ++++++++++++++++ providers/dns/f5xc/internal/types.go | 48 ++++ providers/dns/zz_gen_dns_providers.go | 3 + 17 files changed, 1809 insertions(+), 28 deletions(-) create mode 100644 docs/content/dns/zz_gen_f5xc.md create mode 100644 providers/dns/f5xc/f5xc.go create mode 100644 providers/dns/f5xc/f5xc.toml create mode 100644 providers/dns/f5xc/f5xc_test.go create mode 100644 providers/dns/f5xc/internal/client.go create mode 100644 providers/dns/f5xc/internal/client_test.go create mode 100644 providers/dns/f5xc/internal/fixtures/create.json create mode 100644 providers/dns/f5xc/internal/fixtures/delete.json create mode 100644 providers/dns/f5xc/internal/fixtures/error_404.json create mode 100644 providers/dns/f5xc/internal/fixtures/error_503.json create mode 100644 providers/dns/f5xc/internal/fixtures/get.json create mode 100644 providers/dns/f5xc/internal/fixtures/replace.json create mode 100644 providers/dns/f5xc/internal/types.go diff --git a/README.md b/README.md index 844a0fa66..80eed622f 100644 --- a/README.md +++ b/README.md @@ -110,138 +110,138 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Exoscale External program + F5 XC freemyip.com - G-Core + G-Core Gandi Gandi Live DNS (v5) Glesys - Go Daddy + Go Daddy Google Cloud Google Domains Hetzner - Hosting.de + Hosting.de Hosttech HTTP request http.net - Huawei Cloud + Huawei Cloud Hurricane Electric DNS HyperOne IBM Cloud (SoftLayer) - IIJ DNS Platform Service + IIJ DNS Platform Service Infoblox Infomaniak Internet Initiative Japan - Internet.bs + Internet.bs INWX Ionos IPv64 - iwantmyname + iwantmyname Joker Joohoi's ACME-DNS Liara - Lima-City + Lima-City Linode (v4) Liquid Web Loopia - LuaDNS + LuaDNS Mail-in-a-Box ManageEngine CloudDNS Manual - Metaname + Metaname Metaregistrar mijn.host Mittwald - myaddr.{tools,dev,io} + myaddr.{tools,dev,io} MyDNS.jp MythicBeasts Name.com - Namecheap + Namecheap Namesilo NearlyFreeSpeech.NET Netcup - Netlify + Netlify Nicmanager NIFCloud Njalla - Nodion + Nodion NS1 Open Telekom Cloud Oracle Cloud - OVH + OVH plesk.com Porkbun PowerDNS - Rackspace + Rackspace Rain Yun/雨云 RcodeZero reg.ru - Regfish + Regfish RFC2136 RimuHosting Sakura Cloud - Scaleway + Scaleway Selectel Selectel v2 SelfHost.(de|eu) - Servercow + Servercow Shellrent Simply.com Sonic - Spaceship + Spaceship Stackpath Technitium 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 - West.cn/西部数码 + West.cn/西部数码 Yandex 360 Yandex Cloud Yandex PDD - Zone.ee + Zone.ee Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 3668f661c..80f9ab243 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -58,6 +58,7 @@ func allDNSCodes() string { "epik", "exec", "exoscale", + "f5xc", "freemyip", "gandi", "gandiv5", @@ -1148,6 +1149,28 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/exoscale`) + case "f5xc": + // generated from: providers/dns/f5xc/f5xc.toml + ew.writeln(`Configuration for F5 XC.`) + ew.writeln(`Code: 'f5xc'`) + ew.writeln(`Since: 'v4.23.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "F5XC_API_TOKEN": API token`) + ew.writeln(` - "F5XC_GROUP_NAME": Group name`) + ew.writeln(` - "F5XC_TENANT_NAME": XC Tenant shortname`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "F5XC_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "F5XC_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "F5XC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "F5XC_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/f5xc`) + case "freemyip": // generated from: providers/dns/freemyip/freemyip.toml ew.writeln(`Configuration for freemyip.com.`) diff --git a/docs/content/dns/zz_gen_f5xc.md b/docs/content/dns/zz_gen_f5xc.md new file mode 100644 index 000000000..c8a664a00 --- /dev/null +++ b/docs/content/dns/zz_gen_f5xc.md @@ -0,0 +1,71 @@ +--- +title: "F5 XC" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: f5xc +dnsprovider: + since: "v4.23.0" + code: "f5xc" + url: "https://www.f5.com/products/distributed-cloud-services" +--- + + + + + + +Configuration for [F5 XC](https://www.f5.com/products/distributed-cloud-services). + + + + +- Code: `f5xc` +- Since: v4.23.0 + + +Here is an example bash command using the F5 XC provider: + +```bash +F5XC_API_TOKEN="xxx" \ +F5XC_TENANT_NAME="yyy" \ +F5XC_GROUP_NAME="zzz" \ +lego --email you@example.com --dns f5xc -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `F5XC_API_TOKEN` | API token | +| `F5XC_GROUP_NAME` | Group name | +| `F5XC_TENANT_NAME` | XC Tenant shortname | + +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 | +|--------------------------------|-------------| +| `F5XC_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `F5XC_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `F5XC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `F5XC_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://docs.cloud.f5.com/docs-v2/api/dns-zone-rrset) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index aff8766a2..1cbfdbf4e 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -149,7 +149,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, bookmyname, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi + acme-dns, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, bookmyname, 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, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/f5xc/f5xc.go b/providers/dns/f5xc/f5xc.go new file mode 100644 index 000000000..2ed1f0c4f --- /dev/null +++ b/providers/dns/f5xc/f5xc.go @@ -0,0 +1,180 @@ +// Package f5xc implements a DNS provider for solving the DNS-01 challenge using F5 XC. +package f5xc + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/platform/wait" + "github.com/go-acme/lego/v4/providers/dns/f5xc/internal" +) + +// Environment variables names. +const ( + envNamespace = "F5XC_" + + EnvToken = envNamespace + "API_TOKEN" + EnvTenantName = envNamespace + "TENANT_NAME" + EnvGroupName = envNamespace + "GROUP_NAME" + + 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 { + APIToken string + TenantName string + GroupName 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for F5 XC. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvToken, EnvTenantName, EnvGroupName) + if err != nil { + return nil, fmt.Errorf("f5xc: %w", err) + } + + config := NewDefaultConfig() + config.APIToken = values[EnvToken] + config.TenantName = values[EnvTenantName] + config.GroupName = values[EnvGroupName] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for F5 XC. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("f5xc: the configuration of the DNS provider is nil") + } + + if config.GroupName == "" { + return nil, errors.New("f5xc: missing group name") + } + + client, err := internal.NewClient(config.APIToken, config.TenantName) + if err != nil { + return nil, fmt.Errorf("f5xc: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("f5xc: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("f5xc: %w", err) + } + + existingRRSet, err := d.client.GetRRSet(context.Background(), dns01.UnFqdn(authZone), d.config.GroupName, subDomain, "TXT") + if err != nil { + return fmt.Errorf("f5xc: get RR Set: %w", err) + } + + // New RRSet. + if existingRRSet == nil || existingRRSet.RRSet.TXTRecord == nil { + rrSet := internal.RRSet{ + Description: "lego", + TTL: d.config.TTL, + TXTRecord: &internal.TXTRecord{ + Name: subDomain, + Values: []string{info.Value}, + }, + } + + return wait.For("f5xc create", 60*time.Second, 2*time.Second, func() (bool, error) { + _, err = d.client.CreateRRSet(context.Background(), dns01.UnFqdn(authZone), d.config.GroupName, rrSet) + if err != nil { + return false, fmt.Errorf("f5xc: create RR set: %w", err) + } + + return true, nil + }) + } + + // Update RRSet. + existingRRSet.RRSet.TXTRecord.Values = append(existingRRSet.RRSet.TXTRecord.Values, info.Value) + + return wait.For("f5xc replace", 60*time.Second, 2*time.Second, func() (bool, error) { + _, err = d.client.ReplaceRRSet(context.Background(), dns01.UnFqdn(authZone), d.config.GroupName, subDomain, "TXT", existingRRSet.RRSet) + if err != nil { + return false, fmt.Errorf("f5xc: replace RR set: %w", err) + } + + return true, nil + }) +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("f5xc: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("f5xc: %w", err) + } + + _, err = d.client.DeleteRRSet(context.Background(), dns01.UnFqdn(authZone), d.config.GroupName, subDomain, "TXT") + if err != nil { + return fmt.Errorf("f5xc: delete RR set: %w", err) + } + + 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/f5xc/f5xc.toml b/providers/dns/f5xc/f5xc.toml new file mode 100644 index 000000000..7a4cab419 --- /dev/null +++ b/providers/dns/f5xc/f5xc.toml @@ -0,0 +1,27 @@ +Name = "F5 XC" +Description = '''''' +URL = "https://www.f5.com/products/distributed-cloud-services" +Code = "f5xc" +Since = "v4.23.0" + +Example = ''' +F5XC_API_TOKEN="xxx" \ +F5XC_TENANT_NAME="yyy" \ +F5XC_GROUP_NAME="zzz" \ +lego --email you@example.com --dns f5xc -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + F5XC_API_TOKEN = "API token" + F5XC_TENANT_NAME = "XC Tenant shortname" + F5XC_GROUP_NAME = "Group name" + [Configuration.Additional] + F5XC_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + F5XC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + F5XC_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + F5XC_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://docs.cloud.f5.com/docs-v2/api/dns-zone-rrset" + Documentation = "https://my.f5.com/manage/s/article/K000147937" diff --git a/providers/dns/f5xc/f5xc_test.go b/providers/dns/f5xc/f5xc_test.go new file mode 100644 index 000000000..a298b9e51 --- /dev/null +++ b/providers/dns/f5xc/f5xc_test.go @@ -0,0 +1,166 @@ +package f5xc + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvToken, EnvTenantName, EnvGroupName).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvToken: "secret", + EnvTenantName: "shortname", + EnvGroupName: "group", + }, + }, + { + desc: "missing API token", + envVars: map[string]string{ + EnvToken: "", + EnvTenantName: "shortname", + EnvGroupName: "group", + }, + expected: "f5xc: some credentials information are missing: F5XC_API_TOKEN", + }, + { + desc: "missing tenant name", + envVars: map[string]string{ + EnvToken: "secret", + EnvTenantName: "", + EnvGroupName: "group", + }, + expected: "f5xc: some credentials information are missing: F5XC_TENANT_NAME", + }, + { + desc: "missing group name", + envVars: map[string]string{ + EnvToken: "secret", + EnvTenantName: "shortname", + EnvGroupName: "", + }, + expected: "f5xc: some credentials information are missing: F5XC_GROUP_NAME", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "f5xc: some credentials information are missing: F5XC_API_TOKEN,F5XC_TENANT_NAME,F5XC_GROUP_NAME", + }, + } + + 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 + apiToken string + tenantName string + groupName string + expected string + }{ + { + desc: "success", + apiToken: "secret", + tenantName: "shortname", + groupName: "group", + }, + { + desc: "missing API token", + tenantName: "shortname", + groupName: "group", + expected: "f5xc: credentials missing", + }, + { + desc: "missing tenant name", + apiToken: "secret", + groupName: "group", + expected: "f5xc: missing tenant name", + }, + { + desc: "missing group name", + apiToken: "secret", + tenantName: "shortname", + expected: "f5xc: missing group name", + }, + { + desc: "missing credentials", + expected: "f5xc: missing group name", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIToken = test.apiToken + config.TenantName = test.tenantName + config.GroupName = test.groupName + + 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/f5xc/internal/client.go b/providers/dns/f5xc/internal/client.go new file mode 100644 index 000000000..620336c88 --- /dev/null +++ b/providers/dns/f5xc/internal/client.go @@ -0,0 +1,210 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const defaultHost = "console.ves.volterra.io" + +const authorizationHeader = "Authorization" + +// Client the F5 XC API client. +type Client struct { + apiToken string + + baseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(apiToken string, tenantName string) (*Client, error) { + if apiToken == "" { + return nil, errors.New("credentials missing") + } + + if tenantName == "" { + return nil, errors.New("missing tenant name") + } + + baseURL, err := url.Parse(fmt.Sprintf("https://%s.%s", tenantName, defaultHost)) + if err != nil { + return nil, fmt.Errorf("parse base URL: %w", err) + } + + return &Client{ + apiToken: apiToken, + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +// CreateRRSet creates RRSet. +// https://docs.cloud.f5.com/docs-v2/api/dns-zone-rrset#operation/ves.io.schema.dns_zone.rrset.CustomAPI.Create +func (c *Client) CreateRRSet(ctx context.Context, dnsZoneName, groupName string, rrSet RRSet) (*APIRRSet, error) { + endpoint := c.baseURL.JoinPath("api", "config", "dns", "namespaces", "system", "dns_zones", dnsZoneName, "rrsets", groupName) + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, APIRRSet{ + DNSZoneName: dnsZoneName, + GroupName: groupName, + RRSet: rrSet, + }) + if err != nil { + return nil, err + } + + result := &APIRRSet{} + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result, nil +} + +// GetRRSet gets RRSets. +// https://docs.cloud.f5.com/docs-v2/api/dns-zone-rrset#operation/ves.io.schema.dns_zone.rrset.CustomAPI.Get +func (c *Client) GetRRSet(ctx context.Context, dnsZoneName, groupName, recordName, recordType string) (*APIRRSet, error) { + endpoint := c.baseURL.JoinPath("api", "config", "dns", "namespaces", "system", "dns_zones", dnsZoneName, "rrsets", groupName, recordName, recordType) + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + result := &APIRRSet{} + + err = c.do(req, result) + if err != nil { + usce := &APIError{} + if errors.As(err, &usce) && usce.StatusCode == http.StatusNotFound { + return nil, nil + } + + return nil, err + } + + return result, nil +} + +// DeleteRRSet deletes RRSet. +// https://docs.cloud.f5.com/docs-v2/api/dns-zone-rrset#operation/ves.io.schema.dns_zone.rrset.CustomAPI.Delete +func (c *Client) DeleteRRSet(ctx context.Context, dnsZoneName, groupName, recordName, recordType string) (*APIRRSet, error) { + endpoint := c.baseURL.JoinPath("api", "config", "dns", "namespaces", "system", "dns_zones", dnsZoneName, "rrsets", groupName, recordName, recordType) + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return nil, err + } + + result := &APIRRSet{} + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result, nil +} + +// ReplaceRRSet replaces RRSet. +// https://docs.cloud.f5.com/docs-v2/api/dns-zone-rrset#operation/ves.io.schema.dns_zone.rrset.CustomAPI.Replace +func (c *Client) ReplaceRRSet(ctx context.Context, dnsZoneName, groupName, recordName, recordType string, rrSet RRSet) (*APIRRSet, error) { + endpoint := c.baseURL.JoinPath("api", "config", "dns", "namespaces", "system", "dns_zones", dnsZoneName, "rrsets", groupName, recordName, recordType) + + req, err := newJSONRequest(ctx, http.MethodPut, endpoint, APIRRSet{ + DNSZoneName: dnsZoneName, + GroupName: groupName, + RRSet: rrSet, + Type: recordType, + }) + if err != nil { + return nil, err + } + + result := &APIRRSet{} + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result, nil +} + +func (c *Client) do(req *http.Request, result any) error { + req.Header.Set(authorizationHeader, "APIToken "+c.apiToken) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + apiErr := APIError{StatusCode: resp.StatusCode} + err := json.Unmarshal(raw, &apiErr) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &apiErr +} diff --git a/providers/dns/f5xc/internal/client_test.go b/providers/dns/f5xc/internal/client_test.go new file mode 100644 index 000000000..ca9ac2950 --- /dev/null +++ b/providers/dns/f5xc/internal/client_test.go @@ -0,0 +1,219 @@ +package internal + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupTest(t *testing.T, pattern string, status int, filename string) *Client { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { + if filename == "" { + rw.WriteHeader(status) + return + } + + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + defer func() { _ = file.Close() }() + + rw.WriteHeader(status) + _, err = io.Copy(rw, file) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + }) + + client, err := NewClient("secret", "shortname") + require.NoError(t, err) + + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) + + return client +} + +func TestClient_Create(t *testing.T) { + client := setupTest(t, "POST /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA", http.StatusOK, "create.json") + + rrSet := RRSet{ + Description: "lego", + TTL: 60, + TXTRecord: &TXTRecord{ + Name: "wwww", + Values: []string{"txt"}, + }, + } + + result, err := client.CreateRRSet(context.Background(), "example.com", "groupA", rrSet) + require.NoError(t, err) + + expected := &APIRRSet{ + DNSZoneName: "string", + GroupName: "string", + RRSet: RRSet{ + Description: "string", + TXTRecord: &TXTRecord{ + Name: "string", + Values: []string{"string"}, + }, + }, + } + + assert.Equal(t, expected, result) +} + +func TestClient_Create_error(t *testing.T) { + client := setupTest(t, "POST /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA", http.StatusBadRequest, "") + + rrSet := RRSet{ + Description: "lego", + TTL: 60, + TXTRecord: &TXTRecord{ + Name: "wwww", + Values: []string{"txt"}, + }, + } + + _, err := client.CreateRRSet(context.Background(), "example.com", "groupA", rrSet) + require.Error(t, err) +} + +func TestClient_Get(t *testing.T) { + client := setupTest(t, "GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusOK, "get.json") + + result, err := client.GetRRSet(context.Background(), "example.com", "groupA", "www", "TXT") + require.NoError(t, err) + + expected := &APIRRSet{ + DNSZoneName: "string", + GroupName: "string", + Namespace: "string", + RecordName: "string", + Type: "string", + RRSet: RRSet{ + Description: "string", + TXTRecord: &TXTRecord{ + Name: "string", + Values: []string{"string"}, + }, + }, + } + + assert.Equal(t, expected, result) +} + +func TestClient_Get_not_found(t *testing.T) { + client := setupTest(t, "GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusNotFound, "error_404.json") + + result, err := client.GetRRSet(context.Background(), "example.com", "groupA", "www", "TXT") + require.NoError(t, err) + + assert.Nil(t, result) +} + +func TestClient_Get_error(t *testing.T) { + client := setupTest(t, "GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusBadRequest, "") + + _, err := client.GetRRSet(context.Background(), "example.com", "groupA", "www", "TXT") + require.Error(t, err) +} + +func TestClient_Delete(t *testing.T) { + client := setupTest(t, "DELETE /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusOK, "get.json") + + result, err := client.DeleteRRSet(context.Background(), "example.com", "groupA", "www", "TXT") + require.NoError(t, err) + + expected := &APIRRSet{ + DNSZoneName: "string", + GroupName: "string", + Namespace: "string", + RecordName: "string", + Type: "string", + RRSet: RRSet{ + Description: "string", + TXTRecord: &TXTRecord{ + Name: "string", + Values: []string{"string"}, + }, + }, + } + + assert.Equal(t, expected, result) +} + +func TestClient_Delete_error(t *testing.T) { + client := setupTest(t, "DELETE /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusBadRequest, "") + + _, err := client.DeleteRRSet(context.Background(), "example.com", "groupA", "www", "TXT") + require.Error(t, err) +} + +func TestClient_Replace(t *testing.T) { + client := setupTest(t, "PUT /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusOK, "get.json") + + rrSet := RRSet{ + Description: "lego", + TTL: 60, + TXTRecord: &TXTRecord{ + Name: "wwww", + Values: []string{"txt"}, + }, + } + + result, err := client.ReplaceRRSet(context.Background(), "example.com", "groupA", "www", "TXT", rrSet) + require.NoError(t, err) + + expected := &APIRRSet{ + DNSZoneName: "string", + GroupName: "string", + Namespace: "string", + RecordName: "string", + Type: "string", + RRSet: RRSet{ + Description: "string", + TXTRecord: &TXTRecord{ + Name: "string", + Values: []string{"string"}, + }, + }, + } + + assert.Equal(t, expected, result) +} + +func TestClient_Replace_error(t *testing.T) { + client := setupTest(t, "PUT /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusBadRequest, "") + + rrSet := RRSet{ + Description: "lego", + TTL: 60, + TXTRecord: &TXTRecord{ + Name: "wwww", + Values: []string{"txt"}, + }, + } + + _, err := client.ReplaceRRSet(context.Background(), "example.com", "groupA", "www", "TXT", rrSet) + require.Error(t, err) +} diff --git a/providers/dns/f5xc/internal/fixtures/create.json b/providers/dns/f5xc/internal/fixtures/create.json new file mode 100644 index 000000000..8c852304d --- /dev/null +++ b/providers/dns/f5xc/internal/fixtures/create.json @@ -0,0 +1,204 @@ +{ + "dns_zone_name": "string", + "group_name": "string", + "rrset": { + "a_record": { + "name": "string", + "values": [ + "string" + ] + }, + "aaaa_record": { + "name": "string", + "values": [ + "string" + ] + }, + "afsdb_record": { + "name": "string", + "values": [ + { + "hostname": "string", + "subtype": "NONE" + } + ] + }, + "alias_record": { + "value": "string" + }, + "caa_record": { + "name": "string", + "values": [ + { + "flags": 0, + "tag": "string", + "value": "string" + } + ] + }, + "cds_record": { + "name": "string", + "values": [ + { + "ds_key_algorithm": "UNSPECIFIED", + "key_tag": 0, + "sha1_digest": { + "digest": "stringstringstringstringstringstringstri" + }, + "sha256_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstri" + }, + "sha384_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstring" + } + } + ] + }, + "cert_record": { + "name": "string", + "values": [ + { + "algorithm": "RESERVEDALGORITHM", + "cert_key_tag": 0, + "cert_type": "INVALIDCERTTYPE", + "certificate": "string" + } + ] + }, + "cname_record": { + "name": "string", + "value": "string" + }, + "description": "string", + "ds_record": { + "name": "string", + "values": [ + { + "ds_key_algorithm": "UNSPECIFIED", + "key_tag": 0, + "sha1_digest": { + "digest": "stringstringstringstringstringstringstri" + }, + "sha256_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstri" + }, + "sha384_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstring" + } + } + ] + }, + "eui48_record": { + "name": "string", + "value": "stringstringstrin" + }, + "eui64_record": { + "name": "string", + "value": "stringstringstringstrin" + }, + "lb_record": { + "name": "string", + "value": { + "name": "string", + "namespace": "string", + "tenant": "string" + } + }, + "loc_record": { + "name": "string", + "values": [ + { + "altitude": 0.1, + "horizontal_precision": 0.1, + "latitude_degree": 0, + "latitude_hemisphere": "N", + "latitude_minute": 0, + "latitude_second": 0.1, + "location_diameter": 0.1, + "longitude_degree": 0, + "longitude_hemisphere": "E", + "longitude_minute": 0, + "longitude_second": 0.1, + "vertical_precision": 0.1 + } + ] + }, + "mx_record": { + "name": "string", + "values": [ + { + "domain": "string", + "priority": 0 + } + ] + }, + "naptr_record": { + "name": "string", + "values": [ + { + "flags": "string", + "order": 0, + "preference": 0, + "regexp": "string", + "replacement": "string", + "service": "string" + } + ] + }, + "ns_record": { + "name": "string", + "values": [ + "string" + ] + }, + "ptr_record": { + "name": "string", + "values": [ + "string" + ] + }, + "srv_record": { + "name": "string", + "values": [ + { + "port": 0, + "priority": 0, + "target": "string", + "weight": 0 + } + ] + }, + "sshfp_record": { + "name": "string", + "values": [ + { + "algorithm": "UNSPECIFIEDALGORITHM", + "sha1_fingerprint": { + "fingerprint": "stringstringstringstringstringstringstri" + }, + "sha256_fingerprint": { + "fingerprint": "stringstringstringstringstringstringstringstringstringstringstri" + } + } + ] + }, + "tlsa_record": { + "name": "string", + "values": [ + { + "certificate_association_data": "string", + "certificate_usage": "CertificateAuthorityConstraint", + "matching_type": "NoHash", + "selector": "FullCertificate" + } + ] + }, + "ttl": 0, + "txt_record": { + "name": "string", + "values": [ + "string" + ] + } + } +} diff --git a/providers/dns/f5xc/internal/fixtures/delete.json b/providers/dns/f5xc/internal/fixtures/delete.json new file mode 100644 index 000000000..5c5143cae --- /dev/null +++ b/providers/dns/f5xc/internal/fixtures/delete.json @@ -0,0 +1,207 @@ +{ + "dns_zone_name": "string", + "group_name": "string", + "namespace": "string", + "record_name": "string", + "rrset": { + "a_record": { + "name": "string", + "values": [ + "string" + ] + }, + "aaaa_record": { + "name": "string", + "values": [ + "string" + ] + }, + "afsdb_record": { + "name": "string", + "values": [ + { + "hostname": "string", + "subtype": "NONE" + } + ] + }, + "alias_record": { + "value": "string" + }, + "caa_record": { + "name": "string", + "values": [ + { + "flags": 0, + "tag": "string", + "value": "string" + } + ] + }, + "cds_record": { + "name": "string", + "values": [ + { + "ds_key_algorithm": "UNSPECIFIED", + "key_tag": 0, + "sha1_digest": { + "digest": "stringstringstringstringstringstringstri" + }, + "sha256_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstri" + }, + "sha384_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstring" + } + } + ] + }, + "cert_record": { + "name": "string", + "values": [ + { + "algorithm": "RESERVEDALGORITHM", + "cert_key_tag": 0, + "cert_type": "INVALIDCERTTYPE", + "certificate": "string" + } + ] + }, + "cname_record": { + "name": "string", + "value": "string" + }, + "description": "string", + "ds_record": { + "name": "string", + "values": [ + { + "ds_key_algorithm": "UNSPECIFIED", + "key_tag": 0, + "sha1_digest": { + "digest": "stringstringstringstringstringstringstri" + }, + "sha256_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstri" + }, + "sha384_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstring" + } + } + ] + }, + "eui48_record": { + "name": "string", + "value": "stringstringstrin" + }, + "eui64_record": { + "name": "string", + "value": "stringstringstringstrin" + }, + "lb_record": { + "name": "string", + "value": { + "name": "string", + "namespace": "string", + "tenant": "string" + } + }, + "loc_record": { + "name": "string", + "values": [ + { + "altitude": 0.1, + "horizontal_precision": 0.1, + "latitude_degree": 0, + "latitude_hemisphere": "N", + "latitude_minute": 0, + "latitude_second": 0.1, + "location_diameter": 0.1, + "longitude_degree": 0, + "longitude_hemisphere": "E", + "longitude_minute": 0, + "longitude_second": 0.1, + "vertical_precision": 0.1 + } + ] + }, + "mx_record": { + "name": "string", + "values": [ + { + "domain": "string", + "priority": 0 + } + ] + }, + "naptr_record": { + "name": "string", + "values": [ + { + "flags": "string", + "order": 0, + "preference": 0, + "regexp": "string", + "replacement": "string", + "service": "string" + } + ] + }, + "ns_record": { + "name": "string", + "values": [ + "string" + ] + }, + "ptr_record": { + "name": "string", + "values": [ + "string" + ] + }, + "srv_record": { + "name": "string", + "values": [ + { + "port": 0, + "priority": 0, + "target": "string", + "weight": 0 + } + ] + }, + "sshfp_record": { + "name": "string", + "values": [ + { + "algorithm": "UNSPECIFIEDALGORITHM", + "sha1_fingerprint": { + "fingerprint": "stringstringstringstringstringstringstri" + }, + "sha256_fingerprint": { + "fingerprint": "stringstringstringstringstringstringstringstringstringstringstri" + } + } + ] + }, + "tlsa_record": { + "name": "string", + "values": [ + { + "certificate_association_data": "string", + "certificate_usage": "CertificateAuthorityConstraint", + "matching_type": "NoHash", + "selector": "FullCertificate" + } + ] + }, + "ttl": 0, + "txt_record": { + "name": "string", + "values": [ + "string" + ] + } + }, + "type": "string" +} diff --git a/providers/dns/f5xc/internal/fixtures/error_404.json b/providers/dns/f5xc/internal/fixtures/error_404.json new file mode 100644 index 000000000..4abd79dd4 --- /dev/null +++ b/providers/dns/f5xc/internal/fixtures/error_404.json @@ -0,0 +1,5 @@ +{ + "code": 5, + "details": [], + "message": "the requested resource record was not found: (group,name,type) (acme-records,_acme-challenge,TXT)" +} diff --git a/providers/dns/f5xc/internal/fixtures/error_503.json b/providers/dns/f5xc/internal/fixtures/error_503.json new file mode 100644 index 000000000..8d286a2a0 --- /dev/null +++ b/providers/dns/f5xc/internal/fixtures/error_503.json @@ -0,0 +1,5 @@ +{ + "code": 14, + "details": [], + "message": "Previous DNS zone change is pending. Try again later" +} diff --git a/providers/dns/f5xc/internal/fixtures/get.json b/providers/dns/f5xc/internal/fixtures/get.json new file mode 100644 index 000000000..5c5143cae --- /dev/null +++ b/providers/dns/f5xc/internal/fixtures/get.json @@ -0,0 +1,207 @@ +{ + "dns_zone_name": "string", + "group_name": "string", + "namespace": "string", + "record_name": "string", + "rrset": { + "a_record": { + "name": "string", + "values": [ + "string" + ] + }, + "aaaa_record": { + "name": "string", + "values": [ + "string" + ] + }, + "afsdb_record": { + "name": "string", + "values": [ + { + "hostname": "string", + "subtype": "NONE" + } + ] + }, + "alias_record": { + "value": "string" + }, + "caa_record": { + "name": "string", + "values": [ + { + "flags": 0, + "tag": "string", + "value": "string" + } + ] + }, + "cds_record": { + "name": "string", + "values": [ + { + "ds_key_algorithm": "UNSPECIFIED", + "key_tag": 0, + "sha1_digest": { + "digest": "stringstringstringstringstringstringstri" + }, + "sha256_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstri" + }, + "sha384_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstring" + } + } + ] + }, + "cert_record": { + "name": "string", + "values": [ + { + "algorithm": "RESERVEDALGORITHM", + "cert_key_tag": 0, + "cert_type": "INVALIDCERTTYPE", + "certificate": "string" + } + ] + }, + "cname_record": { + "name": "string", + "value": "string" + }, + "description": "string", + "ds_record": { + "name": "string", + "values": [ + { + "ds_key_algorithm": "UNSPECIFIED", + "key_tag": 0, + "sha1_digest": { + "digest": "stringstringstringstringstringstringstri" + }, + "sha256_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstri" + }, + "sha384_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstring" + } + } + ] + }, + "eui48_record": { + "name": "string", + "value": "stringstringstrin" + }, + "eui64_record": { + "name": "string", + "value": "stringstringstringstrin" + }, + "lb_record": { + "name": "string", + "value": { + "name": "string", + "namespace": "string", + "tenant": "string" + } + }, + "loc_record": { + "name": "string", + "values": [ + { + "altitude": 0.1, + "horizontal_precision": 0.1, + "latitude_degree": 0, + "latitude_hemisphere": "N", + "latitude_minute": 0, + "latitude_second": 0.1, + "location_diameter": 0.1, + "longitude_degree": 0, + "longitude_hemisphere": "E", + "longitude_minute": 0, + "longitude_second": 0.1, + "vertical_precision": 0.1 + } + ] + }, + "mx_record": { + "name": "string", + "values": [ + { + "domain": "string", + "priority": 0 + } + ] + }, + "naptr_record": { + "name": "string", + "values": [ + { + "flags": "string", + "order": 0, + "preference": 0, + "regexp": "string", + "replacement": "string", + "service": "string" + } + ] + }, + "ns_record": { + "name": "string", + "values": [ + "string" + ] + }, + "ptr_record": { + "name": "string", + "values": [ + "string" + ] + }, + "srv_record": { + "name": "string", + "values": [ + { + "port": 0, + "priority": 0, + "target": "string", + "weight": 0 + } + ] + }, + "sshfp_record": { + "name": "string", + "values": [ + { + "algorithm": "UNSPECIFIEDALGORITHM", + "sha1_fingerprint": { + "fingerprint": "stringstringstringstringstringstringstri" + }, + "sha256_fingerprint": { + "fingerprint": "stringstringstringstringstringstringstringstringstringstringstri" + } + } + ] + }, + "tlsa_record": { + "name": "string", + "values": [ + { + "certificate_association_data": "string", + "certificate_usage": "CertificateAuthorityConstraint", + "matching_type": "NoHash", + "selector": "FullCertificate" + } + ] + }, + "ttl": 0, + "txt_record": { + "name": "string", + "values": [ + "string" + ] + } + }, + "type": "string" +} diff --git a/providers/dns/f5xc/internal/fixtures/replace.json b/providers/dns/f5xc/internal/fixtures/replace.json new file mode 100644 index 000000000..e3e483df5 --- /dev/null +++ b/providers/dns/f5xc/internal/fixtures/replace.json @@ -0,0 +1,206 @@ +{ + "dns_zone_name": "string", + "group_name": "string", + "record_name": "string", + "rrset": { + "a_record": { + "name": "string", + "values": [ + "string" + ] + }, + "aaaa_record": { + "name": "string", + "values": [ + "string" + ] + }, + "afsdb_record": { + "name": "string", + "values": [ + { + "hostname": "string", + "subtype": "NONE" + } + ] + }, + "alias_record": { + "value": "string" + }, + "caa_record": { + "name": "string", + "values": [ + { + "flags": 0, + "tag": "string", + "value": "string" + } + ] + }, + "cds_record": { + "name": "string", + "values": [ + { + "ds_key_algorithm": "UNSPECIFIED", + "key_tag": 0, + "sha1_digest": { + "digest": "stringstringstringstringstringstringstri" + }, + "sha256_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstri" + }, + "sha384_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstring" + } + } + ] + }, + "cert_record": { + "name": "string", + "values": [ + { + "algorithm": "RESERVEDALGORITHM", + "cert_key_tag": 0, + "cert_type": "INVALIDCERTTYPE", + "certificate": "string" + } + ] + }, + "cname_record": { + "name": "string", + "value": "string" + }, + "description": "string", + "ds_record": { + "name": "string", + "values": [ + { + "ds_key_algorithm": "UNSPECIFIED", + "key_tag": 0, + "sha1_digest": { + "digest": "stringstringstringstringstringstringstri" + }, + "sha256_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstri" + }, + "sha384_digest": { + "digest": "stringstringstringstringstringstringstringstringstringstringstringstringstringstringstringstring" + } + } + ] + }, + "eui48_record": { + "name": "string", + "value": "stringstringstrin" + }, + "eui64_record": { + "name": "string", + "value": "stringstringstringstrin" + }, + "lb_record": { + "name": "string", + "value": { + "name": "string", + "namespace": "string", + "tenant": "string" + } + }, + "loc_record": { + "name": "string", + "values": [ + { + "altitude": 0.1, + "horizontal_precision": 0.1, + "latitude_degree": 0, + "latitude_hemisphere": "N", + "latitude_minute": 0, + "latitude_second": 0.1, + "location_diameter": 0.1, + "longitude_degree": 0, + "longitude_hemisphere": "E", + "longitude_minute": 0, + "longitude_second": 0.1, + "vertical_precision": 0.1 + } + ] + }, + "mx_record": { + "name": "string", + "values": [ + { + "domain": "string", + "priority": 0 + } + ] + }, + "naptr_record": { + "name": "string", + "values": [ + { + "flags": "string", + "order": 0, + "preference": 0, + "regexp": "string", + "replacement": "string", + "service": "string" + } + ] + }, + "ns_record": { + "name": "string", + "values": [ + "string" + ] + }, + "ptr_record": { + "name": "string", + "values": [ + "string" + ] + }, + "srv_record": { + "name": "string", + "values": [ + { + "port": 0, + "priority": 0, + "target": "string", + "weight": 0 + } + ] + }, + "sshfp_record": { + "name": "string", + "values": [ + { + "algorithm": "UNSPECIFIEDALGORITHM", + "sha1_fingerprint": { + "fingerprint": "stringstringstringstringstringstringstri" + }, + "sha256_fingerprint": { + "fingerprint": "stringstringstringstringstringstringstringstringstringstringstri" + } + } + ] + }, + "tlsa_record": { + "name": "string", + "values": [ + { + "certificate_association_data": "string", + "certificate_usage": "CertificateAuthorityConstraint", + "matching_type": "NoHash", + "selector": "FullCertificate" + } + ] + }, + "ttl": 0, + "txt_record": { + "name": "string", + "values": [ + "string" + ] + } + }, + "type": "string" +} diff --git a/providers/dns/f5xc/internal/types.go b/providers/dns/f5xc/internal/types.go new file mode 100644 index 000000000..3122ca4ef --- /dev/null +++ b/providers/dns/f5xc/internal/types.go @@ -0,0 +1,48 @@ +package internal + +import ( + "fmt" + "strings" +) + +type APIError struct { + StatusCode int `json:"-"` + Code int `json:"code"` + Details []string `json:"details"` + Message string `json:"message"` +} + +func (a *APIError) Error() string { + var details string + if len(a.Details) > 0 { + details = " " + strings.Join(a.Details, ", ") + } + + return fmt.Sprintf("code: %d, message: %s%s", a.Code, a.Message, details) +} + +type APIRRSet struct { + DNSZoneName string `json:"dns_zone_name,omitempty"` + GroupName string `json:"group_name,omitempty"` + Namespace string `json:"namespace,omitempty"` + RecordName string `json:"record_name,omitempty"` + Type string `json:"type,omitempty"` + RRSet RRSet `json:"rrset,omitempty"` +} + +type RRSetRequest struct { + DNSZoneName string `json:"dns_zone_name,omitempty"` + GroupName string `json:"group_name,omitempty"` + RRSet RRSet `json:"rrset,omitempty"` +} + +type RRSet struct { + Description string `json:"description,omitempty"` + TTL int `json:"ttl,omitempty"` + TXTRecord *TXTRecord `json:"txt_record,omitempty"` +} + +type TXTRecord struct { + Name string `json:"name,omitempty"` + Values []string `json:"values,omitempty"` +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index f3afca153..f52feaa25 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -52,6 +52,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/epik" "github.com/go-acme/lego/v4/providers/dns/exec" "github.com/go-acme/lego/v4/providers/dns/exoscale" + "github.com/go-acme/lego/v4/providers/dns/f5xc" "github.com/go-acme/lego/v4/providers/dns/freemyip" "github.com/go-acme/lego/v4/providers/dns/gandi" "github.com/go-acme/lego/v4/providers/dns/gandiv5" @@ -252,6 +253,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return exec.NewDNSProvider() case "exoscale": return exoscale.NewDNSProvider() + case "f5xc": + return f5xc.NewDNSProvider() case "freemyip": return freemyip.NewDNSProvider() case "gandi": From c8aa9920eaacef8e6c5e16d647f376a8a80ea593 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 5 Mar 2025 13:32:06 +0100 Subject: [PATCH 065/298] dnssimple: use GetZone (#2467) --- providers/dns/dnsimple/dnsimple.go | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/providers/dns/dnsimple/dnsimple.go b/providers/dns/dnsimple/dnsimple.go index db80eb80c..3cdbe73b4 100644 --- a/providers/dns/dnsimple/dnsimple.go +++ b/providers/dns/dnsimple/dnsimple.go @@ -161,25 +161,16 @@ func (d *DNSProvider) getHostedZone(domain string) (string, error) { return "", err } - zoneName := dns01.UnFqdn(authZone) - - zones, err := d.client.Zones.ListZones(context.Background(), accountID, &dnsimple.ZoneListOptions{NameLike: &zoneName}) + hostedZone, err := d.client.Zones.GetZone(context.Background(), accountID, dns01.UnFqdn(authZone)) if err != nil { - return "", fmt.Errorf("API call failed: %w", err) + return "", fmt.Errorf("get zone: %w", err) } - var hostedZone dnsimple.Zone - for _, zone := range zones.Data { - if zone.Name == zoneName { - hostedZone = zone - } - } - - if hostedZone.ID == 0 { + if hostedZone == nil || hostedZone.Data == nil || hostedZone.Data.ID == 0 { return "", fmt.Errorf("zone %s not found in DNSimple for domain %s", authZone, domain) } - return hostedZone.Name, nil + return hostedZone.Data.Name, nil } func (d *DNSProvider) findTxtRecords(fqdn string) ([]dnsimple.ZoneRecord, error) { From 13780562cc80a59d14805a81daedb7d0b0062260 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 5 Mar 2025 13:32:23 +0100 Subject: [PATCH 066/298] fix: kill hook when the command is stuck (#2469) --- cmd/hook.go | 44 ++++++++++++++++++++------ cmd/hook_test.go | 56 +++++++++++++++++++++++++++++++++ cmd/testdata/sleeping_beauty.sh | 3 ++ cmd/testdata/sleepy.sh | 7 +++++ 4 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 cmd/hook_test.go create mode 100755 cmd/testdata/sleeping_beauty.sh create mode 100755 cmd/testdata/sleepy.sh diff --git a/cmd/hook.go b/cmd/hook.go index 5e19c7995..495dbbd79 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -1,6 +1,7 @@ package cmd import ( + "bufio" "context" "errors" "fmt" @@ -10,6 +11,7 @@ import ( "time" "github.com/go-acme/lego/v4/certificate" + "github.com/go-acme/lego/v4/log" ) const ( @@ -32,20 +34,44 @@ func launchHook(hook string, timeout time.Duration, meta map[string]string) erro parts := strings.Fields(hook) - cmdCtx := exec.CommandContext(ctxCmd, parts[0], parts[1:]...) - cmdCtx.Env = append(os.Environ(), metaToEnv(meta)...) + cmd := exec.CommandContext(ctxCmd, parts[0], parts[1:]...) + cmd.Env = append(os.Environ(), metaToEnv(meta)...) - output, err := cmdCtx.CombinedOutput() - - if len(output) > 0 { - fmt.Println(string(output)) + stdout, err := cmd.StdoutPipe() + if err != nil { + return fmt.Errorf("create pipe: %w", err) } - if errors.Is(ctxCmd.Err(), context.DeadlineExceeded) { - return errors.New("hook timed out") + cmd.Stderr = cmd.Stdout + + err = cmd.Start() + if err != nil { + return fmt.Errorf("start command: %w", err) } - return err + timer := time.AfterFunc(timeout, func() { + log.Println("hook timed out: killing command") + _ = cmd.Process.Kill() + _ = stdout.Close() + }) + + defer timer.Stop() + + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + fmt.Println(scanner.Text()) + } + + err = cmd.Wait() + if err != nil { + if errors.Is(ctxCmd.Err(), context.DeadlineExceeded) { + return errors.New("hook timed out") + } + + return fmt.Errorf("wait command: %w", err) + } + + return nil } func metaToEnv(meta map[string]string) []string { diff --git a/cmd/hook_test.go b/cmd/hook_test.go new file mode 100644 index 000000000..dd8551da6 --- /dev/null +++ b/cmd/hook_test.go @@ -0,0 +1,56 @@ +package cmd + +import ( + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func Test_launchHook_errors(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } + + testCases := []struct { + desc string + hook string + timeout time.Duration + expected string + }{ + { + desc: "kill the hook", + hook: "sleep 5", + timeout: 1 * time.Second, + expected: "hook timed out", + }, + { + desc: "context timeout on Start", + hook: "echo foo", + timeout: 1 * time.Nanosecond, + expected: "start command: context deadline exceeded", + }, + { + desc: "multiple short sleeps", + hook: "./testdata/sleepy.sh", + timeout: 1 * time.Second, + expected: "hook timed out", + }, + { + desc: "long sleep", + hook: "./testdata/sleeping_beauty.sh", + timeout: 1 * time.Second, + expected: "hook timed out", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + err := launchHook(test.hook, test.timeout, map[string]string{}) + require.EqualError(t, err, test.expected) + }) + } +} diff --git a/cmd/testdata/sleeping_beauty.sh b/cmd/testdata/sleeping_beauty.sh new file mode 100755 index 000000000..96b42a005 --- /dev/null +++ b/cmd/testdata/sleeping_beauty.sh @@ -0,0 +1,3 @@ +#!/bin/bash -e + +sleep 50 diff --git a/cmd/testdata/sleepy.sh b/cmd/testdata/sleepy.sh new file mode 100755 index 000000000..60bb903a1 --- /dev/null +++ b/cmd/testdata/sleepy.sh @@ -0,0 +1,7 @@ +#!/bin/bash -e + +for i in `seq 1 10` +do + echo $i + sleep 0.2 +done From 3b9752b625d2ded173ff287acaf1a4a357dc3343 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 5 Mar 2025 13:32:40 +0100 Subject: [PATCH 067/298] chore: update dependencies (#2470) --- go.mod | 129 ++++++++++++------------- go.sum | 295 +++++++++++++++++++++++++++++---------------------------- 2 files changed, 213 insertions(+), 211 deletions(-) diff --git a/go.mod b/go.mod index 2ec442f0e..85ac79f3c 100644 --- a/go.mod +++ b/go.mod @@ -5,45 +5,45 @@ go 1.23.0 require ( cloud.google.com/go/compute/metadata v0.6.0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 - github.com/Azure/go-autorest/autorest v0.11.29 + github.com/Azure/go-autorest/autorest v0.11.30 github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 - github.com/Azure/go-autorest/autorest/to v0.4.0 + github.com/Azure/go-autorest/autorest/to v0.4.1 github.com/BurntSushi/toml v1.4.0 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 - github.com/aliyun/alibaba-cloud-sdk-go v1.63.72 - github.com/aws/aws-sdk-go-v2 v1.32.7 - github.com/aws/aws-sdk-go-v2/config v1.28.7 - github.com/aws/aws-sdk-go-v2/credentials v1.17.48 - github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8 - github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4 - github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1 - github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 + github.com/aliyun/alibaba-cloud-sdk-go v1.63.92 + github.com/aws/aws-sdk-go-v2 v1.36.3 + github.com/aws/aws-sdk-go-v2/config v1.29.9 + github.com/aws/aws-sdk-go-v2/credentials v1.17.62 + github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.1 + github.com/aws/aws-sdk-go-v2/service/route53 v1.49.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.78.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 github.com/cenkalti/backoff/v4 v4.3.0 github.com/civo/civogo v0.3.11 - github.com/cloudflare/cloudflare-go v0.112.0 + github.com/cloudflare/cloudflare-go v0.115.0 github.com/dnsimple/dnsimple-go v1.7.0 - github.com/exoscale/egoscale/v3 v3.1.7 - github.com/go-jose/go-jose/v4 v4.0.4 + github.com/exoscale/egoscale/v3 v3.1.10 + github.com/go-jose/go-jose/v4 v4.0.5 github.com/go-viper/mapstructure/v2 v2.2.1 github.com/google/go-querystring v1.1.0 github.com/gophercloud/gophercloud v1.14.1 github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/hashicorp/go-retryablehttp v0.7.7 github.com/hashicorp/go-version v1.7.0 - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.138 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df github.com/infobloxopen/infoblox-go-client/v2 v2.9.0 github.com/labbsr0x/bindman-dns-webhook v1.0.2 - github.com/linode/linodego v1.44.0 + github.com/linode/linodego v1.48.0 github.com/liquidweb/liquidweb-go v1.6.4 github.com/mattn/go-isatty v0.0.20 - github.com/miekg/dns v1.1.62 + github.com/miekg/dns v1.1.63 github.com/mimuret/golang-iij-dpf v0.9.1 github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 github.com/nrdcg/auroradns v1.1.0 @@ -58,42 +58,42 @@ require ( github.com/nrdcg/nodion v0.1.0 github.com/nrdcg/porkbun v0.4.0 github.com/nzdjb/go-metaname v1.0.0 - github.com/oracle/oci-go-sdk/v65 v65.81.1 - github.com/ovh/go-ovh v1.6.0 + github.com/oracle/oci-go-sdk/v65 v65.85.0 + github.com/ovh/go-ovh v1.7.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.14.0 - github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 + github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32 github.com/selectel/domains-go v1.1.0 github.com/selectel/go-selvpcclient/v3 v3.2.1 github.com/softlayer/softlayer-go v1.1.7 github.com/stretchr/testify v1.10.0 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1114 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1113 github.com/transip/gotransip/v6 v6.26.0 github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec github.com/urfave/cli/v2 v2.27.5 github.com/vinyldns/go-vinyldns v0.9.16 - github.com/volcengine/volc-sdk-golang v1.0.189 - github.com/vultr/govultr/v3 v3.9.1 - github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c - github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 - golang.org/x/crypto v0.31.0 - golang.org/x/net v0.33.0 - golang.org/x/oauth2 v0.24.0 - golang.org/x/text v0.21.0 - golang.org/x/time v0.8.0 - google.golang.org/api v0.214.0 + github.com/volcengine/volc-sdk-golang v1.0.197 + github.com/vultr/govultr/v3 v3.14.1 + github.com/yandex-cloud/go-genproto v0.0.0-20250304111827-f558b88ff434 + github.com/yandex-cloud/go-sdk v0.0.0-20250304120247-c2605c41f59f + golang.org/x/crypto v0.35.0 + golang.org/x/net v0.36.0 + golang.org/x/oauth2 v0.27.0 + golang.org/x/text v0.22.0 + golang.org/x/time v0.10.0 + google.golang.org/api v0.223.0 gopkg.in/ns1/ns1-go.v2 v2.13.0 gopkg.in/yaml.v2 v2.4.0 software.sslmate.com/src/go-pkcs12 v0.5.0 ) require ( - cloud.google.com/go/auth v0.13.0 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect + cloud.google.com/go/auth v0.15.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect @@ -102,20 +102,20 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect - github.com/aws/smithy-go v1.22.1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 // indirect + github.com/aws/smithy-go v1.22.2 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect @@ -132,15 +132,15 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.16.0 // indirect - github.com/go-resty/resty/v2 v2.16.2 // indirect - github.com/goccy/go-json v0.10.4 // indirect + github.com/go-resty/resty/v2 v2.16.5 // indirect + github.com/goccy/go-json v0.10.5 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect - github.com/google/s2a-go v0.1.8 // indirect + github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect - github.com/googleapis/gax-go/v2 v2.14.0 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -186,24 +186,25 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - go.mongodb.org/mongo-driver v1.12.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect + go.mongodb.org/mongo-driver v1.13.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/ratelimit v0.3.0 // indirect golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect golang.org/x/tools v0.28.0 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect - google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.35.2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect + google.golang.org/grpc v1.70.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index eea7bf9fe..39f8c89cf 100644 --- a/go.sum +++ b/go.sum @@ -13,10 +13,10 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= -cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= -cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= -cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= +cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= +cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= +cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -42,12 +42,12 @@ github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYs github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI= -github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c= @@ -63,8 +63,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1. github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= -github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= -github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs= +github.com/Azure/go-autorest/autorest v0.11.30 h1:iaZ1RGz/ALZtN5eq4Nr1SOFSlf2E4pDI3Tcsl+dZPVE= +github.com/Azure/go-autorest/autorest v0.11.30/go.mod h1:t1kpPIOpIVX7annvothKvb0stsrXa37i7b+xpmBW8Fs= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/adal v0.9.22 h1:/GblQdIudfEM3AWWZ0mrYJQSd7JS4S/Mbzh6F0ov0Xc= github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= @@ -77,16 +77,16 @@ github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSY github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= -github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= +github.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc= +github.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4= +github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= @@ -113,8 +113,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/aliyun/alibaba-cloud-sdk-go v1.63.72 h1:HvFZUzEbNvfe8F2Mg0wBGv90bPhWDxgVtDHR5zoBOU0= -github.com/aliyun/alibaba-cloud-sdk-go v1.63.72/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= +github.com/aliyun/alibaba-cloud-sdk-go v1.63.92 h1:qespx4b6EexlXkvQUow9x0v1GnWUJYGU5FWYw3a4Wlg= +github.com/aliyun/alibaba-cloud-sdk-go v1.63.92/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -124,48 +124,48 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw= -github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= -github.com/aws/aws-sdk-go-v2/config v1.28.7 h1:GduUnoTXlhkgnxTD93g1nv4tVPILbdNQOzav+Wpg7AE= -github.com/aws/aws-sdk-go-v2/config v1.28.7/go.mod h1:vZGX6GVkIE8uECSUHB6MWAUsd4ZcG2Yq/dMa4refR3M= -github.com/aws/aws-sdk-go-v2/credentials v1.17.48 h1:IYdLD1qTJ0zanRavulofmqut4afs45mOWEI+MzZtTfQ= -github.com/aws/aws-sdk-go-v2/credentials v1.17.48/go.mod h1:tOscxHN3CGmuX9idQ3+qbkzrjVIx32lqDSU1/0d/qXs= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 h1:kqOrpojG71DxJm/KDPO+Z/y1phm1JlC8/iT+5XRmAn8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22/go.mod h1:NtSFajXVVL8TA2QNngagVZmUtXciyrHOt7xgz4faS/M= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 h1:GeNJsIFHB+WW5ap2Tec4K6dzcVTsRbsT1Lra46Hv9ME= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26/go.mod h1:zfgMpwHDXX2WGoG84xG2H+ZlPTkJUU4YUvx2svLQYWo= +github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= +github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14= +github.com/aws/aws-sdk-go-v2/config v1.29.9 h1:Kg+fAYNaJeGXp1vmjtidss8O2uXIsXwaRqsQJKXVr+0= +github.com/aws/aws-sdk-go-v2/config v1.29.9/go.mod h1:oU3jj2O53kgOU4TXq/yipt6ryiooYjlkqqVaZk7gY/U= +github.com/aws/aws-sdk-go-v2/credentials v1.17.62 h1:fvtQY3zFzYJ9CfixuAQ96IxDrBajbBWGqjNTCa79ocU= +github.com/aws/aws-sdk-go-v2/credentials v1.17.62/go.mod h1:ElETBxIQqcxej++Cs8GyPBbgMys5DgQPTwo7cUPDKt8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7 h1:tB4tNw83KcajNAzaIMhkhVI2Nt8fAZd5A5ro113FEMY= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7/go.mod h1:lvpyBGkZ3tZ9iSsUIcC2EWp+0ywa7aK3BLT+FwZi+mQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 h1:8eUsivBQzZHqe/3FE+cqwfH+0p5Jo8PFM/QYQSmeZ+M= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7/go.mod h1:kLPQvGUmxn/fqiCrDeohwG33bq2pQpGeY62yRO6Nrh0= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7 h1:Hi0KGbrnr57bEHWM0bJ1QcBzxLrL/k2DHvGYhb8+W1w= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7/go.mod h1:wKNgWgExdjjrm4qvfbTorkvocEstaoDl4WCvGfeCy9c= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8 h1:+lmJoqxuUoPlSfGk5JYQQivd9YFjUvRZR6RPY+Wcx48= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.42.8/go.mod h1:Gg8/myP4+rgRi4+j9gQdbOEnMtwMAUUIeXo+nKCFVj8= -github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4 h1:0jMtawybbfpFEIMy4wvfyW2Z4YLr7mnuzT0fhR67Nrc= -github.com/aws/aws-sdk-go-v2/service/route53 v1.46.4/go.mod h1:xlMODgumb0Pp8bzfpojqelDrf8SL9rb5ovwmwKJl+oU= -github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1 h1:aOVVZJgWbaH+EJYPvEgkNhCEbXXvH7+oML36oaPK3zE= -github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1/go.mod h1:r+xl5yzMk9083rMR+sJ5TYj9Tihvf/l1oxzZXDgGj2Q= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 h1:CvuUmnXI7ebaUAhbJcDy9YQx8wHR69eZ9I7q5hszt/g= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.8/go.mod h1:XDeGv1opzwm8ubxddF0cgqkZWsyOtw4lr6dxwmb6YQg= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 h1:F2rBfNAL5UyswqoeWv9zs74N/NanhK16ydHW1pahX6E= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7/go.mod h1:JfyQ0g2JG8+Krq0EuZNnRwX0mU0HrwY/tG6JNfcqh4k= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 h1:Xgv/hyNgvLda/M9l9qxXc4UFSgppnRczLxlMs5Ae/QY= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.3/go.mod h1:5Gn+d+VaaRgsjewpMvGazt0WfcFO+Md4wLOuBfGR9Bc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.2 h1:t/gZFyrijKuSU0elA5kRngP/oU3mc0I+Dvp8HwRE4c0= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.2/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.1 h1:0j58UseBtLuBcP6nY2z4SM1qZEvLF0ylyH6+ggnphLg= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.1/go.mod h1:Qy22QnQSdHbZwMZrarsWZBIuK51isPlkD+Z4sztxX0o= +github.com/aws/aws-sdk-go-v2/service/route53 v1.49.1 h1:krDhGq5RpSgpfPB9riTYLLSoCB8bNBhtdva6t1HDEWc= +github.com/aws/aws-sdk-go-v2/service/route53 v1.49.1/go.mod h1:kGYOjvTa0Vw0qxrqrOLut1vMnui6qLxqv/SX3vYeM8Y= +github.com/aws/aws-sdk-go-v2/service/s3 v1.78.1 h1:1M0gSbyP6q06gl3384wpoKPaH9G16NPqZFieEhLboSU= +github.com/aws/aws-sdk-go-v2/service/s3 v1.78.1/go.mod h1:4qzsZSzB/KiX2EzDjs9D7A8rI/WGJxZceVJIHqtJjIU= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 h1:8JdC7Gr9NROg1Rusk25IcZeTO59zLxsKgE0gkh5O6h0= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.1/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 h1:KwuLovgQPcdjNMfFt9OhUd9a2OwcOKhxfvF4glTzLuA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= -github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= +github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -198,8 +198,8 @@ github.com/civo/civogo v0.3.11 h1:mON/fyrV946Sbk6paRtOSGsN+asCgCmHCgArf5xmGxM= github.com/civo/civogo v0.3.11/go.mod h1:7+GeeFwc4AYTULaEshpT2vIcl3Qq8HPoxA17viX3l6g= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.112.0 h1:caFwqXdGJCl3rjVMgbPEn8iCYAg9JsRYV3dIVQE5d7g= -github.com/cloudflare/cloudflare-go v0.112.0/go.mod h1:QB55kuJ5ZTeLNFcLJePfMuBilhu/LDKpLBmKFQIoSZ0= +github.com/cloudflare/cloudflare-go v0.115.0 h1:84/dxeeXweCc0PN5Cto44iTA8AkG1fyT11yPO5ZB7sM= +github.com/cloudflare/cloudflare-go v0.115.0/go.mod h1:Ds6urDwn/TF2uIU24mu7H91xkKP8gSAHxQ44DSZgVmU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= @@ -239,8 +239,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/exoscale/egoscale/v3 v3.1.7 h1:Q6p9tOVY0IiOW0fUpaPQWY7ggGEuSPZLAGxFgDd2sCE= -github.com/exoscale/egoscale/v3 v3.1.7/go.mod h1:GHKucK/J26v8PGWztGdhxWNMjrjG9PbelxKCJ4YI11Q= +github.com/exoscale/egoscale/v3 v3.1.10 h1:L4f8dW7q6uqgMwLWdlRgXSfJfrry4uMigls8dUF72ZA= +github.com/exoscale/egoscale/v3 v3.1.10/go.mod h1:t9+MpSEam94na48O/xgvvPFpQPRiwZ3kBN4/UuQtKco= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= @@ -272,8 +272,8 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= -github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= @@ -296,8 +296,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg= -github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= +github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= +github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -308,8 +308,8 @@ github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= -github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= -github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= @@ -355,6 +355,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -374,8 +376,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -394,8 +396,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= -github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -405,8 +407,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gT github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= -github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw= github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= @@ -475,8 +477,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 h1:kQ2Agpfy7Ze1ajn9xCQG9G6T7XIbqv+FBDS/U98W9Mk= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.138 h1:VH/OZE73y0IRomF9QqCw71etSdfFbQIq/utq164IOVg= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.138/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -549,8 +551,8 @@ github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk= github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/linode/linodego v1.44.0 h1:JZLLWzCAx3CmHSV9NmCoXisuqKtrmPhfY9MrgvaHMUY= -github.com/linode/linodego v1.44.0/go.mod h1:umdoNOmtbqAdGQbmQnPFZ2YS4US+/mU/1bA7MjoKAvg= +github.com/linode/linodego v1.48.0 h1:Xn00rWYSpK5arNEFwymW58jpsdnK8axxhwS/9+cFkQ0= +github.com/linode/linodego v1.48.0/go.mod h1:k/lRz48xUtGaeVYyvF2X2iNxMpt8JJ+DR4I77R8I1Vg= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= @@ -585,8 +587,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= -github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= +github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34= github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= @@ -680,10 +682,10 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= -github.com/oracle/oci-go-sdk/v65 v65.81.1 h1:JYc47bk8n/MUchA2KHu1ggsCQzlJZQLJ+tTKfOho00E= -github.com/oracle/oci-go-sdk/v65 v65.81.1/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0= -github.com/ovh/go-ovh v1.6.0 h1:ixLOwxQdzYDx296sXcgS35TOPEahJkpjMGtzPadCjQI= -github.com/ovh/go-ovh v1.6.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= +github.com/oracle/oci-go-sdk/v65 v65.85.0 h1:lRMFVgTCZhl7IzP0fXHIROFGWlIoVZMGIGr96Aai2eE= +github.com/oracle/oci-go-sdk/v65 v65.85.0/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0= +github.com/ovh/go-ovh v1.7.0 h1:V14nF7FwDjQrZt9g7jzcvAAQ3HN6DNShRFRMC3jLoPw= +github.com/ovh/go-ovh v1.7.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= @@ -745,15 +747,15 @@ 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/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= -github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= +github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= +github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= 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= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -770,8 +772,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 h1:yoKAVkEVwAqbGbR8n87rHQ1dulL25rKloGadb3vm770= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30/go.mod h1:sH0u6fq6x4R5M7WxkoQFY/o7UaiItec0o1LinLCJNq8= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32 h1:4+LP7qmsLSGbmc66m1s5dKRMBwztRppfxFKlYqYte/c= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32/go.mod h1:kzh+BSAvpoyHHdHBCDhmSWtBc1NbLMZ2lWHqnBoxFks= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/selectel/domains-go v1.1.0 h1:futG50J43ALLKQAnZk9H9yOtLGnSUh7c5hSvuC5gSHo= github.com/selectel/domains-go v1.1.0/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA= @@ -839,6 +841,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -847,10 +850,11 @@ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065 h1:krcqtAmexnHHBm/4ge4tr2b1cn/a7JGBESVGoZYXQAE= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 h1:aEFtLD1ceyeljQXB1S2BjN0zjTkf0X3XmpuxFIiC29w= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065/go.mod h1:HWvwy09hFSMXrj9SMvVRWV4U7rZO3l+WuogyNuxiT3M= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1113/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1114 h1:+LcGbkxgk+MRjplJej/Kmj9Hp043RI3rAGGcHqHNj2I= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1114/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1113 h1:RMPbo4SisyxOpaOZ+QtaXlGTEdedxzXqTu4MtUjC29U= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1113/go.mod h1:pKq9XZ1gACH+iZMCt6FIU2nhHcCoCbQETNj1/7FzvR0= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -868,10 +872,10 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= -github.com/volcengine/volc-sdk-golang v1.0.189 h1:VMDTHWYXakXJtZqPYn0As/h4eB0c4imvyru6mIp+o60= -github.com/volcengine/volc-sdk-golang v1.0.189/go.mod h1:u0VtPvlXWpXDTmc9IHkaW1q+5Jjwus4oAqRhNMDRInE= -github.com/vultr/govultr/v3 v3.9.1 h1:uxSIb8Miel7tqTs3ee+z3t+JelZikwqBBsZzCOPBy/8= -github.com/vultr/govultr/v3 v3.9.1/go.mod h1:Rd8ebpXm7jxH3MDmhnEs+zrlYW212ouhx+HeUMfHm2o= +github.com/volcengine/volc-sdk-golang v1.0.197 h1:jJlcMp+4i3lVL2ZZTVo8u3cndlXzC5SZoqH/4M6pyuw= +github.com/volcengine/volc-sdk-golang v1.0.197/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ= +github.com/vultr/govultr/v3 v3.14.1 h1:9BpyZgsWasuNoR39YVMcq44MSaF576Z4D+U3ro58eJQ= +github.com/vultr/govultr/v3 v3.14.1/go.mod h1:q34Wd76upKmf+vxFMgaNMH3A8BbsPBmSYZUGC8oZa5w= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= @@ -883,10 +887,10 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c h1:Rnr+lDYXVkP+3eT8/d68iq4G/UeIhyCQk+HKa8toTvg= -github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= -github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 h1:qmpz0Kvr9GAng8LAhRcKIpY71CEAcL3EBkftVlsP5Cw= -github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134/go.mod h1:KgZCJrxdhdw/sKhTQ/M3S9WOLri2PCnBlc4C3s+PfKY= +github.com/yandex-cloud/go-genproto v0.0.0-20250304111827-f558b88ff434 h1:yTo+nye0TPT/eh/WxMLRed+/IgIXFq4ngl2TKKBLY5Q= +github.com/yandex-cloud/go-genproto v0.0.0-20250304111827-f558b88ff434/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-sdk v0.0.0-20250304120247-c2605c41f59f h1:teFweT3kDo6fwIqNRy97faZf7TinptQjjtgqFznH4HQ= +github.com/yandex-cloud/go-sdk v0.0.0-20250304120247-c2605c41f59f/go.mod h1:T0bCwRJFLctOZMqmQCZPVcI0I5DtVtmVsidavp0K2+w= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -899,22 +903,28 @@ go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQc go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE= -go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0= +go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= +go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -952,13 +962,10 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1051,17 +1058,16 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= +golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1075,8 +1081,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1152,20 +1158,16 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1181,17 +1183,16 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1272,8 +1273,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA= -google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE= +google.golang.org/api v0.223.0 h1:JUTaWEriXmEy5AhvdMgksGGPEFsYfUKaPEYXd4c3Wvc= +google.golang.org/api v0.223.0/go.mod h1:C+RS7Z+dDwds2b+zoAk5hN/eSfsiCn0UDrYof/M4d2M= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1314,10 +1315,10 @@ google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxH google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU= google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4= -google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ= -google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 h1:DMTIbak9GhdaSxEjvVzAeNZvyc03I61duqNbnm3SU0M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1335,8 +1336,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1351,8 +1352,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 2bc147f58a53afcbfd9379710d4092d5f945ba25 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 5 Mar 2025 15:07:13 +0100 Subject: [PATCH 068/298] chore: related timer with context.Done (#2471) Co-authored-by: Dominik Menke --- cmd/hook.go | 15 +++++++-------- cmd/hook_test.go | 5 +++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/cmd/hook.go b/cmd/hook.go index 495dbbd79..c1de29c58 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -11,7 +11,6 @@ import ( "time" "github.com/go-acme/lego/v4/certificate" - "github.com/go-acme/lego/v4/log" ) const ( @@ -49,13 +48,13 @@ func launchHook(hook string, timeout time.Duration, meta map[string]string) erro return fmt.Errorf("start command: %w", err) } - timer := time.AfterFunc(timeout, func() { - log.Println("hook timed out: killing command") - _ = cmd.Process.Kill() - _ = stdout.Close() - }) - - defer timer.Stop() + go func() { + <-ctxCmd.Done() + if ctxCmd.Err() != nil { + _ = cmd.Process.Kill() + _ = stdout.Close() + } + }() scanner := bufio.NewScanner(stdout) for scanner.Scan() { diff --git a/cmd/hook_test.go b/cmd/hook_test.go index dd8551da6..d643bba30 100644 --- a/cmd/hook_test.go +++ b/cmd/hook_test.go @@ -8,6 +8,11 @@ import ( "github.com/stretchr/testify/require" ) +func Test_launchHook(t *testing.T) { + err := launchHook("echo foo", 1*time.Second, map[string]string{}) + require.NoError(t, err) +} + func Test_launchHook_errors(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("skipping test on Windows") From 730af10596bc4ef08bd68706950c36cc590d22ab Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 11 Mar 2025 17:54:28 +0100 Subject: [PATCH 069/298] Add DNS provider for Active24 (#2478) --- README.md | 76 +++--- cmd/zz_gen_cmd_dnshelp.go | 22 ++ docs/content/dns/zz_gen_active24.md | 69 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/active24/active24.go | 216 ++++++++++++++++++ providers/dns/active24/active24.toml | 24 ++ providers/dns/active24/active24_test.go | 145 ++++++++++++ providers/dns/active24/internal/client.go | 214 +++++++++++++++++ .../dns/active24/internal/client_test.go | 174 ++++++++++++++ .../active24/internal/fixtures/error_403.json | 5 + .../active24/internal/fixtures/error_422.json | 16 ++ .../active24/internal/fixtures/error_v1.json | 4 + .../active24/internal/fixtures/records.json | 28 +++ .../active24/internal/fixtures/services.json | 31 +++ providers/dns/active24/internal/types.go | 65 ++++++ providers/dns/zz_gen_dns_providers.go | 3 + 16 files changed, 1055 insertions(+), 39 deletions(-) create mode 100644 docs/content/dns/zz_gen_active24.md create mode 100644 providers/dns/active24/active24.go create mode 100644 providers/dns/active24/active24.toml create mode 100644 providers/dns/active24/active24_test.go create mode 100644 providers/dns/active24/internal/client.go create mode 100644 providers/dns/active24/internal/client_test.go create mode 100644 providers/dns/active24/internal/fixtures/error_403.json create mode 100644 providers/dns/active24/internal/fixtures/error_422.json create mode 100644 providers/dns/active24/internal/fixtures/error_v1.json create mode 100644 providers/dns/active24/internal/fixtures/records.json create mode 100644 providers/dns/active24/internal/fixtures/services.json create mode 100644 providers/dns/active24/internal/types.go diff --git a/README.md b/README.md index 80eed622f..646b064e9 100644 --- a/README.md +++ b/README.md @@ -53,195 +53,195 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + -
Active24 Akamai EdgeDNS Alibaba Cloud DNS all-inklAmazon Lightsail
Amazon Lightsail Amazon Route 53 ArvanCloud Aurora DNSAutodns
Autodns Azure (deprecated) Azure DNS BindmanBluecat
Bluecat BookMyName Brandit (deprecated) BunnyCheckdomain
Checkdomain Civo Cloud.ru CloudDNSCloudflare
Cloudflare ClouDNS CloudXNS (Deprecated) ConoHaConstellix
Constellix Core-Networks CPanel/WHM Derak ClouddeSEC.io
deSEC.io Designate DNSaaS for Openstack Digital Ocean DirectAdminDNS Made Easy
DNS Made Easy dnsHome.de DNSimple DNSPod (deprecated)Domain Offensive (do.de)
Domain Offensive (do.de) Domeneshop DreamHost Duck DNSDyn
Dyn Dynu EasyDNS Efficient IPEpik
Epik Exoscale External program F5 XCfreemyip.com
freemyip.com G-Core Gandi Gandi Live DNS (v5)Glesys
Glesys Go Daddy Google Cloud Google DomainsHetzner
Hetzner Hosting.de Hosttech HTTP requesthttp.net
http.net Huawei Cloud Hurricane Electric DNS HyperOneIBM Cloud (SoftLayer)
IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox InfomaniakInternet Initiative Japan
Internet Initiative Japan Internet.bs INWX IonosIPv64
IPv64 iwantmyname Joker Joohoi's ACME-DNSLiara
Liara Lima-City Linode (v4) Liquid WebLoopia
Loopia LuaDNS Mail-in-a-Box ManageEngine CloudDNSManual
Manual Metaname Metaregistrar mijn.hostMittwald
Mittwald myaddr.{tools,dev,io} MyDNS.jp MythicBeastsName.com
Name.com Namecheap Namesilo NearlyFreeSpeech.NETNetcup
Netcup Netlify Nicmanager NIFCloudNjalla
Njalla Nodion NS1 Open Telekom CloudOracle Cloud
Oracle Cloud OVH plesk.com PorkbunPowerDNS
PowerDNS Rackspace Rain Yun/雨云 RcodeZeroreg.ru
reg.ru Regfish RFC2136 RimuHostingSakura Cloud
Sakura Cloud Scaleway Selectel Selectel v2SelfHost.(de|eu)
SelfHost.(de|eu) Servercow Shellrent Simply.comSonic
Sonic Spaceship Stackpath TechnitiumTencent Cloud DNS
Tencent Cloud DNS Timeweb Cloud TransIP UKFast SafeDNSUltradns
Ultradns Variomedia VegaDNS VercelVersio.[nl|eu|uk]
Versio.[nl|eu|uk] VinylDNS VK Cloud Volcano Engine/火山引擎Vscale
Vscale Vultr Webnames WebsupportWEDOS
WEDOS West.cn/西部数码 Yandex 360 Yandex CloudYandex PDD
Yandex PDD Zone.ee Zonomi
diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 80f9ab243..2e8822501 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -14,6 +14,7 @@ func allDNSCodes() string { providers := []string{ "manual", "acme-dns", + "active24", "alidns", "allinkl", "arvancloud", @@ -191,6 +192,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/acme-dns`) + case "active24": + // generated from: providers/dns/active24/active24.toml + ew.writeln(`Configuration for Active24.`) + ew.writeln(`Code: 'active24'`) + ew.writeln(`Since: 'v4.23.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "ACTIVE24_API_KEY": API key`) + ew.writeln(` - "ACTIVE24_SECRET": Secret`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "ACTIVE24_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "ACTIVE24_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "ACTIVE24_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "ACTIVE24_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/active24`) + case "alidns": // generated from: providers/dns/alidns/alidns.toml ew.writeln(`Configuration for Alibaba Cloud DNS.`) diff --git a/docs/content/dns/zz_gen_active24.md b/docs/content/dns/zz_gen_active24.md new file mode 100644 index 000000000..cadc6660c --- /dev/null +++ b/docs/content/dns/zz_gen_active24.md @@ -0,0 +1,69 @@ +--- +title: "Active24" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: active24 +dnsprovider: + since: "v4.23.0" + code: "active24" + url: "https://www.active24.cz" +--- + + + + + + +Configuration for [Active24](https://www.active24.cz). + + + + +- Code: `active24` +- Since: v4.23.0 + + +Here is an example bash command using the Active24 provider: + +```bash +ACTIVE24_API_KEY="xxx" \ +ACTIVE24_SECRET="yyy" \ +lego --email you@example.com --dns active24 -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `ACTIVE24_API_KEY` | API key | +| `ACTIVE24_SECRET` | Secret | + +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 | +|--------------------------------|-------------| +| `ACTIVE24_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `ACTIVE24_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `ACTIVE24_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `ACTIVE24_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://rest.active24.cz/v2/docs) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 1cbfdbf4e..da083666f 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -149,7 +149,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, bookmyname, 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, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi + acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, bookmyname, 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, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/active24/active24.go b/providers/dns/active24/active24.go new file mode 100644 index 000000000..5f146d66e --- /dev/null +++ b/providers/dns/active24/active24.go @@ -0,0 +1,216 @@ +// Package active24 implements a DNS provider for solving the DNS-01 challenge using Active24. +package active24 + +import ( + "context" + "errors" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/active24/internal" +) + +// Environment variables names. +const ( + envNamespace = "ACTIVE24_" + + EnvAPIKey = envNamespace + "API_KEY" + EnvSecret = envNamespace + "SECRET" + + 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 + Secret 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for Active24. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIKey, EnvSecret) + if err != nil { + return nil, fmt.Errorf("active24: %w", err) + } + + config := NewDefaultConfig() + config.APIKey = values[EnvAPIKey] + config.Secret = values[EnvSecret] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Active24. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("active24: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.APIKey, config.Secret) + if err != nil { + return nil, fmt.Errorf("active24: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("active24: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("active24: %w", err) + } + + serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("active24: find service ID: %w", err) + } + + record := internal.Record{ + Type: "TXT", + Name: subDomain, + Content: info.Value, + TTL: d.config.TTL, + } + + err = d.client.CreateRecord(ctx, strconv.Itoa(serviceID), record) + if err != nil { + return fmt.Errorf("active24: create record: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("active24: could not find zone for domain %q: %w", domain, err) + } + + serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("active24: find service ID: %w", err) + } + + recordID, err := d.findRecordID(ctx, strconv.Itoa(serviceID), info) + if err != nil { + return fmt.Errorf("active24: find record ID: %w", err) + } + + err = d.client.DeleteRecord(ctx, strconv.Itoa(serviceID), strconv.Itoa(recordID)) + if err != nil { + return fmt.Errorf("active24: delete record %w", err) + } + + 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 +} + +func (d *DNSProvider) findServiceID(ctx context.Context, domain string) (int, error) { + services, err := d.client.GetServices(ctx) + if err != nil { + return 0, fmt.Errorf("get services: %w", err) + } + + for _, service := range services { + if service.ServiceName != "domain" { + continue + } + + if service.Name != domain { + continue + } + + return service.ID, nil + } + + return 0, fmt.Errorf("service not found for domain: %s", domain) +} + +func (d *DNSProvider) findRecordID(ctx context.Context, serviceID string, info dns01.ChallengeInfo) (int, error) { + // NOTE(ldez): Despite the API documentation, the filter doesn't seem to work. + filter := internal.RecordFilter{ + Name: dns01.UnFqdn(info.EffectiveFQDN), + Type: []string{"TXT"}, + Content: info.Value, + } + + records, err := d.client.GetRecords(ctx, serviceID, filter) + if err != nil { + return 0, fmt.Errorf("get records: %w", err) + } + + for _, record := range records { + if record.Type != "TXT" { + continue + } + + if record.Name != dns01.UnFqdn(info.EffectiveFQDN) { + continue + } + + if record.Content != info.Value { + continue + } + + return record.ID, nil + } + + return 0, errors.New("no record found") +} diff --git a/providers/dns/active24/active24.toml b/providers/dns/active24/active24.toml new file mode 100644 index 000000000..a1bf03924 --- /dev/null +++ b/providers/dns/active24/active24.toml @@ -0,0 +1,24 @@ +Name = "Active24" +Description = '''''' +URL = "https://www.active24.cz" +Code = "active24" +Since = "v4.23.0" + +Example = ''' +ACTIVE24_API_KEY="xxx" \ +ACTIVE24_SECRET="yyy" \ +lego --email you@example.com --dns active24 -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + ACTIVE24_API_KEY = "API key" + ACTIVE24_SECRET = "Secret" + [Configuration.Additional] + ACTIVE24_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + ACTIVE24_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + ACTIVE24_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + ACTIVE24_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://rest.active24.cz/v2/docs" diff --git a/providers/dns/active24/active24_test.go b/providers/dns/active24/active24_test.go new file mode 100644 index 000000000..d7d2c5535 --- /dev/null +++ b/providers/dns/active24/active24_test.go @@ -0,0 +1,145 @@ +package active24 + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvAPIKey, EnvSecret).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvAPIKey: "user", + EnvSecret: "secret", + }, + }, + { + desc: "missing API key", + envVars: map[string]string{ + EnvAPIKey: "", + EnvSecret: "secret", + }, + expected: "active24: some credentials information are missing: ACTIVE24_API_KEY", + }, + { + desc: "missing secret", + envVars: map[string]string{ + EnvAPIKey: "user", + EnvSecret: "", + }, + expected: "active24: some credentials information are missing: ACTIVE24_SECRET", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "active24: some credentials information are missing: ACTIVE24_API_KEY,ACTIVE24_SECRET", + }, + } + + 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 + secret string + expected string + }{ + { + desc: "success", + apiKey: "user", + secret: "secret", + }, + { + desc: "missing API key", + apiKey: "", + secret: "secret", + expected: "active24: credentials missing", + }, + { + desc: "missing secret", + apiKey: "user", + secret: "", + expected: "active24: credentials missing", + }, + { + desc: "missing credentials", + expected: "active24: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIKey = test.apiKey + config.Secret = test.secret + + 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/active24/internal/client.go b/providers/dns/active24/internal/client.go new file mode 100644 index 000000000..064cc61fb --- /dev/null +++ b/providers/dns/active24/internal/client.go @@ -0,0 +1,214 @@ +package internal + +import ( + "bytes" + "context" + "crypto/hmac" + "crypto/sha1" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const defaultBaseURL = "https://rest.active24.cz" + +// Client the Active24 API client. +type Client struct { + apiKey string + secret string + + baseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(apiKey, secret string) (*Client, error) { + if apiKey == "" || secret == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + apiKey: apiKey, + secret: secret, + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +// GetServices lists of all services. +// https://rest.active24.cz/docs/v1.service#services +func (c *Client) GetServices(ctx context.Context) ([]Service, error) { + endpoint := c.baseURL.JoinPath("v1", "user", "self", "service") + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var result OldAPIResponse + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result.Items, err +} + +// GetRecords lists of DNS records. +// https://rest.active24.cz/v2/docs#/DNS/rest.v2.dns.record_f94908d4e0e48489468498fce87cb90b +func (c *Client) GetRecords(ctx context.Context, service string, filter RecordFilter) ([]Record, error) { + endpoint := c.baseURL.JoinPath("v2", "service", service, "dns", "record") + + encodedFilter, err := json.Marshal(filter) + if err != nil { + return nil, fmt.Errorf("marshal records filter: %w", err) + } + + query := endpoint.Query() + query.Add("filters", string(encodedFilter)) + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var result APIResponse + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result.Data, err +} + +// CreateRecord creates a new DNS record. +// https://rest.active24.cz/v2/docs#/DNS/rest.v2.dns.create-record_6773d572235be9a72646bf6c54863573 +func (c *Client) CreateRecord(ctx context.Context, service string, record Record) error { + endpoint := c.baseURL.JoinPath("v2", "service", service, "dns", "record") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) + if err != nil { + return err + } + + return c.do(req, nil) +} + +// DeleteRecord deletes a DNS record. +// https://rest.active24.cz/v2/docs#/DNS/rest.v2.dns.delete-record_fc6603c14848e547f8d0b967842f0a2c +func (c *Client) DeleteRecord(ctx context.Context, service, recordID string) error { + endpoint := c.baseURL.JoinPath("v2", "service", service, "dns", "record", recordID) + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) do(req *http.Request, result any) error { + req.Header.Set("Accept-Language", "en_us") + + err := c.sign(req, time.Now()) + if err != nil { + return fmt.Errorf("sign request: %w", err) + } + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} + +// sign creates and sets request signature and date. +// https://rest.active24.cz/v2/docs/intro +func (c *Client) sign(req *http.Request, now time.Time) error { + if req.URL.Path == "" { + req.URL.Path += "/" + } + + canonicalRequest := fmt.Sprintf("%s %s %d", req.Method, req.URL.Path, now.Unix()) + + mac := hmac.New(sha1.New, []byte(c.secret)) + _, err := mac.Write([]byte(canonicalRequest)) + if err != nil { + return err + } + + hashed := mac.Sum(nil) + signature := hex.EncodeToString(hashed) + + req.SetBasicAuth(c.apiKey, signature) + + req.Header.Set("Date", now.Format(time.RFC3339)) + + return nil +} diff --git a/providers/dns/active24/internal/client_test.go b/providers/dns/active24/internal/client_test.go new file mode 100644 index 000000000..2e7359313 --- /dev/null +++ b/providers/dns/active24/internal/client_test.go @@ -0,0 +1,174 @@ +package internal + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupTest(t *testing.T, pattern string, status int, filename string) *Client { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { + if filename == "" { + rw.WriteHeader(status) + return + } + + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + defer func() { _ = file.Close() }() + + rw.WriteHeader(status) + _, err = io.Copy(rw, file) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + }) + + client, err := NewClient("user", "secret") + require.NoError(t, err) + + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) + + return client +} + +func TestClient_GetServices(t *testing.T) { + client := setupTest(t, "GET /v1/user/self/service", http.StatusOK, "services.json") + + services, err := client.GetServices(context.Background()) + require.NoError(t, err) + + expected := []Service{ + { + ID: 1111, + ServiceName: ".sk doména", + Status: "active", + Name: "mydomain.sk", + CreateTime: 1374357600, + ExpireTime: 1405914526, + Price: 12.3, + }, + { + ID: 2222, + ServiceName: "The Hosting", + Status: "active", + Name: "myname_1", + CreateTime: 1400145443, + ExpireTime: 1431702371, + Price: 55.2, + }, + } + + assert.Equal(t, expected, services) +} + +func TestClient_GetServices_errors(t *testing.T) { + client := setupTest(t, "GET /v1/user/self/service", http.StatusUnauthorized, "error_v1.json") + + _, err := client.GetServices(context.Background()) + require.EqualError(t, err, "401: No username or password.") +} + +func TestClient_GetRecords(t *testing.T) { + client := setupTest(t, "GET /v2/service/aaa/dns/record", http.StatusOK, "records.json") + + filter := RecordFilter{ + Name: "example.com", + Type: []string{"TXT"}, + Content: "txt", + } + + records, err := client.GetRecords(context.Background(), "aaa", filter) + require.NoError(t, err) + + expected := []Record{{ + ID: 13, + Name: "string", + Content: "string", + TTL: 120, + Priority: 1, + Port: 443, + Weight: 50, + }} + + assert.Equal(t, expected, records) +} + +func TestClient_GetRecords_errors(t *testing.T) { + client := setupTest(t, "GET /v2/service/aaa/dns/record", http.StatusForbidden, "error_403.json") + + filter := RecordFilter{ + Name: "example.com", + Type: []string{"TXT"}, + Content: "txt", + } + + _, err := client.GetRecords(context.Background(), "aaa", filter) + require.EqualError(t, err, "403: /errors/httpException: This action is unauthorized.") +} + +func TestClient_CreateRecord(t *testing.T) { + client := setupTest(t, "POST /v2/service/aaa/dns/record", http.StatusNoContent, "") + + err := client.CreateRecord(context.Background(), "aaa", Record{}) + require.NoError(t, err) +} + +func TestClient_CreateRecord_errors(t *testing.T) { + client := setupTest(t, "POST /v2/service/aaa/dns/record", http.StatusForbidden, "error_403.json") + + err := client.CreateRecord(context.Background(), "aaa", Record{}) + require.EqualError(t, err, "403: /errors/httpException: This action is unauthorized.") +} + +func TestClient_DeleteRecord(t *testing.T) { + client := setupTest(t, "DELETE /v2/service/aaa/dns/record/123", http.StatusNoContent, "") + + err := client.DeleteRecord(context.Background(), "aaa", "123") + require.NoError(t, err) +} + +func TestClient_DeleteRecord_error(t *testing.T) { + client := setupTest(t, "DELETE /v2/service/aaa/dns/record/123", http.StatusForbidden, "error_403.json") + + err := client.DeleteRecord(context.Background(), "aaa", "123") + require.EqualError(t, err, "403: /errors/httpException: This action is unauthorized.") +} + +func TestClient_sign(t *testing.T) { + client, err := NewClient("user", "secret") + require.NoError(t, err) + + req, err := http.NewRequest(http.MethodGet, "/v1/user/self/service", nil) + require.NoError(t, err) + + err = client.sign(req, time.Date(2025, 6, 28, 1, 2, 3, 4, time.UTC)) + require.NoError(t, err) + + username, password, ok := req.BasicAuth() + require.True(t, ok) + + assert.Equal(t, "user", username) + assert.Equal(t, "743e2257421b260ed561f3e7af4b035414636393", password) +} diff --git a/providers/dns/active24/internal/fixtures/error_403.json b/providers/dns/active24/internal/fixtures/error_403.json new file mode 100644 index 000000000..ee3ce196e --- /dev/null +++ b/providers/dns/active24/internal/fixtures/error_403.json @@ -0,0 +1,5 @@ +{ + "type": "/errors/httpException", + "status": 403, + "title": "This action is unauthorized." +} diff --git a/providers/dns/active24/internal/fixtures/error_422.json b/providers/dns/active24/internal/fixtures/error_422.json new file mode 100644 index 000000000..0864a1fce --- /dev/null +++ b/providers/dns/active24/internal/fixtures/error_422.json @@ -0,0 +1,16 @@ +{ + "type": "/errors/validation", + "status": 422, + "title": "The given data was invalid.", + "violations": [ + { + "propertyPath": "string", + "errors": [ + {} + ] + } + ], + "data": { + "name": "Merlin" + } +} diff --git a/providers/dns/active24/internal/fixtures/error_v1.json b/providers/dns/active24/internal/fixtures/error_v1.json new file mode 100644 index 000000000..8043412e5 --- /dev/null +++ b/providers/dns/active24/internal/fixtures/error_v1.json @@ -0,0 +1,4 @@ +{ + "message": "No username or password.", + "code": 401 +} diff --git a/providers/dns/active24/internal/fixtures/records.json b/providers/dns/active24/internal/fixtures/records.json new file mode 100644 index 000000000..bf07d9ef7 --- /dev/null +++ b/providers/dns/active24/internal/fixtures/records.json @@ -0,0 +1,28 @@ +{ + "currentPage": 0, + "rowsPerPage": 0, + "totalPages": 0, + "totalRecords": 0, + "actions": { + "additionalProp1": { + "additionalProp1": {} + }, + "additionalProp2": { + "additionalProp1": {} + }, + "additionalProp3": { + "additionalProp1": {} + } + }, + "data": [ + { + "id": 13, + "name": "string", + "content": "string", + "ttl": 120, + "priority": 1, + "port": 443, + "weight": 50 + } + ] +} diff --git a/providers/dns/active24/internal/fixtures/services.json b/providers/dns/active24/internal/fixtures/services.json new file mode 100644 index 000000000..ad9b28700 --- /dev/null +++ b/providers/dns/active24/internal/fixtures/services.json @@ -0,0 +1,31 @@ +{ + "items": + [ + { + "id": 1111, + "serviceName": ".sk doména", + "status": "active", + "name": "mydomain.sk", + "createTime": 1374357600, + "expireTime": 1405914526, + "price": 12.3, + "autoExtend": false + }, + { + "id": 2222, + "serviceName": "The Hosting", + "status": "active", + "name": "myname_1", + "createTime": 1400145443, + "expireTime": 1431702371, + "price": 55.2, + "autoExtend": false + } + ], + "pager": + { + "page": 1, + "pagesize": null, + "items": 2 + } +} diff --git a/providers/dns/active24/internal/types.go b/providers/dns/active24/internal/types.go new file mode 100644 index 000000000..ed8dfc9d3 --- /dev/null +++ b/providers/dns/active24/internal/types.go @@ -0,0 +1,65 @@ +package internal + +import "fmt" + +type APIError struct { + // v2 error + Type string `json:"type,omitempty"` + Status int `json:"status,omitempty"` + Title string `json:"title,omitempty"` + + // v1 error + Message string `json:"message,omitempty"` + Code int `json:"code,omitempty"` +} + +func (a *APIError) Error() string { + if a.Message != "" { + return fmt.Sprintf("%d: %s", a.Code, a.Message) + } + + return fmt.Sprintf("%d: %s: %s", a.Status, a.Type, a.Title) +} + +type APIResponse struct { + Data []Record `json:"data"` +} + +type Record struct { + ID int `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Content string `json:"content,omitempty"` + TTL int `json:"ttl,omitempty"` + Priority int `json:"priority,omitempty"` + Port int `json:"port,omitempty"` + Weight int `json:"weight,omitempty"` +} + +type OldAPIResponse struct { + Items []Service `json:"items"` +} + +type Service struct { + ID int `json:"id,omitempty"` + ServiceName string `json:"serviceName,omitempty"` + Status string `json:"status,omitempty"` + Name string `json:"name,omitempty"` + CreateTime int `json:"createTime,omitempty"` + ExpireTime int `json:"expireTime,omitempty"` + Price float64 `json:"price,omitempty"` + AutoExtend bool `json:"autoExtend,omitempty"` +} + +type RecordFilter struct { + Name string `json:"name,omitempty"` + Type []string `json:"type,omitempty"` + Content string `json:"content,omitempty"` + TTL int `json:"ttl,omitempty"` + Note string `json:"note,omitempty"` + Priority int `json:"priority,omitempty"` + Port int `json:"port,omitempty"` + Weight int `json:"weight,omitempty"` + Flags int `json:"flags,omitempty"` + Tag []string `json:"tag,omitempty"` +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index f52feaa25..9b3f70771 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -8,6 +8,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/providers/dns/acmedns" + "github.com/go-acme/lego/v4/providers/dns/active24" "github.com/go-acme/lego/v4/providers/dns/alidns" "github.com/go-acme/lego/v4/providers/dns/allinkl" "github.com/go-acme/lego/v4/providers/dns/arvancloud" @@ -165,6 +166,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return dns01.NewDNSProviderManual() case "acme-dns", "acmedns": return acmedns.NewDNSProvider() + case "active24": + return active24.NewDNSProvider() case "alidns": return alidns.NewDNSProvider() case "allinkl": From 46420fef71cac341eced08ca2a10329cd22a749f Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 11 Mar 2025 23:13:45 +0100 Subject: [PATCH 070/298] websupport: migrate to API v2 (#2479) --- docs/content/dns/zz_gen_websupport.md | 2 +- providers/dns/active24/active24.go | 12 +- providers/dns/active24/active24.toml | 1 + .../internal => internal/active24}/client.go | 8 +- .../active24}/client_test.go | 6 +- .../active24}/fixtures/error_403.json | 0 .../active24}/fixtures/error_422.json | 0 .../active24}/fixtures/error_v1.json | 0 .../active24}/fixtures/records.json | 0 .../active24}/fixtures/services.json | 0 .../internal => internal/active24}/types.go | 2 +- providers/dns/websupport/internal/client.go | 239 ------------------ .../dns/websupport/internal/client_test.go | 234 ----------------- .../fixtures/add-record-error-400.json | 26 -- .../fixtures/add-record-error-404.json | 4 - .../internal/fixtures/add-record.json | 19 -- .../fixtures/delete-record-error-404.json | 4 - .../internal/fixtures/delete-record.json | 19 -- .../internal/fixtures/get-record.json | 12 - .../internal/fixtures/get-user.json | 36 --- .../internal/fixtures/list-records.json | 29 --- providers/dns/websupport/internal/types.go | 121 --------- providers/dns/websupport/websupport.go | 135 ++++++---- providers/dns/websupport/websupport.toml | 3 +- providers/dns/websupport/websupport_test.go | 12 +- 25 files changed, 111 insertions(+), 813 deletions(-) rename providers/dns/{active24/internal => internal/active24}/client.go (96%) rename providers/dns/{active24/internal => internal/active24}/client_test.go (97%) rename providers/dns/{active24/internal => internal/active24}/fixtures/error_403.json (100%) rename providers/dns/{active24/internal => internal/active24}/fixtures/error_422.json (100%) rename providers/dns/{active24/internal => internal/active24}/fixtures/error_v1.json (100%) rename providers/dns/{active24/internal => internal/active24}/fixtures/records.json (100%) rename providers/dns/{active24/internal => internal/active24}/fixtures/services.json (100%) rename providers/dns/{active24/internal => internal/active24}/types.go (99%) delete mode 100644 providers/dns/websupport/internal/client.go delete mode 100644 providers/dns/websupport/internal/client_test.go delete mode 100644 providers/dns/websupport/internal/fixtures/add-record-error-400.json delete mode 100644 providers/dns/websupport/internal/fixtures/add-record-error-404.json delete mode 100644 providers/dns/websupport/internal/fixtures/add-record.json delete mode 100644 providers/dns/websupport/internal/fixtures/delete-record-error-404.json delete mode 100644 providers/dns/websupport/internal/fixtures/delete-record.json delete mode 100644 providers/dns/websupport/internal/fixtures/get-record.json delete mode 100644 providers/dns/websupport/internal/fixtures/get-user.json delete mode 100644 providers/dns/websupport/internal/fixtures/list-records.json delete mode 100644 providers/dns/websupport/internal/types.go diff --git a/docs/content/dns/zz_gen_websupport.md b/docs/content/dns/zz_gen_websupport.md index de3262112..5fe44a860 100644 --- a/docs/content/dns/zz_gen_websupport.md +++ b/docs/content/dns/zz_gen_websupport.md @@ -63,7 +63,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). ## More information -- [API documentation](https://rest.websupport.sk/docs/v1.zone) +- [API documentation](https://rest.websupport.sk/v2/docs) diff --git a/providers/dns/active24/active24.go b/providers/dns/active24/active24.go index 5f146d66e..1acd72f61 100644 --- a/providers/dns/active24/active24.go +++ b/providers/dns/active24/active24.go @@ -11,9 +11,11 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/active24/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/active24" ) +const baseAPIDomain = "active24.cz" + // Environment variables names. const ( envNamespace = "ACTIVE24_" @@ -53,7 +55,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { config *Config - client *internal.Client + client *active24.Client } // NewDNSProvider returns a DNSProvider instance configured for Active24. @@ -76,7 +78,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("active24: the configuration of the DNS provider is nil") } - client, err := internal.NewClient(config.APIKey, config.Secret) + client, err := active24.NewClient(baseAPIDomain, config.APIKey, config.Secret) if err != nil { return nil, fmt.Errorf("active24: %w", err) } @@ -112,7 +114,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("active24: find service ID: %w", err) } - record := internal.Record{ + record := active24.Record{ Type: "TXT", Name: subDomain, Content: info.Value, @@ -185,7 +187,7 @@ func (d *DNSProvider) findServiceID(ctx context.Context, domain string) (int, er func (d *DNSProvider) findRecordID(ctx context.Context, serviceID string, info dns01.ChallengeInfo) (int, error) { // NOTE(ldez): Despite the API documentation, the filter doesn't seem to work. - filter := internal.RecordFilter{ + filter := active24.RecordFilter{ Name: dns01.UnFqdn(info.EffectiveFQDN), Type: []string{"TXT"}, Content: info.Value, diff --git a/providers/dns/active24/active24.toml b/providers/dns/active24/active24.toml index a1bf03924..6a54d4695 100644 --- a/providers/dns/active24/active24.toml +++ b/providers/dns/active24/active24.toml @@ -22,3 +22,4 @@ lego --email you@example.com --dns active24 -d '*.example.com' -d example.com ru [Links] API = "https://rest.active24.cz/v2/docs" + APIv1 = "https://rest.active24.cz/docs/v1.service#services" diff --git a/providers/dns/active24/internal/client.go b/providers/dns/internal/active24/client.go similarity index 96% rename from providers/dns/active24/internal/client.go rename to providers/dns/internal/active24/client.go index 064cc61fb..32ecc2186 100644 --- a/providers/dns/active24/internal/client.go +++ b/providers/dns/internal/active24/client.go @@ -1,4 +1,4 @@ -package internal +package active24 import ( "bytes" @@ -17,7 +17,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) -const defaultBaseURL = "https://rest.active24.cz" +const defaultBaseURL = "https://rest.%s" // Client the Active24 API client. type Client struct { @@ -29,12 +29,12 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(apiKey, secret string) (*Client, error) { +func NewClient(baseAPIDomain, apiKey, secret string) (*Client, error) { if apiKey == "" || secret == "" { return nil, errors.New("credentials missing") } - baseURL, _ := url.Parse(defaultBaseURL) + baseURL, _ := url.Parse(fmt.Sprintf(defaultBaseURL, baseAPIDomain)) return &Client{ apiKey: apiKey, diff --git a/providers/dns/active24/internal/client_test.go b/providers/dns/internal/active24/client_test.go similarity index 97% rename from providers/dns/active24/internal/client_test.go rename to providers/dns/internal/active24/client_test.go index 2e7359313..9bbdebc8b 100644 --- a/providers/dns/active24/internal/client_test.go +++ b/providers/dns/internal/active24/client_test.go @@ -1,4 +1,4 @@ -package internal +package active24 import ( "context" @@ -44,7 +44,7 @@ func setupTest(t *testing.T, pattern string, status int, filename string) *Clien } }) - client, err := NewClient("user", "secret") + client, err := NewClient("example.com", "user", "secret") require.NoError(t, err) client.HTTPClient = server.Client() @@ -157,7 +157,7 @@ func TestClient_DeleteRecord_error(t *testing.T) { } func TestClient_sign(t *testing.T) { - client, err := NewClient("user", "secret") + client, err := NewClient("example.com", "user", "secret") require.NoError(t, err) req, err := http.NewRequest(http.MethodGet, "/v1/user/self/service", nil) diff --git a/providers/dns/active24/internal/fixtures/error_403.json b/providers/dns/internal/active24/fixtures/error_403.json similarity index 100% rename from providers/dns/active24/internal/fixtures/error_403.json rename to providers/dns/internal/active24/fixtures/error_403.json diff --git a/providers/dns/active24/internal/fixtures/error_422.json b/providers/dns/internal/active24/fixtures/error_422.json similarity index 100% rename from providers/dns/active24/internal/fixtures/error_422.json rename to providers/dns/internal/active24/fixtures/error_422.json diff --git a/providers/dns/active24/internal/fixtures/error_v1.json b/providers/dns/internal/active24/fixtures/error_v1.json similarity index 100% rename from providers/dns/active24/internal/fixtures/error_v1.json rename to providers/dns/internal/active24/fixtures/error_v1.json diff --git a/providers/dns/active24/internal/fixtures/records.json b/providers/dns/internal/active24/fixtures/records.json similarity index 100% rename from providers/dns/active24/internal/fixtures/records.json rename to providers/dns/internal/active24/fixtures/records.json diff --git a/providers/dns/active24/internal/fixtures/services.json b/providers/dns/internal/active24/fixtures/services.json similarity index 100% rename from providers/dns/active24/internal/fixtures/services.json rename to providers/dns/internal/active24/fixtures/services.json diff --git a/providers/dns/active24/internal/types.go b/providers/dns/internal/active24/types.go similarity index 99% rename from providers/dns/active24/internal/types.go rename to providers/dns/internal/active24/types.go index ed8dfc9d3..b9a7ea427 100644 --- a/providers/dns/active24/internal/types.go +++ b/providers/dns/internal/active24/types.go @@ -1,4 +1,4 @@ -package internal +package active24 import "fmt" diff --git a/providers/dns/websupport/internal/client.go b/providers/dns/websupport/internal/client.go deleted file mode 100644 index 4fef0be91..000000000 --- a/providers/dns/websupport/internal/client.go +++ /dev/null @@ -1,239 +0,0 @@ -package internal - -import ( - "bytes" - "context" - "crypto/hmac" - "crypto/sha1" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "time" - - "github.com/go-acme/lego/v4/providers/dns/internal/errutils" -) - -const defaultBaseURL = "https://rest.websupport.sk" - -// StatusSuccess expected status text when success. -const StatusSuccess = "success" - -// Client a Websupport DNS API client. -type Client struct { - apiKey string - secretKey string - - baseURL *url.URL - HTTPClient *http.Client -} - -// NewClient creates a new Client. -func NewClient(apiKey, secretKey string) (*Client, error) { - if apiKey == "" || secretKey == "" { - return nil, errors.New("credentials missing") - } - - baseURL, _ := url.Parse(defaultBaseURL) - - return &Client{ - apiKey: apiKey, - secretKey: secretKey, - baseURL: baseURL, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - }, nil -} - -// GetUser gets a user detail. -// https://rest.websupport.sk/docs/v1.user#user -func (c *Client) GetUser(ctx context.Context, userID string) (*User, error) { - endpoint := c.baseURL.JoinPath("v1", "user", userID) - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, fmt.Errorf("request payload: %w", err) - } - - result := &User{} - - err = c.do(req, result) - if err != nil { - return nil, err - } - - return result, nil -} - -// ListRecords lists all records. -// https://rest.websupport.sk/docs/v1.zone#records -func (c *Client) ListRecords(ctx context.Context, domainName string) (*ListResponse, error) { - endpoint := c.baseURL.JoinPath("v1", "user", "self", "zone", domainName, "record") - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, fmt.Errorf("request payload: %w", err) - } - - result := &ListResponse{} - - err = c.do(req, result) - if err != nil { - return nil, err - } - - return result, nil -} - -// GetRecords gets a DNS record. -func (c *Client) GetRecords(ctx context.Context, domainName string, recordID int) (*Record, error) { - endpoint := c.baseURL.JoinPath("v1", "user", "self", "zone", domainName, "record", strconv.Itoa(recordID)) - - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) - if err != nil { - return nil, err - } - - result := &Record{} - - err = c.do(req, result) - if err != nil { - return nil, err - } - - return result, nil -} - -// AddRecord adds a DNS record. -// https://rest.websupport.sk/docs/v1.zone#post-record -func (c *Client) AddRecord(ctx context.Context, domainName string, record Record) (*Response, error) { - endpoint := c.baseURL.JoinPath("v1", "user", "self", "zone", domainName, "record") - - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) - if err != nil { - return nil, fmt.Errorf("create request: %w", err) - } - - result := &Response{} - - err = c.do(req, result) - if err != nil { - return nil, err - } - - return result, nil -} - -// DeleteRecord deletes a DNS record. -// https://rest.websupport.sk/docs/v1.zone#delete-record -func (c *Client) DeleteRecord(ctx context.Context, domainName string, recordID int) (*Response, error) { - endpoint := c.baseURL.JoinPath("v1", "user", "self", "zone", domainName, "record", strconv.Itoa(recordID)) - - req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) - if err != nil { - return nil, fmt.Errorf("create request: %w", err) - } - - result := &Response{} - - err = c.do(req, result) - if err != nil { - return nil, err - } - - return result, nil -} - -func (c *Client) do(req *http.Request, result any) error { - req.Header.Set("Accept-Language", "en_us") - - err := c.sign(req, time.Now().UTC()) - if err != nil { - return fmt.Errorf("signature: %w", err) - } - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode > http.StatusBadRequest { - return parseError(req, resp) - } - - raw, err := io.ReadAll(resp.Body) - if err != nil { - return errutils.NewReadResponseError(req, resp.StatusCode, err) - } - - err = json.Unmarshal(raw, result) - if err != nil { - return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) - } - - return nil -} - -func (c *Client) sign(req *http.Request, now time.Time) error { - if req.URL.Path == "" { - req.URL.Path += "/" - } - - canonicalRequest := fmt.Sprintf("%s %s %d", req.Method, req.URL.Path, now.Unix()) - - mac := hmac.New(sha1.New, []byte(c.secretKey)) - _, err := mac.Write([]byte(canonicalRequest)) - if err != nil { - return err - } - - hashed := mac.Sum(nil) - signature := hex.EncodeToString(hashed) - - req.SetBasicAuth(c.apiKey, signature) - - req.Header.Set("Date", now.Format(time.RFC3339)) - - return nil -} - -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { - buf := new(bytes.Buffer) - - if payload != nil { - err := json.NewEncoder(buf).Encode(payload) - if err != nil { - return nil, fmt.Errorf("failed to create request JSON body: %w", err) - } - } - - req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) - if err != nil { - return nil, fmt.Errorf("unable to create request: %w", err) - } - - req.Header.Set("Accept", "application/json") - - if payload != nil { - req.Header.Set("Content-Type", "application/json") - } - - return req, nil -} - -func parseError(req *http.Request, resp *http.Response) error { - raw, _ := io.ReadAll(resp.Body) - - var errAPI APIError - err := json.Unmarshal(raw, &errAPI) - if err != nil { - return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) - } - - return &errAPI -} diff --git a/providers/dns/websupport/internal/client_test.go b/providers/dns/websupport/internal/client_test.go deleted file mode 100644 index 9612f6096..000000000 --- a/providers/dns/websupport/internal/client_test.go +++ /dev/null @@ -1,234 +0,0 @@ -package internal - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "net/url" - "os" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func setupTest(t *testing.T, method, pattern string, status int, file string) *Client { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - open, err := os.Open(file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client, err := NewClient("apiKey", "secretKey") - require.NoError(t, err) - - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client -} - -func TestClient_GetUser(t *testing.T) { - client := setupTest(t, http.MethodGet, "/v1/user/self", http.StatusOK, "./fixtures/get-user.json") - - user, err := client.GetUser(context.Background(), "self") - require.NoError(t, err) - - expected := &User{ - ID: 987654321, - Login: "lego@example.com", - Active: true, - CreateTime: 1675237889, - Group: "users", - Email: "lego@example.com", - Phone: "+123456789", - ContactPerson: "", - AwaitingTosConfirmation: "1", - UserLanguage: "sk-SK", - Credit: 0, - VerifyURL: "https://rest.websupport.sk/v1/user/verify/key/xxx", - Billing: []Billing{{ - ID: 1099970, - Profile: "default", - IsDefault: true, - Name: "asdsdfs", - City: "Žilina", - Street: "asddfsdfsdf", - Zip: "01234", - Country: "sk", - }}, - Market: Market{Name: "Slovakia", Identifier: "sk", Currency: "EUR"}, - } - - assert.Equal(t, expected, user) -} - -func TestClient_ListRecords(t *testing.T) { - client := setupTest(t, http.MethodGet, "/v1/user/self/zone/example.com/record", http.StatusOK, "./fixtures/list-records.json") - - resp, err := client.ListRecords(context.Background(), "example.com") - require.NoError(t, err) - - expected := &ListResponse{ - Items: []Record{ - { - ID: 1, - Type: "A", - Name: "@", - Content: "37.9.169.99", - TTL: 600, - }, { - ID: 2, - Type: "NS", - Name: "@", - Content: "ns1.scaledo.com", - TTL: 600, - }, - }, - Pager: Pager{Page: 1, PageSize: 0, Items: 2}, - } - - assert.Equal(t, expected, resp) -} - -func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, http.MethodPost, "/v1/user/self/zone/example.com/record", http.StatusCreated, "./fixtures/add-record.json") - - record := Record{ - Type: "TXT", - Name: "_acme-challenge", - Content: "txttxttxt", - TTL: 600, - } - - resp, err := client.AddRecord(context.Background(), "example.com", record) - require.NoError(t, err) - - expected := &Response{ - Status: "success", - Item: &Record{ - ID: 4, - Type: "A", - Name: "@", - Content: "1.2.3.4", - TTL: 600, - Zone: &Zone{ - ID: 1, - Name: "example.com", - UpdateTime: 1381169608, - }, - }, - Errors: json.RawMessage("[]"), - } - - assert.Equal(t, expected, resp) -} - -func TestClient_AddRecord_error_400(t *testing.T) { - client := setupTest(t, http.MethodPost, "/v1/user/self/zone/example.com/record", http.StatusBadRequest, "./fixtures/add-record-error-400.json") - - record := Record{ - Type: "TXT", - Name: "_acme-challenge", - Content: "txttxttxt", - TTL: 600, - } - - resp, err := client.AddRecord(context.Background(), "example.com", record) - require.NoError(t, err) - - assert.Equal(t, "error", resp.Status) - - expectedRecord := &Record{ - ID: 0, - Type: "A", - Name: "something bad !@#$%^&*(", - Content: "123.456.789.123", - TTL: 600, - Zone: &Zone{ - ID: 1, - Name: "scaledo.com", - UpdateTime: 1381169608, - }, - } - assert.Equal(t, expectedRecord, resp.Item) - - expected := &Errors{Name: []string{"Invalid input."}, Content: []string{"Wrong IP address format"}} - assert.Equal(t, expected, ParseError(resp)) -} - -func TestClient_AddRecord_error_404(t *testing.T) { - client := setupTest(t, http.MethodPost, "/v1/user/self/zone/example.com/record", http.StatusNotFound, "./fixtures/add-record-error-404.json") - - record := Record{ - Type: "TXT", - Name: "_acme-challenge", - Content: "txttxttxt", - TTL: 600, - } - - resp, err := client.AddRecord(context.Background(), "example.com", record) - require.Error(t, err) - - assert.Nil(t, resp) -} - -func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, http.MethodDelete, "/v1/user/self/zone/example.com/record/123", http.StatusOK, "./fixtures/delete-record.json") - - resp, err := client.DeleteRecord(context.Background(), "example.com", 123) - require.NoError(t, err) - - expected := &Response{ - Status: "success", - Item: &Record{ - ID: 1, - Type: "A", - Name: "@", - Content: "1.2.3.4", - TTL: 600, - Zone: &Zone{ - ID: 1, - Name: "scaledo.com", - UpdateTime: 1381316081, - }, - }, - Errors: json.RawMessage("[]"), - } - - assert.Equal(t, expected, resp) -} - -func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, http.MethodDelete, "/v1/user/self/zone/example.com/record/123", http.StatusNotFound, "./fixtures/delete-record-error-404.json") - - resp, err := client.DeleteRecord(context.Background(), "example.com", 123) - require.Error(t, err) - - assert.Nil(t, resp) -} diff --git a/providers/dns/websupport/internal/fixtures/add-record-error-400.json b/providers/dns/websupport/internal/fixtures/add-record-error-400.json deleted file mode 100644 index b60b7989a..000000000 --- a/providers/dns/websupport/internal/fixtures/add-record-error-400.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "status": "error", - "item": { - "id": null, - "type": "A", - "name": "something bad !@#$%^&*(", - "content": "123.456.789.123", - "ttl": 600, - "prio": null, - "weight": null, - "port": null, - "zone": { - "id": 1, - "name": "scaledo.com", - "updateTime": 1381169608 - } - }, - "errors": { - "content": [ - "Wrong IP address format" - ], - "name": [ - "Invalid input." - ] - } -} diff --git a/providers/dns/websupport/internal/fixtures/add-record-error-404.json b/providers/dns/websupport/internal/fixtures/add-record-error-404.json deleted file mode 100644 index 837b5392a..000000000 --- a/providers/dns/websupport/internal/fixtures/add-record-error-404.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "code": 404, - "message": "Zone not found" -} diff --git a/providers/dns/websupport/internal/fixtures/add-record.json b/providers/dns/websupport/internal/fixtures/add-record.json deleted file mode 100644 index 5990cf3d3..000000000 --- a/providers/dns/websupport/internal/fixtures/add-record.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "status": "success", - "item": { - "id": 4, - "type": "A", - "name": "@", - "content": "1.2.3.4", - "ttl": 600, - "prio": null, - "weight": null, - "port": null, - "zone": { - "id": 1, - "name": "example.com", - "updateTime": 1381169608 - } - }, - "errors": [] -} diff --git a/providers/dns/websupport/internal/fixtures/delete-record-error-404.json b/providers/dns/websupport/internal/fixtures/delete-record-error-404.json deleted file mode 100644 index e66fa5dc6..000000000 --- a/providers/dns/websupport/internal/fixtures/delete-record-error-404.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "code": 404, - "message": "Record not found" -} diff --git a/providers/dns/websupport/internal/fixtures/delete-record.json b/providers/dns/websupport/internal/fixtures/delete-record.json deleted file mode 100644 index 8fdff82cb..000000000 --- a/providers/dns/websupport/internal/fixtures/delete-record.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "status": "success", - "item": { - "id": 1, - "type": "A", - "name": "@", - "content": "1.2.3.4", - "ttl": 600, - "prio": null, - "weight": null, - "port": null, - "zone": { - "id": 1, - "name": "scaledo.com", - "updateTime": 1381316081 - } - }, - "errors": [] -} diff --git a/providers/dns/websupport/internal/fixtures/get-record.json b/providers/dns/websupport/internal/fixtures/get-record.json deleted file mode 100644 index d1bd2f137..000000000 --- a/providers/dns/websupport/internal/fixtures/get-record.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "id": 69966832, - "type": "TXT", - "name": "_acme-challenge", - "content": "txttxttxt", - "ttl": 600, - "zone": { - "id": 0, - "name": "example.com", - "updateTime": 1675240207 - } -} diff --git a/providers/dns/websupport/internal/fixtures/get-user.json b/providers/dns/websupport/internal/fixtures/get-user.json deleted file mode 100644 index ad4978755..000000000 --- a/providers/dns/websupport/internal/fixtures/get-user.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "id": 987654321, - "login": "lego@example.com", - "parentId": null, - "active": true, - "createTime": 1675237889, - "group": "users", - "email": "lego@example.com", - "phone": "+123456789", - "contactPerson": "", - "awaitingTosConfirmation": "1", - "userLanguage": "sk-SK", - "credit": 0, - "verifyUrl": "https:\/\/rest.websupport.sk\/v1\/user\/verify\/key\/xxx", - "billing": [ - { - "id": 1099970, - "profile": "default", - "isDefault": true, - "name": "asdsdfs", - "city": "\u017dilina", - "street": "asddfsdfsdf", - "companyRegId": null, - "taxId": null, - "vatId": null, - "zip": "01234", - "country": "sk", - "isic": "" - } - ], - "market": { - "name": "Slovakia", - "identifier": "sk", - "currency": "EUR" - } -} diff --git a/providers/dns/websupport/internal/fixtures/list-records.json b/providers/dns/websupport/internal/fixtures/list-records.json deleted file mode 100644 index d0ad57dc9..000000000 --- a/providers/dns/websupport/internal/fixtures/list-records.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "items": [ - { - "id": 1, - "type": "A", - "name": "@", - "content": "37.9.169.99", - "ttl": 600, - "prio": null, - "weight": null, - "port": null - }, - { - "id": 2, - "type": "NS", - "name": "@", - "content": "ns1.scaledo.com", - "ttl": 600, - "prio": null, - "weight": null, - "port": null - } - ], - "pager": { - "page": 1, - "pagesize": 0, - "items": 2 - } -} diff --git a/providers/dns/websupport/internal/types.go b/providers/dns/websupport/internal/types.go deleted file mode 100644 index 0923282aa..000000000 --- a/providers/dns/websupport/internal/types.go +++ /dev/null @@ -1,121 +0,0 @@ -package internal - -import ( - "encoding/json" - "fmt" -) - -type APIError struct { - Code int `json:"code"` - Message string `json:"message"` -} - -func (a *APIError) Error() string { - return fmt.Sprintf("%d: %s", a.Code, a.Message) -} - -type Record struct { - ID int `json:"id,omitempty"` - Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` // subdomain name or @ if you don't want subdomain - Content string `json:"content,omitempty"` - TTL int `json:"ttl,omitempty"` // default 600 - Zone *Zone `json:"zone"` -} - -type Zone struct { - ID int `json:"id"` - Name string `json:"name"` - UpdateTime int `json:"updateTime"` -} - -type Response struct { - Item *Record `json:"item"` - Status string `json:"status"` - Errors json.RawMessage `json:"errors"` -} - -type ListResponse struct { - Items []Record `json:"items"` - Pager Pager `json:"pager"` -} - -type Pager struct { - Page int `json:"page"` - PageSize int `json:"pagesize"` - Items int `json:"items"` -} - -type Errors struct { - Name []string `json:"name"` - Content []string `json:"content"` -} - -func (e *Errors) Error() string { - var msg string - for i, s := range e.Name { - msg += s - if i != len(e.Name)-1 { - msg += ": " - } - } - - for i, s := range e.Content { - msg += s - if i != len(e.Content)-1 { - msg += ": " - } - } - - return msg -} - -// ParseError extract error from Response. -func ParseError(resp *Response) error { - var errAPI Errors - err := json.Unmarshal(resp.Errors, &errAPI) - if err != nil { - return err - } - - return &errAPI -} - -type User struct { - ID int `json:"id"` - Login string `json:"login"` - ParentID int `json:"parentId"` - Active bool `json:"active"` - CreateTime int `json:"createTime"` - Group string `json:"group"` - Email string `json:"email"` - Phone string `json:"phone"` - ContactPerson string `json:"contactPerson"` - AwaitingTosConfirmation string `json:"awaitingTosConfirmation"` - UserLanguage string `json:"userLanguage"` - Credit int `json:"credit"` - VerifyURL string `json:"verifyUrl"` - Billing []Billing `json:"billing"` - Market Market `json:"market"` -} - -type Billing struct { - ID int `json:"id"` - Profile string `json:"profile"` - IsDefault bool `json:"isDefault"` - Name string `json:"name"` - City string `json:"city"` - Street string `json:"street"` - CompanyRegID int `json:"companyRegId"` - TaxID int `json:"taxId"` - VatID int `json:"vatId"` - Zip string `json:"zip"` - Country string `json:"country"` - ISIC string `json:"isic"` -} - -type Market struct { - Name string `json:"name"` - Identifier string `json:"identifier"` - Currency string `json:"currency"` -} diff --git a/providers/dns/websupport/websupport.go b/providers/dns/websupport/websupport.go index db31315d8..aa3c93578 100644 --- a/providers/dns/websupport/websupport.go +++ b/providers/dns/websupport/websupport.go @@ -6,15 +6,16 @@ import ( "errors" "fmt" "net/http" - "sync" + "strconv" "time" - "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/websupport/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/active24" ) +const baseAPIDomain = "websupport.sk" + // Environment variables names. const ( envNamespace = "WEBSUPPORT_" @@ -26,11 +27,8 @@ const ( EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" - EnvSequenceInterval = envNamespace + "SEQUENCE_INTERVAL" ) -var _ challenge.ProviderTimeout = (*DNSProvider)(nil) - // Config is used to configure the creation of the DNSProvider. type Config struct { APIKey string @@ -38,7 +36,6 @@ type Config struct { PropagationTimeout time.Duration PollingInterval time.Duration - SequenceInterval time.Duration TTL int HTTPClient *http.Client } @@ -46,10 +43,9 @@ type Config struct { // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, 600), + TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - SequenceInterval: env.GetOrDefaultSecond(EnvSequenceInterval, dns01.DefaultPropagationTimeout), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), }, @@ -59,10 +55,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { config *Config - client *internal.Client - - recordIDs map[string]int - recordIDsMu sync.Mutex + client *active24.Client } // NewDNSProvider returns a DNSProvider instance configured for Websupport. @@ -86,7 +79,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("websupport: the configuration of the DNS provider is nil") } - client, err := internal.NewClient(config.APIKey, config.Secret) + client, err := active24.NewClient(baseAPIDomain, config.APIKey, config.Secret) if err != nil { return nil, fmt.Errorf("websupport: %w", err) } @@ -96,14 +89,15 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } return &DNSProvider{ - config: config, - client: client, - recordIDs: make(map[string]int), + config: config, + client: client, }, nil } // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) @@ -116,31 +110,30 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("websupport: %w", err) } - record := internal.Record{ + serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("websupport: find service ID: %w", err) + } + + record := active24.Record{ Type: "TXT", Name: subDomain, Content: info.Value, TTL: d.config.TTL, } - resp, err := d.client.AddRecord(context.Background(), dns01.UnFqdn(authZone), record) + err = d.client.CreateRecord(ctx, strconv.Itoa(serviceID), record) if err != nil { - return fmt.Errorf("websupport: add record: %w", err) + return fmt.Errorf("websupport: create record: %w", err) } - if resp.Status == internal.StatusSuccess { - d.recordIDsMu.Lock() - d.recordIDs[token] = resp.Item.ID - d.recordIDsMu.Unlock() - - return nil - } - - return fmt.Errorf("websupport: %w", internal.ParseError(resp)) + return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) @@ -148,29 +141,22 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("websupport: could not find zone for domain %q: %w", domain, err) } - // gets the record's unique ID - d.recordIDsMu.Lock() - recordID, ok := d.recordIDs[token] - d.recordIDsMu.Unlock() - if !ok { - return fmt.Errorf("websupport: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) - } - - resp, err := d.client.DeleteRecord(context.Background(), dns01.UnFqdn(authZone), recordID) + serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) if err != nil { - return fmt.Errorf("websupport: delete record: %w", err) + return fmt.Errorf("websupport: find service ID: %w", err) } - // deletes record ID from map - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - - if resp.Status == internal.StatusSuccess { - return nil + recordID, err := d.findRecordID(ctx, strconv.Itoa(serviceID), info) + if err != nil { + return fmt.Errorf("websupport: find record ID: %w", err) } - return fmt.Errorf("websupport: %w", internal.ParseError(resp)) + err = d.client.DeleteRecord(ctx, strconv.Itoa(serviceID), strconv.Itoa(recordID)) + if err != nil { + return fmt.Errorf("websupport: delete record %w", err) + } + + return nil } // Timeout returns the timeout and interval to use when checking for DNS propagation. @@ -179,8 +165,55 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -// Sequential All DNS challenges for this provider will be resolved sequentially. -// Returns the interval between each iteration. -func (d *DNSProvider) Sequential() time.Duration { - return d.config.SequenceInterval +func (d *DNSProvider) findServiceID(ctx context.Context, domain string) (int, error) { + services, err := d.client.GetServices(ctx) + if err != nil { + return 0, fmt.Errorf("get services: %w", err) + } + + for _, service := range services { + if service.ServiceName != "domain" { + continue + } + + if service.Name != domain { + continue + } + + return service.ID, nil + } + + return 0, fmt.Errorf("service not found for domain: %s", domain) +} + +func (d *DNSProvider) findRecordID(ctx context.Context, serviceID string, info dns01.ChallengeInfo) (int, error) { + // NOTE(ldez): Despite the API documentation, the filter doesn't seem to work. + filter := active24.RecordFilter{ + Name: dns01.UnFqdn(info.EffectiveFQDN), + Type: []string{"TXT"}, + Content: info.Value, + } + + records, err := d.client.GetRecords(ctx, serviceID, filter) + if err != nil { + return 0, fmt.Errorf("get records: %w", err) + } + + for _, record := range records { + if record.Type != "TXT" { + continue + } + + if record.Name != dns01.UnFqdn(info.EffectiveFQDN) { + continue + } + + if record.Content != info.Value { + continue + } + + return record.ID, nil + } + + return 0, errors.New("no record found") } diff --git a/providers/dns/websupport/websupport.toml b/providers/dns/websupport/websupport.toml index 262df22b6..1f34b431b 100644 --- a/providers/dns/websupport/websupport.toml +++ b/providers/dns/websupport/websupport.toml @@ -22,4 +22,5 @@ lego --email you@example.com --dns websupport -d '*.example.com' -d example.com WEBSUPPORT_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] - API = "https://rest.websupport.sk/docs/v1.zone" + API = "https://rest.websupport.sk/v2/docs" + APIv1 = "https://rest.websupport.sk/docs/v1.service#services" diff --git a/providers/dns/websupport/websupport_test.go b/providers/dns/websupport/websupport_test.go index e79dd7130..051fdb837 100644 --- a/providers/dns/websupport/websupport_test.go +++ b/providers/dns/websupport/websupport_test.go @@ -20,13 +20,14 @@ func TestNewDNSProvider(t *testing.T) { { desc: "success", envVars: map[string]string{ - EnvAPIKey: "key", + EnvAPIKey: "user", EnvSecret: "secret", }, }, { desc: "missing API key", envVars: map[string]string{ + EnvAPIKey: "", EnvSecret: "secret", }, expected: "websupport: some credentials information are missing: WEBSUPPORT_API_KEY", @@ -34,7 +35,8 @@ func TestNewDNSProvider(t *testing.T) { { desc: "missing secret", envVars: map[string]string{ - EnvAPIKey: "key", + EnvAPIKey: "user", + EnvSecret: "", }, expected: "websupport: some credentials information are missing: WEBSUPPORT_SECRET", }, @@ -75,17 +77,19 @@ func TestNewDNSProviderConfig(t *testing.T) { }{ { desc: "success", - apiKey: "key", + apiKey: "user", secret: "secret", }, { desc: "missing API key", + apiKey: "", secret: "secret", expected: "websupport: credentials missing", }, { desc: "missing secret", - apiKey: "key", + apiKey: "user", + secret: "", expected: "websupport: credentials missing", }, { From a8693c1aead8a156429046e3b57fb3e19e6c4c72 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 12 Mar 2025 20:31:00 +0100 Subject: [PATCH 071/298] fix: retry on alreadyReplaced error (#2475) --- acme/api/internal/sender/sender.go | 4 ++++ acme/api/order.go | 15 ++++++++++++++- acme/api/order_test.go | 2 ++ acme/errors.go | 12 ++++++++++-- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/sender.go b/acme/api/internal/sender/sender.go index 29cd7c9be..042a1318a 100644 --- a/acme/api/internal/sender/sender.go +++ b/acme/api/internal/sender/sender.go @@ -142,6 +142,10 @@ func checkError(req *http.Request, resp *http.Response) error { return &acme.NonceError{ProblemDetails: errorDetails} } + if errorDetails.HTTPStatus == http.StatusConflict && errorDetails.Type == acme.AlreadyReplacedErr { + return &acme.AlreadyReplacedError{ProblemDetails: errorDetails} + } + return errorDetails } return nil diff --git a/acme/api/order.go b/acme/api/order.go index befcb541d..4d310e040 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -69,7 +69,20 @@ func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acm var order acme.Order resp, err := o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order) if err != nil { - return acme.ExtendedOrder{}, err + are := &acme.AlreadyReplacedError{} + if !errors.As(err, &are) { + return acme.ExtendedOrder{}, err + } + + // If the Server rejects the request because the identified certificate has already been marked as replaced, + // it MUST return an HTTP 409 (Conflict) with a problem document of type "alreadyReplaced" (see Section 7.4). + // https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-08#section-5 + orderReq.Replaces = "" + + resp, err = o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order) + if err != nil { + return acme.ExtendedOrder{}, err + } } return acme.ExtendedOrder{ diff --git a/acme/api/order_test.go b/acme/api/order_test.go index 3cfe79a76..cf28e517b 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -46,12 +46,14 @@ func TestOrderService_NewWithOptions(t *testing.T) { Status: acme.StatusValid, Expires: order.Expires, Identifiers: order.Identifiers, + Profile: order.Profile, NotBefore: order.NotBefore, NotAfter: order.NotAfter, Error: order.Error, Authorizations: order.Authorizations, Finalize: order.Finalize, Certificate: order.Certificate, + Replaces: order.Replaces, }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/acme/errors.go b/acme/errors.go index 6cd74bfb8..a9d6353d0 100644 --- a/acme/errors.go +++ b/acme/errors.go @@ -6,8 +6,9 @@ import ( // Errors types. const ( - errNS = "urn:ietf:params:acme:error:" - BadNonceErr = errNS + "badNonce" + errNS = "urn:ietf:params:acme:error:" + BadNonceErr = errNS + "badNonce" + AlreadyReplacedErr = errNS + "alreadyReplaced" ) // ProblemDetails the problem details object. @@ -56,3 +57,10 @@ type SubProblem struct { type NonceError struct { *ProblemDetails } + +// AlreadyReplacedError represents the error which is returned +// If the Server rejects the request because the identified certificate has already been marked as replaced. +// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-08#section-5 +type AlreadyReplacedError struct { + *ProblemDetails +} From 51aaf75afb3e75cf386502a9ee70da31096f0ef0 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 15 Mar 2025 13:08:01 +0100 Subject: [PATCH 072/298] tests: change ns (#2482) --- challenge/dns01/nameserver_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/challenge/dns01/nameserver_test.go b/challenge/dns01/nameserver_test.go index 15b19beba..0e66c2fd1 100644 --- a/challenge/dns01/nameserver_test.go +++ b/challenge/dns01/nameserver_test.go @@ -24,8 +24,8 @@ func TestLookupNameserversOK(t *testing.T) { nss: []string{"ns1.google.com.", "ns2.google.com.", "ns3.google.com.", "ns4.google.com."}, }, { - fqdn: "physics.georgetown.edu.", - nss: []string{"ns4.georgetown.edu.", "ns5.georgetown.edu.", "ns6.georgetown.edu."}, + fqdn: "mail.proton.me.", + nss: []string{"ns1.proton.me.", "ns2.proton.me.", "ns3.proton.me."}, }, } From eb48c607ad38bf27e248921783d66d4d0d817c1a Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Sat, 15 Mar 2025 08:26:09 -0400 Subject: [PATCH 073/298] tests: compare RSA priv keys ignoring precomputed (#2481) --- certcrypto/crypto_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/certcrypto/crypto_test.go b/certcrypto/crypto_test.go index 118c19389..0b0ce8f6f 100644 --- a/certcrypto/crypto_test.go +++ b/certcrypto/crypto_test.go @@ -168,10 +168,12 @@ func TestParsePEMPrivateKey(t *testing.T) { pemPrivateKey := PEMEncode(privateKey) - // Decoding a key should work and create an identical key to the original + // Decoding a key should work and create an identical RSA key to the original, + // ignoring precomputed values. decoded, err := ParsePEMPrivateKey(pemPrivateKey) require.NoError(t, err) - assert.Equal(t, decoded, privateKey) + decodedRsaPrivateKey := decoded.(*rsa.PrivateKey) + require.True(t, decodedRsaPrivateKey.Equal(privateKey)) // Decoding a PEM block that doesn't contain a private key should error _, err = ParsePEMPrivateKey(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE"})) From 937f83c92c13a780e54cb9a0a9b66d447ab12649 Mon Sep 17 00:00:00 2001 From: Crys <1208344+huncrys@users.noreply.github.com> Date: Sun, 16 Mar 2025 00:41:35 +0100 Subject: [PATCH 074/298] chore: fix feature request template (#2485) --- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 78b52dbf0..b29c0d9f5 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -33,7 +33,7 @@ body: id: version attributes: label: Effective version of lego - description: `latest` or `dev` are not effective versions. + description: "`latest` or `dev` are not effective versions." validations: required: true From e57af854f1b93995af1561d991c9dab2dab10020 Mon Sep 17 00:00:00 2001 From: Crys <1208344+huncrys@users.noreply.github.com> Date: Mon, 17 Mar 2025 22:02:45 +0100 Subject: [PATCH 075/298] cloudflare: make base URL configurable (#2484) Co-authored-by: Fernandez Ludovic --- cmd/zz_gen_cmd_dnshelp.go | 1 + docs/content/dns/zz_gen_cloudflare.md | 1 + providers/dns/cloudflare/cloudflare.go | 10 ++++++++-- providers/dns/cloudflare/cloudflare.toml | 1 + providers/dns/cloudflare/cloudflare_test.go | 1 + providers/dns/cloudflare/wrapper.go | 11 ++++++++--- 6 files changed, 20 insertions(+), 5 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 2e8822501..258ec0f65 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -564,6 +564,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) + ew.writeln(` - "CLOUDFLARE_BASE_URL": API base URL (Default: https://api.cloudflare.com/client/v4)`) ew.writeln(` - "CLOUDFLARE_HTTP_TIMEOUT": API request timeout in seconds (Default: )`) ew.writeln(` - "CLOUDFLARE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "CLOUDFLARE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) diff --git a/docs/content/dns/zz_gen_cloudflare.md b/docs/content/dns/zz_gen_cloudflare.md index 68317c632..0fd1d440e 100644 --- a/docs/content/dns/zz_gen_cloudflare.md +++ b/docs/content/dns/zz_gen_cloudflare.md @@ -60,6 +60,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| +| `CLOUDFLARE_BASE_URL` | API base URL (Default: https://api.cloudflare.com/client/v4) | | `CLOUDFLARE_HTTP_TIMEOUT` | API request timeout in seconds (Default: ) | | `CLOUDFLARE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `CLOUDFLARE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | diff --git a/providers/dns/cloudflare/cloudflare.go b/providers/dns/cloudflare/cloudflare.go index ce63c86bf..29cfe0f93 100644 --- a/providers/dns/cloudflare/cloudflare.go +++ b/providers/dns/cloudflare/cloudflare.go @@ -22,11 +22,14 @@ import ( const ( envNamespace = "CLOUDFLARE_" - EnvEmail = envNamespace + "EMAIL" - EnvAPIKey = envNamespace + "API_KEY" + EnvEmail = envNamespace + "EMAIL" + EnvAPIKey = envNamespace + "API_KEY" + EnvDNSAPIToken = envNamespace + "DNS_API_TOKEN" EnvZoneAPIToken = envNamespace + "ZONE_API_TOKEN" + EnvBaseURL = envNamespace + "BASE_URL" + EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" @@ -53,6 +56,8 @@ type Config struct { AuthToken string ZoneToken string + BaseURL string + TTL int PropagationTimeout time.Duration PollingInterval time.Duration @@ -114,6 +119,7 @@ func NewDNSProvider() (*DNSProvider, error) { config.AuthKey = values[EnvAPIKey] config.AuthToken = values[EnvDNSAPIToken] config.ZoneToken = values[EnvZoneAPIToken] + config.BaseURL = env.GetOrFile(EnvBaseURL) return NewDNSProviderConfig(config) } diff --git a/providers/dns/cloudflare/cloudflare.toml b/providers/dns/cloudflare/cloudflare.toml index 820101053..caf132bb4 100644 --- a/providers/dns/cloudflare/cloudflare.toml +++ b/providers/dns/cloudflare/cloudflare.toml @@ -73,6 +73,7 @@ It follows the principle of least privilege and limits the possible damage, shou CLOUDFLARE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" CLOUDFLARE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" CLOUDFLARE_HTTP_TIMEOUT = "API request timeout in seconds (Default: )" + CLOUDFLARE_BASE_URL = "API base URL (Default: https://api.cloudflare.com/client/v4)" [Links] API = "https://api.cloudflare.com/" diff --git a/providers/dns/cloudflare/cloudflare_test.go b/providers/dns/cloudflare/cloudflare_test.go index f026bbc4c..f3bba69d2 100644 --- a/providers/dns/cloudflare/cloudflare_test.go +++ b/providers/dns/cloudflare/cloudflare_test.go @@ -16,6 +16,7 @@ var envTest = tester.NewEnvTest( EnvAPIKey, EnvDNSAPIToken, EnvZoneAPIToken, + EnvBaseURL, altEnvEmail, altEnvName(EnvAPIKey), altEnvName(EnvDNSAPIToken), diff --git a/providers/dns/cloudflare/wrapper.go b/providers/dns/cloudflare/wrapper.go index a93feeded..92733a57f 100644 --- a/providers/dns/cloudflare/wrapper.go +++ b/providers/dns/cloudflare/wrapper.go @@ -17,9 +17,14 @@ type metaClient struct { } func newClient(config *Config) (*metaClient, error) { + options := []cloudflare.Option{cloudflare.HTTPClient(config.HTTPClient)} + if config.BaseURL != "" { + options = append(options, cloudflare.BaseURL(config.BaseURL)) + } + // with AuthKey/AuthEmail we can access all available APIs if config.AuthToken == "" { - client, err := cloudflare.New(config.AuthKey, config.AuthEmail, cloudflare.HTTPClient(config.HTTPClient)) + client, err := cloudflare.New(config.AuthKey, config.AuthEmail, options...) if err != nil { return nil, err } @@ -32,7 +37,7 @@ func newClient(config *Config) (*metaClient, error) { }, nil } - dns, err := cloudflare.NewWithAPIToken(config.AuthToken, cloudflare.HTTPClient(config.HTTPClient)) + dns, err := cloudflare.NewWithAPIToken(config.AuthToken, options...) if err != nil { return nil, err } @@ -46,7 +51,7 @@ func newClient(config *Config) (*metaClient, error) { }, nil } - zone, err := cloudflare.NewWithAPIToken(config.ZoneToken, cloudflare.HTTPClient(config.HTTPClient)) + zone, err := cloudflare.NewWithAPIToken(config.ZoneToken, options...) if err != nil { return nil, err } From f4d47c86067be51e696ed0fc70937faad1d1c1f2 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 19 Mar 2025 13:56:07 +0100 Subject: [PATCH 076/298] route53: adds option to use private zone (#2162) --- cmd/zz_gen_cmd_dnshelp.go | 1 + docs/content/dns/zz_gen_route53.md | 1 + providers/dns/route53/route53.go | 5 ++++- providers/dns/route53/route53.toml | 1 + providers/dns/route53/route53_test.go | 1 + 5 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 258ec0f65..1ce9e8f9c 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -2644,6 +2644,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "AWS_MAX_RETRIES": The number of maximum returns the service will use to make an individual API request`) ew.writeln(` - "AWS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 4)`) + ew.writeln(` - "AWS_PRIVATE_ZONE": Set to true to use private zones only (default: use public zones only)`) ew.writeln(` - "AWS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln(` - "AWS_SHARED_CREDENTIALS_FILE": Managed by the AWS client. Shared credentials file.`) ew.writeln(` - "AWS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 10)`) diff --git a/docs/content/dns/zz_gen_route53.md b/docs/content/dns/zz_gen_route53.md index 0d06299a1..a0967a57e 100644 --- a/docs/content/dns/zz_gen_route53.md +++ b/docs/content/dns/zz_gen_route53.md @@ -60,6 +60,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). |--------------------------------|-------------| | `AWS_MAX_RETRIES` | The number of maximum returns the service will use to make an individual API request | | `AWS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 4) | +| `AWS_PRIVATE_ZONE` | Set to true to use private zones only (default: use public zones only) | | `AWS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | | `AWS_SHARED_CREDENTIALS_FILE` | Managed by the AWS client. Shared credentials file. | | `AWS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 10) | diff --git a/providers/dns/route53/route53.go b/providers/dns/route53/route53.go index 8246cd0ad..4d0a13a3d 100644 --- a/providers/dns/route53/route53.go +++ b/providers/dns/route53/route53.go @@ -35,6 +35,7 @@ const ( EnvMaxRetries = envNamespace + "MAX_RETRIES" EnvAssumeRoleArn = envNamespace + "ASSUME_ROLE_ARN" EnvExternalID = envNamespace + "EXTERNAL_ID" + EnvPrivateZone = envNamespace + "PRIVATE_ZONE" EnvWaitForRecordSetsChanged = envNamespace + "WAIT_FOR_RECORD_SETS_CHANGED" @@ -58,6 +59,7 @@ type Config struct { MaxRetries int AssumeRoleArn string ExternalID string + PrivateZone bool WaitForRecordSetsChanged bool @@ -75,6 +77,7 @@ func NewDefaultConfig() *Config { MaxRetries: env.GetOrDefaultInt(EnvMaxRetries, 5), AssumeRoleArn: env.GetOrDefaultString(EnvAssumeRoleArn, ""), ExternalID: env.GetOrDefaultString(EnvExternalID, ""), + PrivateZone: env.GetOrDefaultBool(EnvPrivateZone, false), WaitForRecordSetsChanged: env.GetOrDefaultBool(EnvWaitForRecordSetsChanged, true), @@ -312,7 +315,7 @@ func (d *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string, var hostedZoneID string for _, hostedZone := range resp.HostedZones { // .Name has a trailing dot - if !hostedZone.Config.PrivateZone && ptr.Deref(hostedZone.Name) == authZone { + if ptr.Deref(hostedZone.Name) == authZone && d.config.PrivateZone == hostedZone.Config.PrivateZone { hostedZoneID = ptr.Deref(hostedZone.Id) break } diff --git a/providers/dns/route53/route53.toml b/providers/dns/route53/route53.toml index 0004e9546..9e3b049a6 100644 --- a/providers/dns/route53/route53.toml +++ b/providers/dns/route53/route53.toml @@ -133,6 +133,7 @@ Replace `Z11111112222222333333` with your hosted zone ID and `example.com` with AWS_EXTERNAL_ID = "Managed by STS AssumeRole API operation (`AWS_EXTERNAL_ID_FILE` is not supported)" AWS_WAIT_FOR_RECORD_SETS_CHANGED = "Wait for changes to be INSYNC (it can be unstable)" [Configuration.Additional] + AWS_PRIVATE_ZONE = "Set to true to use private zones only (default: use public zones only)" AWS_SHARED_CREDENTIALS_FILE = "Managed by the AWS client. Shared credentials file." AWS_MAX_RETRIES = "The number of maximum returns the service will use to make an individual API request" AWS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 4)" diff --git a/providers/dns/route53/route53_test.go b/providers/dns/route53/route53_test.go index 1c835ac37..6ab37f674 100644 --- a/providers/dns/route53/route53_test.go +++ b/providers/dns/route53/route53_test.go @@ -23,6 +23,7 @@ var envTest = tester.NewEnvTest( EnvRegion, EnvHostedZoneID, EnvMaxRetries, + EnvPrivateZone, EnvTTL, EnvPropagationTimeout, EnvPollingInterval, From 0fae2f0511d41ad37ed1b8676b3d6d304e5568e1 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 20 Mar 2025 16:27:23 +0100 Subject: [PATCH 077/298] allinkl: remove ReturnInfo (#2490) --- providers/dns/allinkl/internal/client.go | 8 ++++---- providers/dns/allinkl/internal/client_test.go | 2 +- providers/dns/allinkl/internal/types_api.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/providers/dns/allinkl/internal/client.go b/providers/dns/allinkl/internal/client.go index ab8cf9a38..02a5a2c8f 100644 --- a/providers/dns/allinkl/internal/client.go +++ b/providers/dns/allinkl/internal/client.go @@ -86,23 +86,23 @@ func (c *Client) AddDNSSettings(ctx context.Context, record DNSRequest) (string, } // DeleteDNSSettings Deleting a DNS Resource Record. -func (c *Client) DeleteDNSSettings(ctx context.Context, recordID string) (bool, error) { +func (c *Client) DeleteDNSSettings(ctx context.Context, recordID string) (string, error) { requestParams := map[string]string{"record_id": recordID} req, err := c.newRequest(ctx, "delete_dns_settings", requestParams) if err != nil { - return false, err + return "", err } var g DeleteDNSSettingsAPIResponse err = c.do(req, &g) if err != nil { - return false, err + return "", err } c.updateFloodTime(g.Response.KasFloodDelay) - return g.Response.ReturnInfo, nil + return g.Response.ReturnString, nil } func (c *Client) newRequest(ctx context.Context, action string, requestParams any) (*http.Request, error) { diff --git a/providers/dns/allinkl/internal/client_test.go b/providers/dns/allinkl/internal/client_test.go index 3eb7c21a9..b8cc27851 100644 --- a/providers/dns/allinkl/internal/client_test.go +++ b/providers/dns/allinkl/internal/client_test.go @@ -131,7 +131,7 @@ func TestClient_DeleteDNSSettings(t *testing.T) { r, err := client.DeleteDNSSettings(mockContext(), "57347450") require.NoError(t, err) - assert.True(t, r) + assert.Equal(t, "TRUE", r) } func testHandler(filename string) http.HandlerFunc { diff --git a/providers/dns/allinkl/internal/types_api.go b/providers/dns/allinkl/internal/types_api.go index 145163cda..22f2c32ed 100644 --- a/providers/dns/allinkl/internal/types_api.go +++ b/providers/dns/allinkl/internal/types_api.go @@ -89,6 +89,6 @@ type DeleteDNSSettingsAPIResponse struct { type DeleteDNSSettingsResponse struct { KasFloodDelay float64 `json:"KasFloodDelay"` - ReturnInfo bool `json:"ReturnInfo"` ReturnString string `json:"ReturnString"` + // NOTE: ReturnInfo (!= ReturnString) doesn't seem to have a stable type } From dc8a3390aecf07c538dcc953d6381c095b269469 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 23 Mar 2025 20:04:50 +0100 Subject: [PATCH 078/298] chore: update dependencies (#2491) --- go.mod | 64 +++++++++++++-------------- go.sum | 137 ++++++++++++++++++++++++++++----------------------------- 2 files changed, 100 insertions(+), 101 deletions(-) diff --git a/go.mod b/go.mod index 85ac79f3c..f93e7d2d6 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.0 require ( cloud.google.com/go/compute/metadata v0.6.0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 @@ -13,22 +13,22 @@ require ( github.com/Azure/go-autorest/autorest v0.11.30 github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 github.com/Azure/go-autorest/autorest/to v0.4.1 - github.com/BurntSushi/toml v1.4.0 + github.com/BurntSushi/toml v1.5.0 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 - github.com/aliyun/alibaba-cloud-sdk-go v1.63.92 + github.com/aliyun/alibaba-cloud-sdk-go v1.63.100 github.com/aws/aws-sdk-go-v2 v1.36.3 github.com/aws/aws-sdk-go-v2/config v1.29.9 github.com/aws/aws-sdk-go-v2/credentials v1.17.62 github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.1 - github.com/aws/aws-sdk-go-v2/service/route53 v1.49.1 - github.com/aws/aws-sdk-go-v2/service/s3 v1.78.1 + github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0 + github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 github.com/cenkalti/backoff/v4 v4.3.0 github.com/civo/civogo v0.3.11 github.com/cloudflare/cloudflare-go v0.115.0 github.com/dnsimple/dnsimple-go v1.7.0 - github.com/exoscale/egoscale/v3 v3.1.10 + github.com/exoscale/egoscale/v3 v3.1.13 github.com/go-jose/go-jose/v4 v4.0.5 github.com/go-viper/mapstructure/v2 v2.2.1 github.com/google/go-querystring v1.1.0 @@ -36,14 +36,14 @@ require ( github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/hashicorp/go-retryablehttp v0.7.7 github.com/hashicorp/go-version v1.7.0 - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.138 + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.141 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df github.com/infobloxopen/infoblox-go-client/v2 v2.9.0 github.com/labbsr0x/bindman-dns-webhook v1.0.2 - github.com/linode/linodego v1.48.0 + github.com/linode/linodego v1.48.1 github.com/liquidweb/liquidweb-go v1.6.4 github.com/mattn/go-isatty v0.0.20 - github.com/miekg/dns v1.1.63 + github.com/miekg/dns v1.1.64 github.com/mimuret/golang-iij-dpf v0.9.1 github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 github.com/nrdcg/auroradns v1.1.0 @@ -58,7 +58,7 @@ require ( github.com/nrdcg/nodion v0.1.0 github.com/nrdcg/porkbun v0.4.0 github.com/nzdjb/go-metaname v1.0.0 - github.com/oracle/oci-go-sdk/v65 v65.85.0 + github.com/oracle/oci-go-sdk/v65 v65.87.0 github.com/ovh/go-ovh v1.7.0 github.com/pquerna/otp v1.4.0 github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2 @@ -70,22 +70,22 @@ require ( github.com/selectel/go-selvpcclient/v3 v3.2.1 github.com/softlayer/softlayer-go v1.1.7 github.com/stretchr/testify v1.10.0 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1114 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1113 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1128 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128 github.com/transip/gotransip/v6 v6.26.0 github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec - github.com/urfave/cli/v2 v2.27.5 + github.com/urfave/cli/v2 v2.27.6 github.com/vinyldns/go-vinyldns v0.9.16 - github.com/volcengine/volc-sdk-golang v1.0.197 - github.com/vultr/govultr/v3 v3.14.1 - github.com/yandex-cloud/go-genproto v0.0.0-20250304111827-f558b88ff434 - github.com/yandex-cloud/go-sdk v0.0.0-20250304120247-c2605c41f59f - golang.org/x/crypto v0.35.0 - golang.org/x/net v0.36.0 - golang.org/x/oauth2 v0.27.0 - golang.org/x/text v0.22.0 - golang.org/x/time v0.10.0 - google.golang.org/api v0.223.0 + github.com/volcengine/volc-sdk-golang v1.0.199 + github.com/vultr/govultr/v3 v3.17.0 + github.com/yandex-cloud/go-genproto v0.0.0-20250319153614-fb9d3e5eb01a + github.com/yandex-cloud/go-sdk v0.0.0-20250320143332-9cbcfc5de4ae + golang.org/x/crypto v0.36.0 + golang.org/x/net v0.37.0 + golang.org/x/oauth2 v0.28.0 + golang.org/x/text v0.23.0 + golang.org/x/time v0.11.0 + google.golang.org/api v0.227.0 gopkg.in/ns1/ns1-go.v2 v2.13.0 gopkg.in/yaml.v2 v2.4.0 software.sslmate.com/src/go-pkcs12 v0.5.0 @@ -110,7 +110,7 @@ require ( github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect @@ -139,7 +139,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -196,14 +196,14 @@ require ( go.uber.org/multierr v1.9.0 // indirect go.uber.org/ratelimit v0.3.0 // indirect golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect - golang.org/x/mod v0.22.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/tools v0.28.0 // indirect + golang.org/x/mod v0.23.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/tools v0.30.0 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect - google.golang.org/grpc v1.70.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect + google.golang.org/grpc v1.71.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 39f8c89cf..007d094b5 100644 --- a/go.sum +++ b/go.sum @@ -42,8 +42,8 @@ github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYs github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= @@ -89,8 +89,8 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3Xow github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= -github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= @@ -113,8 +113,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/aliyun/alibaba-cloud-sdk-go v1.63.92 h1:qespx4b6EexlXkvQUow9x0v1GnWUJYGU5FWYw3a4Wlg= -github.com/aliyun/alibaba-cloud-sdk-go v1.63.92/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= +github.com/aliyun/alibaba-cloud-sdk-go v1.63.100 h1:yUkCbrSM1cWtgBfRVKMQtdt22KhDvKY7g4V+92eG9wA= +github.com/aliyun/alibaba-cloud-sdk-go v1.63.100/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -145,18 +145,18 @@ github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLH github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.2 h1:t/gZFyrijKuSU0elA5kRngP/oU3mc0I+Dvp8HwRE4c0= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.6.2/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 h1:lguz0bmOoGzozP9XfRJR1QIayEYo+2vP/No3OfLF0pU= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA= github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.1 h1:0j58UseBtLuBcP6nY2z4SM1qZEvLF0ylyH6+ggnphLg= github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.1/go.mod h1:Qy22QnQSdHbZwMZrarsWZBIuK51isPlkD+Z4sztxX0o= -github.com/aws/aws-sdk-go-v2/service/route53 v1.49.1 h1:krDhGq5RpSgpfPB9riTYLLSoCB8bNBhtdva6t1HDEWc= -github.com/aws/aws-sdk-go-v2/service/route53 v1.49.1/go.mod h1:kGYOjvTa0Vw0qxrqrOLut1vMnui6qLxqv/SX3vYeM8Y= -github.com/aws/aws-sdk-go-v2/service/s3 v1.78.1 h1:1M0gSbyP6q06gl3384wpoKPaH9G16NPqZFieEhLboSU= -github.com/aws/aws-sdk-go-v2/service/s3 v1.78.1/go.mod h1:4qzsZSzB/KiX2EzDjs9D7A8rI/WGJxZceVJIHqtJjIU= +github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0 h1:/nkJHXtJXJeelXHqG0898+fWKgvfaXBhGzbCsSmn9j8= +github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0/go.mod h1:kGYOjvTa0Vw0qxrqrOLut1vMnui6qLxqv/SX3vYeM8Y= +github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 h1:jIiopHEV22b4yQP2q36Y0OmwLbsxNWdWwfZRR5QRRO4= +github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc= github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 h1:8JdC7Gr9NROg1Rusk25IcZeTO59zLxsKgE0gkh5O6h0= github.com/aws/aws-sdk-go-v2/service/sso v1.25.1/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 h1:KwuLovgQPcdjNMfFt9OhUd9a2OwcOKhxfvF4glTzLuA= @@ -239,8 +239,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/exoscale/egoscale/v3 v3.1.10 h1:L4f8dW7q6uqgMwLWdlRgXSfJfrry4uMigls8dUF72ZA= -github.com/exoscale/egoscale/v3 v3.1.10/go.mod h1:t9+MpSEam94na48O/xgvvPFpQPRiwZ3kBN4/UuQtKco= +github.com/exoscale/egoscale/v3 v3.1.13 h1:CAGC7QRjp2AiGj01agsSD0VKCp4OZmW5f51vV2IguNQ= +github.com/exoscale/egoscale/v3 v3.1.13/go.mod h1:t9+MpSEam94na48O/xgvvPFpQPRiwZ3kBN4/UuQtKco= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= @@ -403,8 +403,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= -github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= @@ -477,8 +477,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.138 h1:VH/OZE73y0IRomF9QqCw71etSdfFbQIq/utq164IOVg= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.138/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.141 h1:8i57QAi5u+iPAYze92bkIvZoHiS0J45ndul5glr/NE8= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.141/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -551,8 +551,8 @@ github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk= github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/linode/linodego v1.48.0 h1:Xn00rWYSpK5arNEFwymW58jpsdnK8axxhwS/9+cFkQ0= -github.com/linode/linodego v1.48.0/go.mod h1:k/lRz48xUtGaeVYyvF2X2iNxMpt8JJ+DR4I77R8I1Vg= +github.com/linode/linodego v1.48.1 h1:Ojw1S+K5jJr1dggO8/H6r4FINxXnJbOU5GkbpaTfmhU= +github.com/linode/linodego v1.48.1/go.mod h1:fc3t60If8X+yZTFAebhCnNDFrhwQhq9HDU92WnBousQ= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= @@ -587,8 +587,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= -github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= +github.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ= +github.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck= github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34= github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= @@ -682,8 +682,8 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= -github.com/oracle/oci-go-sdk/v65 v65.85.0 h1:lRMFVgTCZhl7IzP0fXHIROFGWlIoVZMGIGr96Aai2eE= -github.com/oracle/oci-go-sdk/v65 v65.85.0/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0= +github.com/oracle/oci-go-sdk/v65 v65.87.0 h1:CeVuK8t0dYODGT3P9IDhz4vyXF8poYE1ijoiO5vrKl0= +github.com/oracle/oci-go-sdk/v65 v65.87.0/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0= github.com/ovh/go-ovh v1.7.0 h1:V14nF7FwDjQrZt9g7jzcvAAQ3HN6DNShRFRMC3jLoPw= github.com/ovh/go-ovh v1.7.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -850,11 +850,10 @@ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1113/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1114 h1:+LcGbkxgk+MRjplJej/Kmj9Hp043RI3rAGGcHqHNj2I= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1114/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1113 h1:RMPbo4SisyxOpaOZ+QtaXlGTEdedxzXqTu4MtUjC29U= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1113/go.mod h1:pKq9XZ1gACH+iZMCt6FIU2nhHcCoCbQETNj1/7FzvR0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1128 h1:NGnqDc8FQL0YdiCHgTO4Wkso6ToD8rE3JW9VOzoPBNA= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1128/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128 h1:mrJ5Fbkd7sZIJ5F6oRfh5zebPQaudPH9Y0+GUmFytYU= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128/go.mod h1:zbsYIBT+VTX4z4ocjTAdLBIWyNYj3z0BRqd0iPdnjsk= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -868,14 +867,14 @@ github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6 github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec h1:2s/ghQ8wKE+UzD/hf3P4Gd1j0JI9ncbxv+nsypPoUYI= github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7Qs3ku1ckpqed8tCRSqTlp8NAeZfAVpfx4OzXMss= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= -github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= +github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= -github.com/volcengine/volc-sdk-golang v1.0.197 h1:jJlcMp+4i3lVL2ZZTVo8u3cndlXzC5SZoqH/4M6pyuw= -github.com/volcengine/volc-sdk-golang v1.0.197/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ= -github.com/vultr/govultr/v3 v3.14.1 h1:9BpyZgsWasuNoR39YVMcq44MSaF576Z4D+U3ro58eJQ= -github.com/vultr/govultr/v3 v3.14.1/go.mod h1:q34Wd76upKmf+vxFMgaNMH3A8BbsPBmSYZUGC8oZa5w= +github.com/volcengine/volc-sdk-golang v1.0.199 h1:zv9QOqTl/IsLwtfC37GlJtcz6vMAHi+pjq8ILWjLYUc= +github.com/volcengine/volc-sdk-golang v1.0.199/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ= +github.com/vultr/govultr/v3 v3.17.0 h1:His5Jh5N8KKqaJxfy3uG6jQbLXy0TmQhNxOiRvkKk00= +github.com/vultr/govultr/v3 v3.17.0/go.mod h1:q34Wd76upKmf+vxFMgaNMH3A8BbsPBmSYZUGC8oZa5w= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= @@ -887,10 +886,10 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yandex-cloud/go-genproto v0.0.0-20250304111827-f558b88ff434 h1:yTo+nye0TPT/eh/WxMLRed+/IgIXFq4ngl2TKKBLY5Q= -github.com/yandex-cloud/go-genproto v0.0.0-20250304111827-f558b88ff434/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= -github.com/yandex-cloud/go-sdk v0.0.0-20250304120247-c2605c41f59f h1:teFweT3kDo6fwIqNRy97faZf7TinptQjjtgqFznH4HQ= -github.com/yandex-cloud/go-sdk v0.0.0-20250304120247-c2605c41f59f/go.mod h1:T0bCwRJFLctOZMqmQCZPVcI0I5DtVtmVsidavp0K2+w= +github.com/yandex-cloud/go-genproto v0.0.0-20250319153614-fb9d3e5eb01a h1:YO8gGyAV4N5SR3NzloZ1128IahSpXWr78oU7aEe7f04= +github.com/yandex-cloud/go-genproto v0.0.0-20250319153614-fb9d3e5eb01a/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-sdk v0.0.0-20250320143332-9cbcfc5de4ae h1:x+uGuST05LVlgCxF5TsP8kQCCTW7uIeAQJ1dKtSmWqE= +github.com/yandex-cloud/go-sdk v0.0.0-20250320143332-9cbcfc5de4ae/go.mod h1:V71iJlJnS/NtNNdg/B7SwccBS19aXxwY3fv/wut9D74= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -921,8 +920,8 @@ go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= @@ -964,8 +963,8 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= -golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1006,8 +1005,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1058,16 +1057,16 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= -golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= -golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= -golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1081,8 +1080,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1158,16 +1157,16 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1183,16 +1182,16 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= -golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1247,8 +1246,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1273,8 +1272,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.223.0 h1:JUTaWEriXmEy5AhvdMgksGGPEFsYfUKaPEYXd4c3Wvc= -google.golang.org/api v0.223.0/go.mod h1:C+RS7Z+dDwds2b+zoAk5hN/eSfsiCn0UDrYof/M4d2M= +google.golang.org/api v0.227.0 h1:QvIHF9IuyG6d6ReE+BNd11kIB8hZvjN8Z5xY5t21zYc= +google.golang.org/api v0.227.0/go.mod h1:EIpaG6MbTgQarWF5xJvX0eOJPK9n/5D4Bynb9j2HXvQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1315,10 +1314,10 @@ google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxH google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU= google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 h1:DMTIbak9GhdaSxEjvVzAeNZvyc03I61duqNbnm3SU0M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1336,8 +1335,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= +google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From ba7b4bcf111d4d19e5f5165f19750cc0942fff0e Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 25 Mar 2025 15:05:26 +0100 Subject: [PATCH 079/298] chore: update linter (#2492) --- .github/workflows/pr.yml | 2 +- .golangci.yml | 502 +++++++++--------- certificate/certificates_test.go | 6 +- challenge/dns01/nameserver_test.go | 2 +- challenge/http01/domain_matcher_test.go | 2 +- .../tlsalpn01/tls_alpn_challenge_test.go | 2 +- platform/tester/env_test.go | 50 +- providers/dns/cloudns/internal/client_test.go | 12 +- providers/dns/loopia/internal/client_test.go | 2 +- providers/dns/netcup/internal/session_test.go | 2 +- providers/dns/oraclecloud/oraclecloud.go | 2 +- providers/dns/sakuracloud/wrapper.go | 2 +- providers/dns/wedos/internal/token.go | 7 +- 13 files changed, 294 insertions(+), 299 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 4381c7fbd..9fc48680a 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: stable - GOLANGCI_LINT_VERSION: v1.64.2 + GOLANGCI_LINT_VERSION: v2.0.1 HUGO_VERSION: 0.131.0 CGO_ENABLED: 0 LEGO_E2E_TESTS: CI diff --git a/.golangci.yml b/.golangci.yml index 80d9c2b49..7281599f5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,270 +1,266 @@ +version: "2" + +formatters: + enable: + - gci + - gofmt + - gofumpt + - goimports + linters: - enable-all: true + default: all disable: + - bodyclose + - canonicalheader + - contextcheck - cyclop # duplicate of gocyclo - - sqlclosecheck # not relevant (SQL) - - rowserrcheck # not relevant (SQL) - - lll - - gosec - dupl # not relevant - - prealloc # too many false-positive - - bodyclose # too many false-positive - - mnd - - testpackage # not relevant - - tparallel # not relevant - - paralleltest # not relevant - - nestif # too many false-positive - - wrapcheck - err113 # not relevant - - nlreturn # not relevant - - wsl # not relevant + - errchkjson + - errname - exhaustive # not relevant - exhaustruct # not relevant - - makezero # not relevant - forbidigo - - varnamelen # not relevant - - nilnil # not relevant - - ireturn # not relevant - - contextcheck # too many false-positive - - tenv # we already have a test "framework" to handle env vars - - noctx - forcetypeassert - - tagliatelle - - errname - - errchkjson - - nonamedreturns + - gosec + - gosmopolitan # not relevant + - ireturn # not relevant + - lll + - makezero # not relevant + - mnd - musttag # false-positive https://github.com/junk1tm/musttag/issues/17 - - gosmopolitan # not relevant - - canonicalheader # Can create side effects in the context of API clients + - nestif # too many false-positive + - nilnil # not relevant + - nlreturn # not relevant + - noctx + - nonamedreturns + - paralleltest # not relevant + - prealloc # too many false-positive + - rowserrcheck # not relevant (SQL) + - sqlclosecheck # not relevant (SQL) + - tagliatelle + - testpackage # not relevant + - tparallel # not relevant - usestdlibvars # false-positive https://github.com/sashamelentyev/usestdlibvars/issues/96 + - varnamelen # not relevant + - wrapcheck + - wsl # should be enabled it the future. -linters-settings: - govet: - enable-all: true - disable: - - fieldalignment - settings: - printf: - funcs: - - Print - - Printf - - Warn - - Warnf - - Fatal - - Fatalf - gocyclo: - min-complexity: 12 - goconst: - min-len: 3 - min-occurrences: 3 - funlen: - lines: -1 - statements: 50 - misspell: - locale: US - ignore-words: - - internetbs - depguard: + settings: + depguard: + rules: + main: + deny: + - pkg: github.com/instana/testify + desc: not allowed + - pkg: github.com/pkg/errors + desc: Should be replaced by standard lib errors package + funlen: + lines: -1 + statements: 50 + goconst: + min-len: 3 + min-occurrences: 3 + gocritic: + disabled-checks: + - paramTypeCombine # already handle by gofumpt.extra-rules + - whyNoLint # already handle by nonolint + - unnamedResult + - hugeParam + - sloppyReassign + - rangeValCopy + - octalLiteral + - ptrToRefParam + - appendAssign + - ruleguard + - httpNoBody + - exposedSyncMutex + enabled-tags: + - diagnostic + - style + - performance + gocyclo: + min-complexity: 12 + godox: + keywords: + - FIXME + govet: + disable: + - fieldalignment + enable-all: true + settings: + printf: + funcs: + - Print + - Printf + - Warn + - Warnf + - Fatal + - Fatalf + misspell: + locale: US + ignore-rules: + - internetbs + perfsprint: + err-error: true + errorf: true + sprintf1: true + strconcat: false + revive: + rules: + - name: struct-tag + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: error-return + - name: error-strings + - name: error-naming + - name: exported + disabled: true + - name: if-return + - name: increment-decrement + - name: var-naming + - name: var-declaration + - name: package-comments + disabled: true + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: indent-error-flow + - name: errorf + - name: empty-block + - name: superfluous-else + - name: unused-parameter + disabled: true + - name: unreachable-code + - name: redefines-builtin-id + tagalign: + align: false + order: + - xml + - json + - yaml + - yml + - toml + - mapstructure + - url + testifylint: + disable: + - require-error + - go-require + usetesting: + os-setenv: false # we already have a test "framework" to handle env vars + + exclusions: + warn-unused: true + presets: + - comments + - std-error-handling rules: - main: - deny: - - pkg: "github.com/instana/testify" - desc: not allowed - - pkg: "github.com/pkg/errors" - desc: Should be replaced by standard lib errors package - tagalign: - align: false - order: - - xml - - json - - yaml - - yml - - toml - - mapstructure - - url - godox: - keywords: - - FIXME - gocritic: - enabled-tags: - - diagnostic - - style - - performance - disabled-checks: - - paramTypeCombine # already handle by gofumpt.extra-rules - - whyNoLint # already handle by nonolint - - unnamedResult - - hugeParam - - sloppyReassign - - rangeValCopy - - octalLiteral - - ptrToRefParam - - appendAssign - - ruleguard - - httpNoBody - - exposedSyncMutex - revive: - rules: - - name: struct-tag - - name: blank-imports - - name: context-as-argument - - name: context-keys-type - - name: dot-imports - - name: error-return - - name: error-strings - - name: error-naming - - name: exported - disabled: true - - name: if-return - - name: increment-decrement - - name: var-naming - - name: var-declaration - - name: package-comments - disabled: true - - name: range - - name: receiver-naming - - name: time-naming - - name: unexported-return - - name: indent-error-flow - - name: errorf - - name: empty-block - - name: superfluous-else - - name: unused-parameter - disabled: true - - name: unreachable-code - - name: redefines-builtin-id - testifylint: - disable: - - require-error - - go-require - perfsprint: - err-error: true - errorf: true - sprintf1: true - strconcat: false - usetesting: - os-setenv: false # we already have a test "framework" to handle env vars - -run: - timeout: 10m - relative-path-mode: cfg - -output: - show-stats: true - sort-results: true - sort-order: - - linter - - file + - linters: + - funlen + - goconst + - maintidx + path: (.+)_test.go + - linters: + - errcheck + path: (.+)_test.go + text: Error return value of `fmt.Fprintln` is not checked + - linters: + - gochecknoglobals + path: certcrypto/crypto.go + text: (tlsFeatureExtensionOID|ocspMustStapleFeature) is a global variable + - linters: + - gochecknoglobals + path: challenge/dns01/nameserver.go + text: (defaultNameservers|recursiveNameservers|fqdnSoaCache|muFqdnSoaCache) is a global variable + - linters: + - gochecknoglobals + path: challenge/dns01/nameserver_.+.go + text: dnsTimeout is a global variable + - linters: + - gochecknoglobals + path: challenge/dns01/nameserver_test.go + text: findXByFqdnTestCases is a global variable + - linters: + - goconst + path: challenge/http01/domain_matcher.go + text: string `Host` has \d occurrences, make it a constant + - linters: + - gocyclo + path: challenge/http01/domain_matcher.go + text: cyclomatic complexity \d+ of func `parseForwardedHeader` is high + - linters: + - funlen + path: challenge/http01/domain_matcher.go + text: Function 'parseForwardedHeader' has too many statements + - linters: + - gochecknoglobals + path: challenge/tlsalpn01/tls_alpn_challenge.go + text: idPeAcmeIdentifierV1 is a global variable + - linters: + - gochecknoglobals + path: log/logger.go + text: Logger is a global variable + - linters: + - gochecknoglobals + path: e2e/(dnschallenge/)?[\d\w]+_test.go + text: load is a global variable + - linters: + - gochecknoglobals + path: providers/dns/([\d\w]+/)*[\d\w]+_test.go + text: envTest is a global variable + - linters: + - gochecknoglobals + path: providers/http/([\d\w]+/)*[\d\w]+_test.go + text: envTest is a global variable + - linters: + - gochecknoglobals + path: providers/dns/namecheap/namecheap_test.go + text: testCases is a global variable + - linters: + - gochecknoglobals + path: providers/dns/acmedns/mock_test.go + text: egTestAccount is a global variable + - linters: + - gochecknoglobals + path: providers/http/memcached/memcached_test.go + text: memcachedHosts is a global variable + - linters: + - misspell + path: providers/dns/checkdomain/internal/types.go + text: '`payed` is a misspelling of `paid`' + - linters: + - thelper + path: platform/tester/env_test.go + - linters: + - staticcheck + path: providers/dns/oraclecloud/oraclecloud_test.go + text: 'SA1019: x509.EncryptPEMBlock has been deprecated since Go 1.16' + - linters: + - gochecknoglobals + path: providers/dns/sakuracloud/wrapper.go + text: mu is a global variable + - linters: + - gocyclo + path: cmd/cmd_renew.go + text: cyclomatic complexity \d+ of func `(renewForDomains|renewForCSR)` is high + - linters: + - funlen + path: cmd/cmd_renew.go + text: Function 'renewForDomains' has too many statements + - linters: + - gocyclo + path: providers/dns/cpanel/cpanel.go + text: cyclomatic complexity 13 of func `\(\*DNSProvider\)\.CleanUp` is high + - linters: + - staticcheck + # Those elements have been replaced by non-exposed structures. + path: providers/dns/linode/linode_test.go + text: 'SA1019: linodego\.(DomainsPagedResponse|DomainRecordsPagedResponse) is deprecated' issues: - exclude-generated: strict - exclude-use-default: false max-issues-per-linter: 0 max-same-issues: 0 - exclude: - - 'Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked' - - 'exported (type|method|function) (.+) should have comment or be unexported' - - 'ST1000: at least one file in a package should have a package comment' - exclude-rules: - - path: (.+)_test.go - linters: - - funlen - - goconst - - maintidx - - path: (.+)_test.go - text: 'Error return value of `fmt.Fprintln` is not checked' - linters: - - errcheck - - path: certcrypto/crypto.go - text: '(tlsFeatureExtensionOID|ocspMustStapleFeature) is a global variable' - linters: - - gochecknoglobals - - path: challenge/dns01/nameserver.go - text: '(defaultNameservers|recursiveNameservers|fqdnSoaCache|muFqdnSoaCache) is a global variable' - linters: - - gochecknoglobals - - path: challenge/dns01/nameserver_.+.go - text: 'dnsTimeout is a global variable' - linters: - - gochecknoglobals - - path: challenge/dns01/nameserver_test.go - text: 'findXByFqdnTestCases is a global variable' - linters: - - gochecknoglobals - - path: challenge/http01/domain_matcher.go - text: 'string `Host` has \d occurrences, make it a constant' - linters: - - goconst - - path: challenge/http01/domain_matcher.go - text: 'cyclomatic complexity \d+ of func `parseForwardedHeader` is high' - linters: - - gocyclo - - path: challenge/http01/domain_matcher.go - text: "Function 'parseForwardedHeader' has too many statements" - linters: - - funlen - - path: challenge/tlsalpn01/tls_alpn_challenge.go - text: 'idPeAcmeIdentifierV1 is a global variable' - linters: - - gochecknoglobals - - path: log/logger.go - text: 'Logger is a global variable' - linters: - - gochecknoglobals - - path: 'e2e/(dnschallenge/)?[\d\w]+_test.go' - text: load is a global variable - linters: - - gochecknoglobals - - path: 'providers/dns/([\d\w]+/)*[\d\w]+_test.go' - text: 'envTest is a global variable' - linters: - - gochecknoglobals - - path: 'providers/http/([\d\w]+/)*[\d\w]+_test.go' - text: 'envTest is a global variable' - linters: - - gochecknoglobals - - path: providers/dns/namecheap/namecheap_test.go - text: 'testCases is a global variable' - linters: - - gochecknoglobals - - path: providers/dns/acmedns/mock_test.go - text: 'egTestAccount is a global variable' - linters: - - gochecknoglobals - - path: providers/http/memcached/memcached_test.go - text: 'memcachedHosts is a global variable' - linters: - - gochecknoglobals - - path: providers/dns/checkdomain/internal/types.go - text: '`payed` is a misspelling of `paid`' - linters: - - misspell - - path: platform/tester/env_test.go - linters: - - thelper - - path: providers/dns/oraclecloud/oraclecloud_test.go - text: 'SA1019: x509.EncryptPEMBlock has been deprecated since Go 1.16' - linters: - - staticcheck - - path: providers/dns/sakuracloud/wrapper.go - text: 'mu is a global variable' - linters: - - gochecknoglobals - - path: cmd/cmd_renew.go - text: 'cyclomatic complexity \d+ of func `(renewForDomains|renewForCSR)` is high' - linters: - - gocyclo - - path: cmd/cmd_renew.go - text: "Function 'renewForDomains' has too many statements" - linters: - - funlen - - path: providers/dns/cpanel/cpanel.go - text: 'cyclomatic complexity 13 of func `\(\*DNSProvider\)\.CleanUp` is high' - linters: - - gocyclo - - # Those elements have been replaced by non-exposed structures. - - path: providers/dns/linode/linode_test.go - linters: - - staticcheck - text: "SA1019: linodego\\.(DomainsPagedResponse|DomainRecordsPagedResponse) is deprecated" diff --git a/certificate/certificates_test.go b/certificate/certificates_test.go index da35dbd1a..3be2d606a 100644 --- a/certificate/certificates_test.go +++ b/certificate/certificates_test.go @@ -205,7 +205,7 @@ func Test_checkResponse(t *testing.T) { require.NoError(t, err) assert.True(t, valid) assert.NotNil(t, certRes) - assert.Equal(t, "", certRes.Domain) + assert.Empty(t, certRes.Domain) assert.Contains(t, certRes.CertStableURL, "/certificate") assert.Contains(t, certRes.CertURL, "/certificate") assert.Nil(t, certRes.CSR) @@ -255,7 +255,7 @@ func Test_checkResponse_issuerRelUp(t *testing.T) { require.NoError(t, err) assert.True(t, valid) assert.NotNil(t, certRes) - assert.Equal(t, "", certRes.Domain) + assert.Empty(t, certRes.Domain) assert.Contains(t, certRes.CertStableURL, "/certificate") assert.Contains(t, certRes.CertURL, "/certificate") assert.Nil(t, certRes.CSR) @@ -295,7 +295,7 @@ func Test_checkResponse_no_bundle(t *testing.T) { require.NoError(t, err) assert.True(t, valid) assert.NotNil(t, certRes) - assert.Equal(t, "", certRes.Domain) + assert.Empty(t, certRes.Domain) assert.Contains(t, certRes.CertStableURL, "/certificate") assert.Contains(t, certRes.CertURL, "/certificate") assert.Nil(t, certRes.CSR) diff --git a/challenge/dns01/nameserver_test.go b/challenge/dns01/nameserver_test.go index 0e66c2fd1..d5bb0c0a1 100644 --- a/challenge/dns01/nameserver_test.go +++ b/challenge/dns01/nameserver_test.go @@ -39,7 +39,7 @@ func TestLookupNameserversOK(t *testing.T) { sort.Strings(nss) sort.Strings(test.nss) - assert.EqualValues(t, test.nss, nss) + assert.Equal(t, test.nss, nss) }) } } diff --git a/challenge/http01/domain_matcher_test.go b/challenge/http01/domain_matcher_test.go index efdc4641d..7bedf9f63 100644 --- a/challenge/http01/domain_matcher_test.go +++ b/challenge/http01/domain_matcher_test.go @@ -77,7 +77,7 @@ func Test_parseForwardedHeader(t *testing.T) { actual, err := parseForwardedHeader(test.input) if test.err == "" { require.NoError(t, err) - assert.EqualValues(t, test.want, actual) + assert.Equal(t, test.want, actual) } else { require.Error(t, err) assert.Contains(t, err.Error(), test.err) diff --git a/challenge/tlsalpn01/tls_alpn_challenge_test.go b/challenge/tlsalpn01/tls_alpn_challenge_test.go index 55ef4e23d..9f65742f3 100644 --- a/challenge/tlsalpn01/tls_alpn_challenge_test.go +++ b/challenge/tlsalpn01/tls_alpn_challenge_test.go @@ -162,7 +162,7 @@ func TestChallengeIPaddress(t *testing.T) { value, err := asn1.Marshal(zBytes[:sha256.Size]) require.NoError(t, err, "Expected marshaling of the keyAuth to return no error") - require.EqualValues(t, value, extValue, "Expected the challenge certificate id-pe-acmeIdentifier extension to contain the SHA-256 digest of the keyAuth") + require.Equal(t, value, extValue, "Expected the challenge certificate id-pe-acmeIdentifier extension to contain the SHA-256 digest of the keyAuth") return nil } diff --git a/platform/tester/env_test.go b/platform/tester/env_test.go index 25748f8ff..d5084056f 100644 --- a/platform/tester/env_test.go +++ b/platform/tester/env_test.go @@ -62,7 +62,7 @@ func TestEnvTest(t *testing.T) { assert.True(t, envTest.IsLiveTest()) assert.Equal(t, "A", envTest.GetValue(envVar01)) assert.Equal(t, "B", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetDomain()) + assert.Empty(t, envTest.GetDomain()) }, }, { @@ -75,9 +75,9 @@ func TestEnvTest(t *testing.T) { }, expected: func(t *testing.T, envTest *tester.EnvTest) { assert.False(t, envTest.IsLiveTest()) - assert.Equal(t, "", envTest.GetValue(envVar01)) + assert.Empty(t, envTest.GetValue(envVar01)) assert.Equal(t, "B", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetDomain()) + assert.Empty(t, envTest.GetDomain()) }, }, { @@ -94,7 +94,7 @@ func TestEnvTest(t *testing.T) { assert.True(t, envTest.IsLiveTest()) assert.Equal(t, "A", envTest.GetValue(envVar01)) assert.Equal(t, "B", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetValue(envVarDomain)) + assert.Empty(t, envTest.GetValue(envVarDomain)) assert.Equal(t, "D", envTest.GetDomain()) }, }, @@ -110,8 +110,8 @@ func TestEnvTest(t *testing.T) { expected: func(t *testing.T, envTest *tester.EnvTest) { assert.False(t, envTest.IsLiveTest()) assert.Equal(t, "A", envTest.GetValue(envVar01)) - assert.Equal(t, "", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetValue(envVarDomain)) + assert.Empty(t, envTest.GetValue(envVar02)) + assert.Empty(t, envTest.GetValue(envVarDomain)) assert.Equal(t, "D", envTest.GetDomain()) }, }, @@ -128,8 +128,8 @@ func TestEnvTest(t *testing.T) { assert.False(t, envTest.IsLiveTest()) assert.Equal(t, "A", envTest.GetValue(envVar01)) assert.Equal(t, "B", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetValue(envVarDomain)) - assert.Equal(t, "", envTest.GetDomain()) + assert.Empty(t, envTest.GetValue(envVarDomain)) + assert.Empty(t, envTest.GetDomain()) }, }, { @@ -145,7 +145,7 @@ func TestEnvTest(t *testing.T) { assert.True(t, envTest.IsLiveTest()) assert.Equal(t, "A", envTest.GetValue(envVar01)) assert.Equal(t, "B", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetDomain()) + assert.Empty(t, envTest.GetDomain()) }, }, { @@ -161,7 +161,7 @@ func TestEnvTest(t *testing.T) { assert.True(t, envTest.IsLiveTest()) assert.Equal(t, "A", envTest.GetValue(envVar01)) assert.Equal(t, "B", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetDomain()) + assert.Empty(t, envTest.GetDomain()) }, }, { @@ -174,9 +174,9 @@ func TestEnvTest(t *testing.T) { }, expected: func(t *testing.T, envTest *tester.EnvTest) { assert.True(t, envTest.IsLiveTest()) - assert.Equal(t, "", envTest.GetValue(envVar01)) + assert.Empty(t, envTest.GetValue(envVar01)) assert.Equal(t, "B", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetDomain()) + assert.Empty(t, envTest.GetDomain()) }, }, { @@ -190,8 +190,8 @@ func TestEnvTest(t *testing.T) { expected: func(t *testing.T, envTest *tester.EnvTest) { assert.False(t, envTest.IsLiveTest()) assert.Equal(t, "A", envTest.GetValue(envVar01)) - assert.Equal(t, "", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetDomain()) + assert.Empty(t, envTest.GetValue(envVar02)) + assert.Empty(t, envTest.GetDomain()) }, }, { @@ -210,7 +210,7 @@ func TestEnvTest(t *testing.T) { assert.True(t, envTest.IsLiveTest()) assert.Equal(t, "A", envTest.GetValue(envVar01)) assert.Equal(t, "B", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetValue(envVarDomain)) + assert.Empty(t, envTest.GetValue(envVarDomain)) assert.Equal(t, "D", envTest.GetDomain()) }, }, @@ -229,8 +229,8 @@ func TestEnvTest(t *testing.T) { assert.True(t, envTest.IsLiveTest()) assert.Equal(t, "A", envTest.GetValue(envVar01)) assert.Equal(t, "B", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetValue(envVarDomain)) - assert.Equal(t, "", envTest.GetDomain()) + assert.Empty(t, envTest.GetValue(envVarDomain)) + assert.Empty(t, envTest.GetDomain()) }, }, { @@ -247,7 +247,7 @@ func TestEnvTest(t *testing.T) { assert.True(t, envTest.IsLiveTest()) assert.Equal(t, "A", envTest.GetValue(envVar01)) assert.Equal(t, "B", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetDomain()) + assert.Empty(t, envTest.GetDomain()) }, }, { @@ -264,7 +264,7 @@ func TestEnvTest(t *testing.T) { assert.False(t, envTest.IsLiveTest()) assert.Equal(t, "A", envTest.GetValue(envVar01)) assert.Equal(t, "B", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetDomain()) + assert.Empty(t, envTest.GetDomain()) }, }, { @@ -282,7 +282,7 @@ func TestEnvTest(t *testing.T) { assert.True(t, envTest.IsLiveTest()) assert.Equal(t, "A", envTest.GetValue(envVar01)) assert.Equal(t, "B", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetDomain()) + assert.Empty(t, envTest.GetDomain()) }, }, { @@ -300,7 +300,7 @@ func TestEnvTest(t *testing.T) { assert.False(t, envTest.IsLiveTest()) assert.Equal(t, "A", envTest.GetValue(envVar01)) assert.Equal(t, "B", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetDomain()) + assert.Empty(t, envTest.GetDomain()) }, }, { @@ -316,8 +316,8 @@ func TestEnvTest(t *testing.T) { expected: func(t *testing.T, envTest *tester.EnvTest) { assert.False(t, envTest.IsLiveTest()) assert.Equal(t, "A", envTest.GetValue(envVar01)) - assert.Equal(t, "", envTest.GetValue(envVar02)) - assert.Equal(t, "", envTest.GetDomain()) + assert.Empty(t, envTest.GetValue(envVar02)) + assert.Empty(t, envTest.GetDomain()) }, }, } @@ -357,7 +357,7 @@ func TestEnvTest_ClearEnv(t *testing.T) { envTest.ClearEnv() - assert.Equal(t, "", os.Getenv(envVar01)) - assert.Equal(t, "", os.Getenv(envVar02)) + assert.Empty(t, os.Getenv(envVar01)) + assert.Empty(t, os.Getenv(envVar02)) assert.Equal(t, "X", os.Getenv("EXTRA_LEGO_TEST")) } diff --git a/providers/dns/cloudns/internal/client_test.go b/providers/dns/cloudns/internal/client_test.go index 999bd1446..8c29bc6ec 100644 --- a/providers/dns/cloudns/internal/client_test.go +++ b/providers/dns/cloudns/internal/client_test.go @@ -97,7 +97,7 @@ func TestClient_GetZone(t *testing.T) { desc string authFQDN string apiResponse string - expected + expected expected }{ { desc: "zone found", @@ -157,7 +157,7 @@ func TestClient_FindTxtRecord(t *testing.T) { authFQDN string zoneName string apiResponse string - expected + expected expected }{ { desc: "record found", @@ -264,7 +264,7 @@ func TestClient_ListTxtRecord(t *testing.T) { authFQDN string zoneName string apiResponse string - expected + expected expected }{ { desc: "record found", @@ -377,7 +377,7 @@ func TestClient_AddTxtRecord(t *testing.T) { value string ttl int apiResponse string - expected + expected expected }{ { desc: "sub-zone", @@ -477,7 +477,7 @@ func TestClient_RemoveTxtRecord(t *testing.T) { id int zoneName string apiResponse string - expected + expected expected }{ { desc: "record found", @@ -550,7 +550,7 @@ func TestClient_GetUpdateStatus(t *testing.T) { authFQDN string zoneName string apiResponse string - expected + expected expected }{ { desc: "50% sync", diff --git a/providers/dns/loopia/internal/client_test.go b/providers/dns/loopia/internal/client_test.go index 4fe2e1fd0..b99e9b202 100644 --- a/providers/dns/loopia/internal/client_test.go +++ b/providers/dns/loopia/internal/client_test.go @@ -206,7 +206,7 @@ func TestClient_GetZoneRecord(t *testing.T) { RecordID: 12345678, }, } - assert.EqualValues(t, expected, recordObjs) + assert.Equal(t, expected, recordObjs) } func TestClient_rpcCall_404(t *testing.T) { diff --git a/providers/dns/netcup/internal/session_test.go b/providers/dns/netcup/internal/session_test.go index 2b69265d2..c5048500e 100644 --- a/providers/dns/netcup/internal/session_test.go +++ b/providers/dns/netcup/internal/session_test.go @@ -124,7 +124,7 @@ func TestClient_Login_errors(t *testing.T) { sessionID, err := client.login(context.Background()) assert.Error(t, err) - assert.Equal(t, "", sessionID) + assert.Empty(t, sessionID) }) } } diff --git a/providers/dns/oraclecloud/oraclecloud.go b/providers/dns/oraclecloud/oraclecloud.go index e41227da0..bb3a03b67 100644 --- a/providers/dns/oraclecloud/oraclecloud.go +++ b/providers/dns/oraclecloud/oraclecloud.go @@ -168,7 +168,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var deleteHash *string - for _, record := range domainRecords.RecordCollection.Items { + for _, record := range domainRecords.Items { if record.Rdata != nil && *record.Rdata == `"`+info.Value+`"` { deleteHash = record.RecordHash break diff --git a/providers/dns/sakuracloud/wrapper.go b/providers/dns/sakuracloud/wrapper.go index a74478f6c..0898ccd3b 100644 --- a/providers/dns/sakuracloud/wrapper.go +++ b/providers/dns/sakuracloud/wrapper.go @@ -62,7 +62,7 @@ func (d *DNSProvider) cleanupTXTRecord(fqdn, value string) error { var updRecords iaas.DNSRecords for _, r := range zone.Records { - if !(r.Name == subDomain && r.Type == "TXT" && r.RData == value) { + if !(r.Name == subDomain && r.Type == "TXT" && r.RData == value) { //nolint:staticcheck // Clearer without De Morgan's law. updRecords = append(updRecords, r) } } diff --git a/providers/dns/wedos/internal/token.go b/providers/dns/wedos/internal/token.go index b83b107c1..6be590f67 100644 --- a/providers/dns/wedos/internal/token.go +++ b/providers/dns/wedos/internal/token.go @@ -52,12 +52,11 @@ func utcToCet(utc time.Time) time.Time { dayOff := 0 breaking := time.Date(utc.Year(), utcMonth+1, dayOff, 1, 0, 0, 0, time.UTC) - for { - if breaking.Weekday() == time.Sunday { - break - } + for breaking.Weekday() != time.Sunday { dayOff-- + breaking = time.Date(utc.Year(), utcMonth+1, dayOff, 1, 0, 0, 0, time.UTC) + if dayOff < -7 { panic("safety exit to avoid infinite loop") } From 627e6e2c3535da3c6e4ea5c923e3028958aca474 Mon Sep 17 00:00:00 2001 From: Sebastian Lohff Date: Wed, 26 Mar 2025 18:14:15 +0100 Subject: [PATCH 080/298] designate: speed up API requests by using filters (#2498) --- providers/dns/designate/designate.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/providers/dns/designate/designate.go b/providers/dns/designate/designate.go index e2a5721c0..c58baaace 100644 --- a/providers/dns/designate/designate.go +++ b/providers/dns/designate/designate.go @@ -245,10 +245,15 @@ func (d *DNSProvider) updateRecord(record *recordsets.RecordSet, value string) e } func (d *DNSProvider) getZoneID(wanted string) (string, error) { - allPages, err := zones.List(d.client, nil).AllPages() + listOpts := zones.ListOpts{ + Name: wanted, + } + + allPages, err := zones.List(d.client, listOpts).AllPages() if err != nil { return "", err } + allZones, err := zones.ExtractZones(allPages) if err != nil { return "", err @@ -259,14 +264,21 @@ func (d *DNSProvider) getZoneID(wanted string) (string, error) { return zone.ID, nil } } + return "", fmt.Errorf("zone id not found for %s", wanted) } func (d *DNSProvider) getRecord(zoneID, wanted string) (*recordsets.RecordSet, error) { - allPages, err := recordsets.ListByZone(d.client, zoneID, nil).AllPages() + listOpts := recordsets.ListOpts{ + Name: wanted, + Type: "TXT", + } + + allPages, err := recordsets.ListByZone(d.client, zoneID, listOpts).AllPages() if err != nil { return nil, err } + allRecords, err := recordsets.ExtractRecordSets(allPages) if err != nil { return nil, err From 24a46d0c1530deca8e5128f4508c40f3113b3dd3 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 27 Mar 2025 13:04:02 +0100 Subject: [PATCH 081/298] feat: add delay option for TLSALPN challenge (#2499) --- challenge/resolver/solver_manager.go | 4 ++-- challenge/tlsalpn01/tls_alpn_challenge.go | 29 +++++++++++++++++++++-- cmd/flags.go | 6 +++++ cmd/setup_challenges.go | 2 +- docs/data/zz_cli_help.toml | 1 + 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/challenge/resolver/solver_manager.go b/challenge/resolver/solver_manager.go index 95db0af54..dcde3a3ed 100644 --- a/challenge/resolver/solver_manager.go +++ b/challenge/resolver/solver_manager.go @@ -42,8 +42,8 @@ func (c *SolverManager) SetHTTP01Provider(p challenge.Provider, opts ...http01.C } // SetTLSALPN01Provider specifies a custom provider p that can solve the given TLS-ALPN-01 challenge. -func (c *SolverManager) SetTLSALPN01Provider(p challenge.Provider) error { - c.solvers[challenge.TLSALPN01] = tlsalpn01.NewChallenge(c.core, validate, p) +func (c *SolverManager) SetTLSALPN01Provider(p challenge.Provider, opts ...tlsalpn01.ChallengeOption) error { + c.solvers[challenge.TLSALPN01] = tlsalpn01.NewChallenge(c.core, validate, p, opts...) return nil } diff --git a/challenge/tlsalpn01/tls_alpn_challenge.go b/challenge/tlsalpn01/tls_alpn_challenge.go index 04ba71507..559e1f905 100644 --- a/challenge/tlsalpn01/tls_alpn_challenge.go +++ b/challenge/tlsalpn01/tls_alpn_challenge.go @@ -7,6 +7,7 @@ import ( "crypto/x509/pkix" "encoding/asn1" "fmt" + "time" "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api" @@ -21,18 +22,38 @@ var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31} type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error +type ChallengeOption func(*Challenge) error + +// SetDelay sets a delay between the start of the TLS listener and the challenge validation. +func SetDelay(delay time.Duration) ChallengeOption { + return func(chlg *Challenge) error { + chlg.delay = delay + return nil + } +} + type Challenge struct { core *api.Core validate ValidateFunc provider challenge.Provider + delay time.Duration } -func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge { - return &Challenge{ +func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider, opts ...ChallengeOption) *Challenge { + chlg := &Challenge{ core: core, validate: validate, provider: provider, } + + for _, opt := range opts { + err := opt(chlg) + if err != nil { + log.Infof("challenge option error: %v", err) + } + } + + return chlg } func (c *Challenge) SetProvider(provider challenge.Provider) { @@ -66,6 +87,10 @@ func (c *Challenge) Solve(authz acme.Authorization) error { } }() + if c.delay > 0 { + time.Sleep(c.delay) + } + chlng.KeyAuthorization = keyAuth return c.validate(c.core, domain, chlng) } diff --git a/cmd/flags.go b/cmd/flags.go index 51aa479b6..ebf051ae6 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -32,6 +32,7 @@ const ( flgHTTPS3Bucket = "http.s3-bucket" flgTLS = "tls" flgTLSPort = "tls.port" + flgTLSDelay = "tls.delay" flgDNS = "dns" flgDNSDisableCP = "dns.disable-cp" flgDNSPropagationWait = "dns.propagation-wait" @@ -164,6 +165,11 @@ func CreateFlags(defaultPath string) []cli.Flag { Usage: "Set the port and interface to use for TLS-ALPN-01 based challenges to listen on. Supported: interface:port or :port.", Value: ":443", }, + &cli.DurationFlag{ + Name: flgTLSDelay, + Usage: "Delay between the start of the TLS listener (use for TLSALPN-01 based challenges) and the validation of the challenge.", + Value: 0, + }, &cli.StringFlag{ Name: flgDNS, Usage: "Solve a DNS-01 challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage.", diff --git a/cmd/setup_challenges.go b/cmd/setup_challenges.go index 16a5b90fe..c923fa004 100644 --- a/cmd/setup_challenges.go +++ b/cmd/setup_challenges.go @@ -32,7 +32,7 @@ func setupChallenges(ctx *cli.Context, client *lego.Client) { } if ctx.Bool(flgTLS) { - err := client.Challenge.SetTLSALPN01Provider(setupTLSProvider(ctx)) + err := client.Challenge.SetTLSALPN01Provider(setupTLSProvider(ctx), tlsalpn01.SetDelay(ctx.Duration(flgTLSDelay))) if err != nil { log.Fatal(err) } diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index da083666f..9b3dd1d02 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -39,6 +39,7 @@ GLOBAL OPTIONS: --http.s3-bucket value Set the S3 bucket name to use for HTTP-01 based challenges. Challenges will be written to the S3 bucket. --tls Use the TLS-ALPN-01 challenge to solve challenges. Can be mixed with other types of challenges. (default: false) --tls.port value Set the port and interface to use for TLS-ALPN-01 based challenges to listen on. Supported: interface:port or :port. (default: ":443") + --tls.delay value Delay between the start of the TLS listener (use for TLSALPN-01 based challenges) and the validation of the challenge. (default: 0s) --dns value Solve a DNS-01 challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage. --dns.disable-cp (deprecated) use dns.propagation-disable-ans instead. (default: false) --dns.propagation-disable-ans By setting this flag to true, disables the need to await propagation of the TXT record to all authoritative name servers. (default: false) From 3f795d6ab1eac0f9727db063ccdc955191e64475 Mon Sep 17 00:00:00 2001 From: fries1234 Date: Sun, 30 Mar 2025 16:49:24 -0700 Subject: [PATCH 082/298] pdns: fix TXT record cleanup for wildcard domains (#2500) Co-authored-by: Fernandez Ludovic --- providers/dns/pdns/pdns.go | 31 +++++++++++++++++++++---------- providers/dns/pdns/pdns_test.go | 4 ++++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/providers/dns/pdns/pdns.go b/providers/dns/pdns/pdns.go index 07bc663f1..c2f780ba8 100644 --- a/providers/dns/pdns/pdns.go +++ b/providers/dns/pdns/pdns.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "net/url" + "strconv" "time" "github.com/go-acme/lego/v4/challenge" @@ -150,7 +151,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { } rec := internal.Record{ - Content: "\"" + info.Value + "\"", + Content: strconv.Quote(info.Value), Disabled: false, // pre-v1 API @@ -202,17 +203,27 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("pdns: no existing record found for %s", info.EffectiveFQDN) } - rrSets := internal.RRSets{ - RRSets: []internal.RRSet{ - { - Name: set.Name, - Type: set.Type, - ChangeType: "DELETE", - }, - }, + var records []internal.Record + for _, r := range set.Records { + if r.Content != strconv.Quote(info.Value) { + records = append(records, r) + } } - err = d.client.UpdateRecords(ctx, zone, rrSets) + rrSet := internal.RRSet{ + Name: set.Name, + Type: set.Type, + } + + if len(records) > 0 { + rrSet.ChangeType = "REPLACE" + rrSet.TTL = d.config.TTL + rrSet.Records = records + } else { + rrSet.ChangeType = "DELETE" + } + + err = d.client.UpdateRecords(ctx, zone, internal.RRSets{RRSets: []internal.RRSet{rrSet}}) if err != nil { return fmt.Errorf("pdns: %w", err) } diff --git a/providers/dns/pdns/pdns_test.go b/providers/dns/pdns/pdns_test.go index 70b386b81..6762e892e 100644 --- a/providers/dns/pdns/pdns_test.go +++ b/providers/dns/pdns/pdns_test.go @@ -141,9 +141,13 @@ func TestLivePresentAndCleanup(t *testing.T) { err = provider.Present(envTest.GetDomain(), "", "123d==") require.NoError(t, err) + err = provider.Present(envTest.GetDomain(), "", "123e==") + require.NoError(t, err) err = provider.CleanUp(envTest.GetDomain(), "", "123d==") require.NoError(t, err) + err = provider.CleanUp(envTest.GetDomain(), "", "123e==") + require.NoError(t, err) } func mustParse(rawURL string) *url.URL { From 3b9653beecadad482c4a7440931290870e135503 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 14 Apr 2025 19:03:14 +0200 Subject: [PATCH 083/298] Add DNS provider for Baidu Cloud (#2505) --- README.md | 72 ++++----- cmd/zz_gen_cmd_dnshelp.go | 21 +++ docs/content/dns/zz_gen_baiducloud.md | 69 +++++++++ docs/data/zz_cli_help.toml | 2 +- go.mod | 1 + go.sum | 2 + providers/dns/baiducloud/baiducloud.go | 159 ++++++++++++++++++++ providers/dns/baiducloud/baiducloud.toml | 24 +++ providers/dns/baiducloud/baiducloud_test.go | 143 ++++++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 10 files changed, 459 insertions(+), 37 deletions(-) create mode 100644 docs/content/dns/zz_gen_baiducloud.md create mode 100644 providers/dns/baiducloud/baiducloud.go create mode 100644 providers/dns/baiducloud/baiducloud.toml create mode 100644 providers/dns/baiducloud/baiducloud_test.go diff --git a/README.md b/README.md index 646b064e9..ad675ba60 100644 --- a/README.md +++ b/README.md @@ -66,182 +66,182 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Autodns Azure (deprecated) Azure DNS - Bindman + Baidu Cloud + Bindman Bluecat BookMyName Brandit (deprecated) - Bunny + Bunny Checkdomain Civo Cloud.ru - CloudDNS + CloudDNS Cloudflare ClouDNS CloudXNS (Deprecated) - ConoHa + ConoHa Constellix Core-Networks CPanel/WHM - Derak Cloud + Derak Cloud deSEC.io Designate DNSaaS for Openstack Digital Ocean - DirectAdmin + DirectAdmin DNS Made Easy dnsHome.de DNSimple - DNSPod (deprecated) + DNSPod (deprecated) Domain Offensive (do.de) Domeneshop DreamHost - Duck DNS + Duck DNS Dyn Dynu EasyDNS - Efficient IP + Efficient IP Epik Exoscale External program - F5 XC + F5 XC freemyip.com G-Core Gandi - Gandi Live DNS (v5) + Gandi Live DNS (v5) Glesys Go Daddy Google Cloud - Google Domains + Google Domains Hetzner Hosting.de Hosttech - HTTP request + HTTP request http.net Huawei Cloud Hurricane Electric DNS - HyperOne + HyperOne IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox - Infomaniak + Infomaniak Internet Initiative Japan Internet.bs INWX - Ionos + Ionos IPv64 iwantmyname Joker - Joohoi's ACME-DNS + Joohoi's ACME-DNS Liara Lima-City Linode (v4) - Liquid Web + Liquid Web Loopia LuaDNS Mail-in-a-Box - ManageEngine CloudDNS + ManageEngine CloudDNS Manual Metaname Metaregistrar - mijn.host + mijn.host Mittwald myaddr.{tools,dev,io} MyDNS.jp - MythicBeasts + MythicBeasts Name.com Namecheap Namesilo - NearlyFreeSpeech.NET + NearlyFreeSpeech.NET Netcup Netlify Nicmanager - NIFCloud + NIFCloud Njalla Nodion NS1 - Open Telekom Cloud + Open Telekom Cloud Oracle Cloud OVH plesk.com - Porkbun + Porkbun PowerDNS Rackspace Rain Yun/雨云 - RcodeZero + 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 Spaceship Stackpath - Technitium + Technitium Tencent Cloud DNS Timeweb Cloud TransIP - UKFast SafeDNS + UKFast SafeDNS Ultradns Variomedia VegaDNS - Vercel + Vercel Versio.[nl|eu|uk] VinylDNS VK Cloud - Volcano Engine/火山引擎 + Volcano Engine/火山引擎 Vscale Vultr Webnames - Websupport + Websupport WEDOS West.cn/西部数码 Yandex 360 - Yandex Cloud + Yandex Cloud Yandex PDD Zone.ee Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 1ce9e8f9c..671334e57 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -22,6 +22,7 @@ func allDNSCodes() string { "autodns", "azure", "azuredns", + "baiducloud", "bindman", "bluecat", "bookmyname", @@ -378,6 +379,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/azuredns`) + case "baiducloud": + // generated from: providers/dns/baiducloud/baiducloud.toml + ew.writeln(`Configuration for Baidu Cloud.`) + ew.writeln(`Code: 'baiducloud'`) + ew.writeln(`Since: 'v4.23.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "BAIDUCLOUD_ACCESS_KEY_ID": Access key`) + ew.writeln(` - "BAIDUCLOUD_SECRET_ACCESS_KEY": Secret access key`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "BAIDUCLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "BAIDUCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "BAIDUCLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/baiducloud`) + case "bindman": // generated from: providers/dns/bindman/bindman.toml ew.writeln(`Configuration for Bindman.`) diff --git a/docs/content/dns/zz_gen_baiducloud.md b/docs/content/dns/zz_gen_baiducloud.md new file mode 100644 index 000000000..11a71c1ab --- /dev/null +++ b/docs/content/dns/zz_gen_baiducloud.md @@ -0,0 +1,69 @@ +--- +title: "Baidu Cloud" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: baiducloud +dnsprovider: + since: "v4.23.0" + code: "baiducloud" + url: "https://cloud.baidu.com" +--- + + + + + + +Configuration for [Baidu Cloud](https://cloud.baidu.com). + + + + +- Code: `baiducloud` +- Since: v4.23.0 + + +Here is an example bash command using the Baidu Cloud provider: + +```bash +BAIDUCLOUD_ACCESS_KEY_ID="xxx" \ +BAIDUCLOUD_SECRET_ACCESS_KEY="yyy" \ +lego --email you@example.com --dns baiducloud -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `BAIDUCLOUD_ACCESS_KEY_ID` | Access key | +| `BAIDUCLOUD_SECRET_ACCESS_KEY` | Secret access 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 | +|--------------------------------|-------------| +| `BAIDUCLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `BAIDUCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `BAIDUCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://cloud.baidu.com/doc/DNS/s/El4s7lssr) +- [Go client](https://github.com/baidubce/bce-sdk-go) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 9b3dd1d02..fe878a546 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -150,7 +150,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, bindman, bluecat, bookmyname, 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, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi + acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, 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, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/go.mod b/go.mod index f93e7d2d6..740641193 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 + github.com/baidubce/bce-sdk-go v0.9.223 github.com/cenkalti/backoff/v4 v4.3.0 github.com/civo/civogo v0.3.11 github.com/cloudflare/cloudflare-go v0.115.0 diff --git a/go.sum b/go.sum index 007d094b5..707d92038 100644 --- a/go.sum +++ b/go.sum @@ -166,6 +166,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjK github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/baidubce/bce-sdk-go v0.9.223 h1:vvDeIemf7ePPP59nLHCntQ/vS++ok2HKbRPgmz1VZKU= +github.com/baidubce/bce-sdk-go v0.9.223/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= diff --git a/providers/dns/baiducloud/baiducloud.go b/providers/dns/baiducloud/baiducloud.go new file mode 100644 index 000000000..fc317904a --- /dev/null +++ b/providers/dns/baiducloud/baiducloud.go @@ -0,0 +1,159 @@ +// Package baiducloud implements a DNS provider for solving the DNS-01 challenge using Baidu Cloud. +package baiducloud + +import ( + "errors" + "fmt" + "time" + + baidudns "github.com/baidubce/bce-sdk-go/services/dns" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" +) + +// Environment variables names. +const ( + envNamespace = "BAIDUCLOUD_" + + EnvAccessKeyID = envNamespace + "ACCESS_KEY_ID" + EnvSecretAccessKey = envNamespace + "SECRET_ACCESS_KEY" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" +) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + AccessKeyID string + SecretAccessKey string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int +} + +// 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), + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *baidudns.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for Baidu Cloud. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAccessKeyID, EnvSecretAccessKey) + if err != nil { + return nil, fmt.Errorf("baiducloud: %w", err) + } + + config := NewDefaultConfig() + config.AccessKeyID = values[EnvAccessKeyID] + config.SecretAccessKey = values[EnvSecretAccessKey] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Baidu Cloud. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("baiducloud: the configuration of the DNS provider is nil") + } + + if config.AccessKeyID == "" && config.SecretAccessKey == "" { + return nil, errors.New("baiducloud: credentials missing") + } + + client, err := baidudns.NewClient(config.AccessKeyID, config.SecretAccessKey, "") + if err != nil { + return nil, fmt.Errorf("baiducloud: %w", err) + } + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("baiducloud: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("baiducloud: %w", err) + } + + crr := &baidudns.CreateRecordRequest{ + Description: ptr.Pointer("lego"), + Rr: subDomain, + Type: "TXT", + Value: info.Value, + } + + err = d.client.CreateRecord(dns01.UnFqdn(authZone), crr, "") + if err != nil { + return fmt.Errorf("baiducloud: create record: %w", err) + } + + 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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("baiducloud: could not find zone for domain %q: %w", domain, err) + } + + lrr := &baidudns.ListRecordRequest{} + + recordResponse, err := d.client.ListRecord(dns01.UnFqdn(authZone), lrr) + if err != nil { + return fmt.Errorf("baiducloud: list record: %w", err) + } + + recordID, err := findRecordID(recordResponse, info) + if err != nil { + return fmt.Errorf("baiducloud: find record: %w", err) + } + + err = d.client.DeleteRecord(dns01.UnFqdn(authZone), recordID, "") + if err != nil { + return fmt.Errorf("baiducloud: delete record: %w", err) + } + + return nil +} + +func findRecordID(recordResponse *baidudns.ListRecordResponse, info dns01.ChallengeInfo) (string, error) { + for _, record := range recordResponse.Records { + if record.Type == "TXT" && record.Value == info.Value { + return record.Id, nil + } + } + + return "", errors.New("record not found") +} + +// 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/baiducloud/baiducloud.toml b/providers/dns/baiducloud/baiducloud.toml new file mode 100644 index 000000000..941d90b2c --- /dev/null +++ b/providers/dns/baiducloud/baiducloud.toml @@ -0,0 +1,24 @@ +Name = "Baidu Cloud" +Description = '''''' +URL = "https://cloud.baidu.com" +Code = "baiducloud" +Since = "v4.23.0" + +Example = ''' +BAIDUCLOUD_ACCESS_KEY_ID="xxx" \ +BAIDUCLOUD_SECRET_ACCESS_KEY="yyy" \ +lego --email you@example.com --dns baiducloud -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + BAIDUCLOUD_ACCESS_KEY_ID = "Access key" + BAIDUCLOUD_SECRET_ACCESS_KEY = "Secret access key" + [Configuration.Additional] + BAIDUCLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + BAIDUCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + BAIDUCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + +[Links] + API = "https://cloud.baidu.com/doc/DNS/s/El4s7lssr" + GoClient = "https://github.com/baidubce/bce-sdk-go" diff --git a/providers/dns/baiducloud/baiducloud_test.go b/providers/dns/baiducloud/baiducloud_test.go new file mode 100644 index 000000000..3cc411323 --- /dev/null +++ b/providers/dns/baiducloud/baiducloud_test.go @@ -0,0 +1,143 @@ +package baiducloud + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvAccessKeyID, EnvSecretAccessKey).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvAccessKeyID: "key", + EnvSecretAccessKey: "secret", + }, + }, + { + desc: "missing access key ID", + envVars: map[string]string{ + EnvAccessKeyID: "key", + }, + expected: "baiducloud: some credentials information are missing: BAIDUCLOUD_SECRET_ACCESS_KEY", + }, + { + desc: "missing secret access key", + envVars: map[string]string{ + EnvSecretAccessKey: "secret", + }, + expected: "baiducloud: some credentials information are missing: BAIDUCLOUD_ACCESS_KEY_ID", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "baiducloud: some credentials information are missing: BAIDUCLOUD_ACCESS_KEY_ID,BAIDUCLOUD_SECRET_ACCESS_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 + accessKeyID string + secretAccessKey string + expected string + }{ + { + desc: "success", + accessKeyID: "key", + secretAccessKey: "secret", + }, + { + desc: "missing access key ID", + accessKeyID: "", + secretAccessKey: "secret", + expected: "baiducloud: accessKeyId should not be empty", + }, + { + desc: "missing secret access key", + accessKeyID: "key", + secretAccessKey: "", + expected: "baiducloud: secretKey should not be empty", + }, + { + desc: "missing credentials", + expected: "baiducloud: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.AccessKeyID = test.accessKeyID + config.SecretAccessKey = test.secretAccessKey + + 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 9b3f70771..c3c10e379 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -16,6 +16,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/autodns" "github.com/go-acme/lego/v4/providers/dns/azure" "github.com/go-acme/lego/v4/providers/dns/azuredns" + "github.com/go-acme/lego/v4/providers/dns/baiducloud" "github.com/go-acme/lego/v4/providers/dns/bindman" "github.com/go-acme/lego/v4/providers/dns/bluecat" "github.com/go-acme/lego/v4/providers/dns/bookmyname" @@ -182,6 +183,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return azure.NewDNSProvider() case "azuredns": return azuredns.NewDNSProvider() + case "baiducloud": + return baiducloud.NewDNSProvider() case "bindman": return bindman.NewDNSProvider() case "bluecat": From fcc64f006802c50745bd6cd7ae63566abd0359ac Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 14 Apr 2025 19:29:36 +0200 Subject: [PATCH 084/298] Add DNS provider for Axelname (#2495) --- README.md | 75 +++---- cmd/zz_gen_cmd_dnshelp.go | 22 +++ docs/content/dns/zz_gen_axelname.md | 69 +++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/axelname/axelname.go | 157 +++++++++++++++ providers/dns/axelname/axelname.toml | 24 +++ providers/dns/axelname/axelname_test.go | 141 ++++++++++++++ providers/dns/axelname/internal/client.go | 183 ++++++++++++++++++ .../dns/axelname/internal/client_test.go | 112 +++++++++++ .../axelname/internal/fixtures/dns_add.json | 5 + .../internal/fixtures/dns_add_error.json | 5 + .../internal/fixtures/dns_delete.json | 5 + .../internal/fixtures/dns_delete_error.json | 5 + .../axelname/internal/fixtures/dns_list.json | 33 ++++ .../internal/fixtures/dns_list_error.json | 5 + providers/dns/axelname/internal/types.go | 35 ++++ providers/dns/zz_gen_dns_providers.go | 3 + 17 files changed, 845 insertions(+), 36 deletions(-) create mode 100644 docs/content/dns/zz_gen_axelname.md create mode 100644 providers/dns/axelname/axelname.go create mode 100644 providers/dns/axelname/axelname.toml create mode 100644 providers/dns/axelname/axelname_test.go create mode 100644 providers/dns/axelname/internal/client.go create mode 100644 providers/dns/axelname/internal/client_test.go create mode 100644 providers/dns/axelname/internal/fixtures/dns_add.json create mode 100644 providers/dns/axelname/internal/fixtures/dns_add_error.json create mode 100644 providers/dns/axelname/internal/fixtures/dns_delete.json create mode 100644 providers/dns/axelname/internal/fixtures/dns_delete_error.json create mode 100644 providers/dns/axelname/internal/fixtures/dns_list.json create mode 100644 providers/dns/axelname/internal/fixtures/dns_list_error.json create mode 100644 providers/dns/axelname/internal/types.go diff --git a/README.md b/README.md index ad675ba60..b22f67bfa 100644 --- a/README.md +++ b/README.md @@ -64,184 +64,189 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Aurora DNS Autodns + Axelname Azure (deprecated) Azure DNS - Baidu Cloud + Baidu Cloud Bindman Bluecat BookMyName - Brandit (deprecated) + Brandit (deprecated) Bunny Checkdomain Civo - Cloud.ru + Cloud.ru CloudDNS Cloudflare ClouDNS - CloudXNS (Deprecated) + CloudXNS (Deprecated) ConoHa Constellix Core-Networks - CPanel/WHM + CPanel/WHM Derak Cloud deSEC.io Designate DNSaaS for Openstack - Digital Ocean + Digital Ocean DirectAdmin DNS Made Easy dnsHome.de - DNSimple + DNSimple DNSPod (deprecated) Domain Offensive (do.de) Domeneshop - DreamHost + DreamHost Duck DNS Dyn Dynu - EasyDNS + EasyDNS Efficient IP Epik Exoscale - External program + External program F5 XC freemyip.com G-Core - Gandi + Gandi Gandi Live DNS (v5) Glesys Go Daddy - Google Cloud + Google Cloud Google Domains Hetzner Hosting.de - Hosttech + Hosttech HTTP request http.net Huawei Cloud - Hurricane Electric DNS + Hurricane Electric DNS HyperOne IBM Cloud (SoftLayer) IIJ DNS Platform Service - Infoblox + Infoblox Infomaniak Internet Initiative Japan Internet.bs - INWX + INWX Ionos IPv64 iwantmyname - Joker + Joker Joohoi's ACME-DNS Liara Lima-City - Linode (v4) + Linode (v4) Liquid Web Loopia LuaDNS - Mail-in-a-Box + Mail-in-a-Box ManageEngine CloudDNS Manual Metaname - Metaregistrar + Metaregistrar mijn.host Mittwald myaddr.{tools,dev,io} - MyDNS.jp + MyDNS.jp MythicBeasts Name.com Namecheap - Namesilo + Namesilo NearlyFreeSpeech.NET Netcup Netlify - Nicmanager + Nicmanager NIFCloud Njalla Nodion - NS1 + NS1 Open Telekom Cloud Oracle Cloud OVH - plesk.com + plesk.com Porkbun PowerDNS Rackspace - Rain Yun/雨云 + Rain Yun/雨云 RcodeZero reg.ru Regfish - RFC2136 + RFC2136 RimuHosting Sakura Cloud Scaleway - Selectel + Selectel Selectel v2 SelfHost.(de|eu) Servercow - Shellrent + Shellrent Simply.com Sonic Spaceship - Stackpath + Stackpath Technitium Tencent Cloud DNS Timeweb Cloud - TransIP + TransIP UKFast SafeDNS Ultradns Variomedia - VegaDNS + VegaDNS Vercel Versio.[nl|eu|uk] VinylDNS - VK Cloud + VK Cloud Volcano Engine/火山引擎 Vscale Vultr - Webnames + Webnames Websupport WEDOS West.cn/西部数码 - 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 671334e57..a409d5ad9 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -20,6 +20,7 @@ func allDNSCodes() string { "arvancloud", "auroradns", "autodns", + "axelname", "azure", "azuredns", "baiducloud", @@ -321,6 +322,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/autodns`) + case "axelname": + // generated from: providers/dns/axelname/axelname.toml + ew.writeln(`Configuration for Axelname.`) + ew.writeln(`Code: 'axelname'`) + ew.writeln(`Since: 'v4.23.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "AXELNAME_NICKNAME": Account nickname`) + ew.writeln(` - "AXELNAME_TOKEN": API token`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "AXELNAME_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "AXELNAME_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "AXELNAME_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "AXELNAME_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/axelname`) + case "azure": // generated from: providers/dns/azure/azure.toml ew.writeln(`Configuration for Azure (deprecated).`) diff --git a/docs/content/dns/zz_gen_axelname.md b/docs/content/dns/zz_gen_axelname.md new file mode 100644 index 000000000..b1bb3e166 --- /dev/null +++ b/docs/content/dns/zz_gen_axelname.md @@ -0,0 +1,69 @@ +--- +title: "Axelname" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: axelname +dnsprovider: + since: "v4.23.0" + code: "axelname" + url: "https://axelname.ru" +--- + + + + + + +Configuration for [Axelname](https://axelname.ru). + + + + +- Code: `axelname` +- Since: v4.23.0 + + +Here is an example bash command using the Axelname provider: + +```bash +AXELNAME_NICKNAME="yyy" \ +AXELNAME_TOKEN="xxx" \ +lego --email you@example.com --dns axelname -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `AXELNAME_NICKNAME` | Account nickname | +| `AXELNAME_TOKEN` | API token | + +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 | +|--------------------------------|-------------| +| `AXELNAME_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `AXELNAME_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `AXELNAME_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `AXELNAME_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://axelname.ru/static/content/files/axelname_api_rest_lite.pdf) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index fe878a546..b15ba00fb 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -150,7 +150,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, 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, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi + acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, 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, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/axelname/axelname.go b/providers/dns/axelname/axelname.go new file mode 100644 index 000000000..033ccc92b --- /dev/null +++ b/providers/dns/axelname/axelname.go @@ -0,0 +1,157 @@ +// Package axelname implements a DNS provider for solving the DNS-01 challenge using Axelname. +package axelname + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/axelname/internal" +) + +// Environment variables names. +const ( + envNamespace = "AXELNAME_" + + EnvNickname = envNamespace + "NICKNAME" + EnvToken = envNamespace + "TOKEN" + + 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 { + Nickname string + Token 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for Axelname. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvNickname, EnvToken) + if err != nil { + return nil, fmt.Errorf("axelname: %w", err) + } + + config := NewDefaultConfig() + config.Nickname = values[EnvNickname] + config.Token = values[EnvToken] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Axelname. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("axelname: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.Nickname, config.Token) + if err != nil { + return nil, fmt.Errorf("axelname: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("axelname: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("axelname: %w", err) + } + + record := internal.Record{ + Name: subDomain, + Type: "TXT", + Value: info.Value, + } + + err = d.client.AddRecord(context.Background(), dns01.UnFqdn(authZone), record) + if err != nil { + return fmt.Errorf("axelname: add record: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("axelname: could not find zone for domain %q: %w", domain, err) + } + + records, err := d.client.ListRecords(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("axelname: list records: %w", err) + } + + for _, record := range records { + if record.Type != "TXT" || record.Value != info.Value { + continue + } + + err = d.client.DeleteRecord(ctx, dns01.UnFqdn(authZone), record) + if err != nil { + return fmt.Errorf("axelname: delete record: %w", err) + } + + return nil + } + + return errors.New("axelname: delete record: record not found") +} + +// 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/axelname/axelname.toml b/providers/dns/axelname/axelname.toml new file mode 100644 index 000000000..ee348d5d8 --- /dev/null +++ b/providers/dns/axelname/axelname.toml @@ -0,0 +1,24 @@ +Name = "Axelname" +Description = '''''' +URL = "https://axelname.ru" +Code = "axelname" +Since = "v4.23.0" + +Example = ''' +AXELNAME_NICKNAME="yyy" \ +AXELNAME_TOKEN="xxx" \ +lego --email you@example.com --dns axelname -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + AXELNAME_NICKNAME = "Account nickname" + AXELNAME_TOKEN = "API token" + [Configuration.Additional] + AXELNAME_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + AXELNAME_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + AXELNAME_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + AXELNAME_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://axelname.ru/static/content/files/axelname_api_rest_lite.pdf" diff --git a/providers/dns/axelname/axelname_test.go b/providers/dns/axelname/axelname_test.go new file mode 100644 index 000000000..52c4f38b9 --- /dev/null +++ b/providers/dns/axelname/axelname_test.go @@ -0,0 +1,141 @@ +package axelname + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvNickname, EnvToken).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvNickname: "user", + EnvToken: "secret", + }, + }, + { + desc: "missing nickname", + envVars: map[string]string{ + EnvNickname: "", + EnvToken: "secret", + }, + expected: "axelname: some credentials information are missing: AXELNAME_NICKNAME", + }, + { + desc: "missing token", + envVars: map[string]string{ + EnvNickname: "user", + EnvToken: "", + }, + expected: "axelname: some credentials information are missing: AXELNAME_TOKEN", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "axelname: some credentials information are missing: AXELNAME_NICKNAME,AXELNAME_TOKEN", + }, + } + + 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 + token string + nickname string + expected string + }{ + { + desc: "success", + nickname: "user", + token: "secret", + }, + { + desc: "missing nickname", + expected: "axelname: credentials missing", + }, + { + desc: "missing token", + expected: "axelname: credentials missing", + }, + { + desc: "missing credentials", + expected: "axelname: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Token = test.token + config.Nickname = test.nickname + + 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/axelname/internal/client.go b/providers/dns/axelname/internal/client.go new file mode 100644 index 000000000..f6cf079e6 --- /dev/null +++ b/providers/dns/axelname/internal/client.go @@ -0,0 +1,183 @@ +package internal + +import ( + "context" + "encoding/json" + "errors" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + querystring "github.com/google/go-querystring/query" +) + +const statusSuccess = "success" + +const defaultBaseURL = "https://my.axelname.ru/rest/" + +// Client the Axelname API client. +type Client struct { + nickname string + token string + + baseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(nickname, token string) (*Client, error) { + if token == "" || nickname == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + nickname: nickname, + token: token, + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) ListRecords(ctx context.Context, domain string) ([]Record, error) { + endpoint := c.baseURL.JoinPath("dns_list") + + query := endpoint.Query() + query.Set("domain", domain) + + endpoint.RawQuery = query.Encode() + + req, err := c.newRequest(ctx, endpoint) + if err != nil { + return nil, err + } + + var results ListResponse + + err = c.do(req, &results) + if err != nil { + return nil, err + } + + if results.Result != statusSuccess { + return nil, &results.APIError + } + + return results.List, nil +} + +func (c *Client) DeleteRecord(ctx context.Context, domain string, record Record) error { + endpoint := c.baseURL.JoinPath("dns_delete") + + values, err := querystring.Values(record) + if err != nil { + return err + } + + values.Set("domain", domain) + + endpoint.RawQuery = values.Encode() + + req, err := c.newRequest(ctx, endpoint) + if err != nil { + return err + } + + var results APIResponse + + err = c.do(req, &results) + if err != nil { + return err + } + + if results.Result != statusSuccess { + return &results.APIError + } + + return nil +} + +func (c *Client) AddRecord(ctx context.Context, domain string, record Record) error { + endpoint := c.baseURL.JoinPath("dns_add") + + values, err := querystring.Values(record) + if err != nil { + return err + } + + values.Set("domain", domain) + + endpoint.RawQuery = values.Encode() + + req, err := c.newRequest(ctx, endpoint) + if err != nil { + return err + } + + var results APIResponse + + err = c.do(req, &results) + if err != nil { + return err + } + + if results.Result != statusSuccess { + return &results.APIError + } + + return nil +} + +func (c *Client) newRequest(ctx context.Context, endpoint *url.URL) (*http.Request, error) { + query := endpoint.Query() + query.Set("token", c.token) + query.Set("nichdl", c.nickname) + + endpoint.RawQuery = query.Encode() + + return http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) +} + +func (c *Client) do(req *http.Request, result any) error { + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/axelname/internal/client_test.go b/providers/dns/axelname/internal/client_test.go new file mode 100644 index 000000000..d68e2dc5c --- /dev/null +++ b/providers/dns/axelname/internal/client_test.go @@ -0,0 +1,112 @@ +package internal + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupTest(t *testing.T, pattern string, status int, filename string) *Client { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { + if filename == "" { + rw.WriteHeader(status) + return + } + + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + defer func() { _ = file.Close() }() + + rw.WriteHeader(status) + _, err = io.Copy(rw, file) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + }) + + client, err := NewClient("user", "secret") + require.NoError(t, err) + + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) + + return client +} + +func TestClient_ListRecords(t *testing.T) { + client := setupTest(t, "GET /dns_list", http.StatusOK, "dns_list.json") + + records, err := client.ListRecords(context.Background(), "example.com") + require.NoError(t, err) + + expected := []Record{ + {ID: "74749", Name: "example.com", Type: "A", Value: "46.161.54.22"}, + {ID: "417", Name: "example.com", Type: "MX", Value: "mx.yandex.ru.", Prio: "10"}, + {ID: "419", Name: "mail.example.com", Type: "CNAME", Value: "mail.yandex.ru."}, + {ID: "74750", Name: "www.example.com", Type: "A", Value: "46.161.54.22"}, + } + + assert.Equal(t, expected, records) +} + +func TestClient_ListRecords_error(t *testing.T) { + client := setupTest(t, "GET /dns_list", http.StatusNotFound, "dns_list_error.json") + + _, err := client.ListRecords(context.Background(), "example.com") + require.EqualError(t, err, "error: Domain not found (1)") +} + +func TestClient_DeleteRecord(t *testing.T) { + client := setupTest(t, "GET /dns_delete", http.StatusOK, "dns_delete.json") + + record := Record{ID: "74749"} + + err := client.DeleteRecord(context.Background(), "example.com", record) + require.NoError(t, err) +} + +func TestClient_DeleteRecord_error(t *testing.T) { + client := setupTest(t, "GET /dns_delete", http.StatusNotFound, "dns_delete_error.json") + + record := Record{ID: "74749"} + + err := client.DeleteRecord(context.Background(), "example.com", record) + require.EqualError(t, err, "error: Domain not found (1)") +} + +func TestClient_AddRecord(t *testing.T) { + client := setupTest(t, "GET /dns_add", http.StatusOK, "dns_add.json") + + record := Record{ID: "74749"} + + err := client.AddRecord(context.Background(), "example.com", record) + require.NoError(t, err) +} + +func TestClient_AddRecord_error(t *testing.T) { + client := setupTest(t, "GET /dns_add", http.StatusNotFound, "dns_add_error.json") + + record := Record{ID: "74749"} + + err := client.AddRecord(context.Background(), "example.com", record) + require.EqualError(t, err, "error: Domain not found (1)") +} diff --git a/providers/dns/axelname/internal/fixtures/dns_add.json b/providers/dns/axelname/internal/fixtures/dns_add.json new file mode 100644 index 000000000..628813579 --- /dev/null +++ b/providers/dns/axelname/internal/fixtures/dns_add.json @@ -0,0 +1,5 @@ +{ + "code": "OK", + "message": "DNS record added", + "result": "success" +} diff --git a/providers/dns/axelname/internal/fixtures/dns_add_error.json b/providers/dns/axelname/internal/fixtures/dns_add_error.json new file mode 100644 index 000000000..5fb9fd368 --- /dev/null +++ b/providers/dns/axelname/internal/fixtures/dns_add_error.json @@ -0,0 +1,5 @@ +{ + "error_code": "1", + "error_text": "Domain not found", + "result": "error" +} diff --git a/providers/dns/axelname/internal/fixtures/dns_delete.json b/providers/dns/axelname/internal/fixtures/dns_delete.json new file mode 100644 index 000000000..a7851fcc6 --- /dev/null +++ b/providers/dns/axelname/internal/fixtures/dns_delete.json @@ -0,0 +1,5 @@ +{ + "code": "OK", + "message": "DNS record deleted", + "result": "success" +} diff --git a/providers/dns/axelname/internal/fixtures/dns_delete_error.json b/providers/dns/axelname/internal/fixtures/dns_delete_error.json new file mode 100644 index 000000000..5fb9fd368 --- /dev/null +++ b/providers/dns/axelname/internal/fixtures/dns_delete_error.json @@ -0,0 +1,5 @@ +{ + "error_code": "1", + "error_text": "Domain not found", + "result": "error" +} diff --git a/providers/dns/axelname/internal/fixtures/dns_list.json b/providers/dns/axelname/internal/fixtures/dns_list.json new file mode 100644 index 000000000..ace11ba73 --- /dev/null +++ b/providers/dns/axelname/internal/fixtures/dns_list.json @@ -0,0 +1,33 @@ +{ + "code": "OK", + "message": "DNS-records", + "count": 4, + "result": "success", + "list": [ + { + "id": "74749", + "name": "example.com", + "type": "A", + "value": "46.161.54.22" + }, + { + "id": "417", + "name": "example.com", + "type": "MX", + "value": "mx.yandex.ru.", + "prio": "10" + }, + { + "id": "419", + "name": "mail.example.com", + "type": "CNAME", + "value": "mail.yandex.ru." + }, + { + "id": "74750", + "name": "www.example.com", + "type": "A", + "value": "46.161.54.22" + } + ] +} diff --git a/providers/dns/axelname/internal/fixtures/dns_list_error.json b/providers/dns/axelname/internal/fixtures/dns_list_error.json new file mode 100644 index 000000000..5fb9fd368 --- /dev/null +++ b/providers/dns/axelname/internal/fixtures/dns_list_error.json @@ -0,0 +1,5 @@ +{ + "error_code": "1", + "error_text": "Domain not found", + "result": "error" +} diff --git a/providers/dns/axelname/internal/types.go b/providers/dns/axelname/internal/types.go new file mode 100644 index 000000000..45583fb2e --- /dev/null +++ b/providers/dns/axelname/internal/types.go @@ -0,0 +1,35 @@ +package internal + +import "fmt" + +type APIError struct { + ErrorCode string `json:"error_code,omitempty"` + ErrorText string `json:"error_text,omitempty"` + Result string `json:"result,omitempty"` +} + +func (a *APIError) Error() string { + return fmt.Sprintf("%s: %s (%s)", a.Result, a.ErrorText, a.ErrorCode) +} + +type APIResponse struct { + APIError + + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} + +type ListResponse struct { + APIResponse + + Count int `json:"count,omitempty"` + List []Record `json:"list,omitempty"` +} + +type Record struct { + ID string `json:"id,omitempty" url:"id,omitempty"` + Name string `json:"name,omitempty" url:"name,omitempty"` + Type string `json:"type,omitempty" url:"type,omitempty"` + Value string `json:"value,omitempty" url:"value,omitempty"` + Prio string `json:"prio,omitempty" url:"prio,omitempty"` +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index c3c10e379..0b7149903 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/arvancloud" "github.com/go-acme/lego/v4/providers/dns/auroradns" "github.com/go-acme/lego/v4/providers/dns/autodns" + "github.com/go-acme/lego/v4/providers/dns/axelname" "github.com/go-acme/lego/v4/providers/dns/azure" "github.com/go-acme/lego/v4/providers/dns/azuredns" "github.com/go-acme/lego/v4/providers/dns/baiducloud" @@ -179,6 +180,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return auroradns.NewDNSProvider() case "autodns": return autodns.NewDNSProvider() + case "axelname": + return axelname.NewDNSProvider() case "azure": return azure.NewDNSProvider() case "azuredns": From b2faa73e23a1b29033a537ec82536f25163f3e57 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 16 Apr 2025 12:46:23 +0200 Subject: [PATCH 085/298] chore: update linter (#2507) --- .github/workflows/pr.yml | 2 +- .golangci.yml | 168 +++++++++---------- providers/dns/acmedns/acmedns.go | 6 +- providers/dns/googledomains/googledomains.go | 10 +- providers/dns/ovh/ovh.go | 8 +- 5 files changed, 96 insertions(+), 98 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 9fc48680a..49a684366 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: stable - GOLANGCI_LINT_VERSION: v2.0.1 + GOLANGCI_LINT_VERSION: v2.1.1 HUGO_VERSION: 0.131.0 CGO_ENABLED: 0 LEGO_E2E_TESTS: CI diff --git a/.golangci.yml b/.golangci.yml index 7281599f5..ef19b99bf 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -152,6 +152,8 @@ linters: - go-require usetesting: os-setenv: false # we already have a test "framework" to handle env vars + funcorder: + struct-method: false exclusions: warn-unused: true @@ -159,107 +161,103 @@ linters: - comments - std-error-handling rules: - - linters: + - path: (.+)_test.go + linters: - funlen - goconst - maintidx - path: (.+)_test.go - - linters: - - errcheck - path: (.+)_test.go + - path: (.+)_test.go text: Error return value of `fmt.Fprintln` is not checked - - linters: - - gochecknoglobals - path: certcrypto/crypto.go + linters: + - errcheck + - path: certcrypto/crypto.go text: (tlsFeatureExtensionOID|ocspMustStapleFeature) is a global variable - - linters: + linters: - gochecknoglobals - path: challenge/dns01/nameserver.go + - path: challenge/dns01/nameserver.go text: (defaultNameservers|recursiveNameservers|fqdnSoaCache|muFqdnSoaCache) is a global variable - - linters: + linters: - gochecknoglobals - path: challenge/dns01/nameserver_.+.go + - path: challenge/dns01/nameserver_.+.go text: dnsTimeout is a global variable - - linters: + linters: - gochecknoglobals - path: challenge/dns01/nameserver_test.go + - path: challenge/dns01/nameserver_test.go text: findXByFqdnTestCases is a global variable - - linters: - - goconst - path: challenge/http01/domain_matcher.go - text: string `Host` has \d occurrences, make it a constant - - linters: - - gocyclo - path: challenge/http01/domain_matcher.go + linters: + - gochecknoglobals + - path: challenge/http01/domain_matcher.go text: cyclomatic complexity \d+ of func `parseForwardedHeader` is high - - linters: - - funlen - path: challenge/http01/domain_matcher.go + linters: + - gocyclo + - path: challenge/http01/domain_matcher.go text: Function 'parseForwardedHeader' has too many statements - - linters: - - gochecknoglobals - path: challenge/tlsalpn01/tls_alpn_challenge.go - text: idPeAcmeIdentifierV1 is a global variable - - linters: - - gochecknoglobals - path: log/logger.go - text: Logger is a global variable - - linters: - - gochecknoglobals - path: e2e/(dnschallenge/)?[\d\w]+_test.go - text: load is a global variable - - linters: - - gochecknoglobals - path: providers/dns/([\d\w]+/)*[\d\w]+_test.go - text: envTest is a global variable - - linters: - - gochecknoglobals - path: providers/http/([\d\w]+/)*[\d\w]+_test.go - text: envTest is a global variable - - linters: - - gochecknoglobals - path: providers/dns/namecheap/namecheap_test.go - text: testCases is a global variable - - linters: - - gochecknoglobals - path: providers/dns/acmedns/mock_test.go - text: egTestAccount is a global variable - - linters: - - gochecknoglobals - path: providers/http/memcached/memcached_test.go - text: memcachedHosts is a global variable - - linters: - - misspell - path: providers/dns/checkdomain/internal/types.go - text: '`payed` is a misspelling of `paid`' - - linters: - - thelper - path: platform/tester/env_test.go - - linters: - - staticcheck - path: providers/dns/oraclecloud/oraclecloud_test.go - text: 'SA1019: x509.EncryptPEMBlock has been deprecated since Go 1.16' - - linters: - - gochecknoglobals - path: providers/dns/sakuracloud/wrapper.go - text: mu is a global variable - - linters: - - gocyclo - path: cmd/cmd_renew.go - text: cyclomatic complexity \d+ of func `(renewForDomains|renewForCSR)` is high - - linters: + linters: - funlen - path: cmd/cmd_renew.go - text: Function 'renewForDomains' has too many statements - - linters: - - gocyclo - path: providers/dns/cpanel/cpanel.go - text: cyclomatic complexity 13 of func `\(\*DNSProvider\)\.CleanUp` is high - - linters: + - path: challenge/tlsalpn01/tls_alpn_challenge.go + text: idPeAcmeIdentifierV1 is a global variable + linters: + - gochecknoglobals + - path: log/logger.go + text: Logger is a global variable + linters: + - gochecknoglobals + - path: e2e/(dnschallenge/)?[\d\w]+_test.go + text: load is a global variable + linters: + - gochecknoglobals + - path: providers/dns/([\d\w]+/)*[\d\w]+_test.go + text: envTest is a global variable + linters: + - gochecknoglobals + - path: providers/http/([\d\w]+/)*[\d\w]+_test.go + text: envTest is a global variable + linters: + - gochecknoglobals + - path: providers/dns/namecheap/namecheap_test.go + text: testCases is a global variable + linters: + - gochecknoglobals + - path: providers/dns/acmedns/mock_test.go + text: egTestAccount is a global variable + linters: + - gochecknoglobals + - path: providers/http/memcached/memcached_test.go + text: memcachedHosts is a global variable + linters: + - gochecknoglobals + - path: providers/dns/checkdomain/internal/types.go + text: '`payed` is a misspelling of `paid`' + linters: + - misspell + - path: platform/tester/env_test.go + linters: + - thelper + - path: providers/dns/oraclecloud/oraclecloud_test.go + text: 'SA1019: x509.EncryptPEMBlock has been deprecated since Go 1.16' + linters: - staticcheck - # Those elements have been replaced by non-exposed structures. - path: providers/dns/linode/linode_test.go + - path: providers/dns/sakuracloud/wrapper.go + text: mu is a global variable + linters: + - gochecknoglobals + - path: cmd/cmd_renew.go + text: cyclomatic complexity \d+ of func `(renewForDomains|renewForCSR)` is high + linters: + - gocyclo + - path: cmd/cmd_renew.go + text: Function 'renewForDomains' has too many statements + linters: + - funlen + - path: providers/dns/cpanel/cpanel.go + text: cyclomatic complexity 13 of func `\(\*DNSProvider\)\.CleanUp` is high + linters: + - gocyclo + # Those elements have been replaced by non-exposed structures. + - path: providers/dns/linode/linode_test.go text: 'SA1019: linodego\.(DomainsPagedResponse|DomainRecordsPagedResponse) is deprecated' + linters: + - staticcheck issues: max-issues-per-linter: 0 diff --git a/providers/dns/acmedns/acmedns.go b/providers/dns/acmedns/acmedns.go index a5e5a4078..9663a656b 100644 --- a/providers/dns/acmedns/acmedns.go +++ b/providers/dns/acmedns/acmedns.go @@ -115,19 +115,19 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // NewDNSProviderClient creates an ACME-DNS DNSProvider with the given acmeDNSClient and [goacmedns.Storage]. // Deprecated: use [NewDNSProviderConfig] instead. -func NewDNSProviderClient(client acmeDNSClient, storage goacmedns.Storage) (*DNSProvider, error) { +func NewDNSProviderClient(client acmeDNSClient, store goacmedns.Storage) (*DNSProvider, error) { if client == nil { return nil, errors.New("acme-dns: Client must be not nil") } - if storage == nil { + if store == nil { return nil, errors.New("acme-dns: Storage must be not nil") } return &DNSProvider{ config: NewDefaultConfig(), client: client, - storage: storage, + storage: store, }, nil } diff --git a/providers/dns/googledomains/googledomains.go b/providers/dns/googledomains/googledomains.go index aef4454fd..d931b7539 100644 --- a/providers/dns/googledomains/googledomains.go +++ b/providers/dns/googledomains/googledomains.go @@ -46,6 +46,11 @@ func NewDefaultConfig() *Config { } } +type DNSProvider struct { + config *Config + acmedns *acmedns.Service +} + // NewDNSProvider returns the Google Domains DNS provider with a default configuration. func NewDNSProvider() (*DNSProvider, error) { values, err := env.Get(EnvAccessToken) @@ -80,11 +85,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { }, nil } -type DNSProvider struct { - config *Config - acmedns *acmedns.Service -} - func (d *DNSProvider) Present(domain, token, keyAuth string) error { zone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain)) if err != nil { diff --git a/providers/dns/ovh/ovh.go b/providers/dns/ovh/ovh.go index 547a1a47d..c70e943bc 100644 --- a/providers/dns/ovh/ovh.go +++ b/providers/dns/ovh/ovh.go @@ -83,10 +83,6 @@ type Config struct { HTTPClient *http.Client } -func (c *Config) hasAppKeyAuth() bool { - return c.ApplicationKey != "" || c.ApplicationSecret != "" || c.ConsumerKey != "" -} - // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ @@ -99,6 +95,10 @@ func NewDefaultConfig() *Config { } } +func (c *Config) hasAppKeyAuth() bool { + return c.ApplicationKey != "" || c.ApplicationSecret != "" || c.ConsumerKey != "" +} + // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { config *Config From da2aad22156a528bedd4bd3e074441643eea30a6 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 16 Apr 2025 12:57:04 +0200 Subject: [PATCH 086/298] chore: use a fixed version of goreleaser Because of a regression about AUR inside v2.8.2 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a102ad796..ee3ea21dd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,7 +66,7 @@ jobs: - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6 with: - version: latest + version: v2.8.1 args: release -p 1 --clean --timeout=90m env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN_REPO }} From ffaa64a88bf9e7cb2dc8a27d11c73b6911aaa24f Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 16 Apr 2025 12:57:44 +0200 Subject: [PATCH 087/298] Prepare release v4.23.0 --- CHANGELOG.md | 31 +++++++++++++++++++ acme/api/internal/sender/useragent.go | 4 +-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 +-- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf51b1fea..3083c5775 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Changelog +## [v4.23.0](https://github.com/go-acme/lego/releases/tag/v4.23.0) (2025-04-16) + +### Added + +- **[dnsprovider]** Add DNS provider for Active24 +- **[dnsprovider]** Add DNS provider for BookMyName +- **[dnsprovider]** Add DNS provider for Axelname +- **[dnsprovider]** Add DNS provider for Baidu Cloud +- **[dnsprovider]** Add DNS provider for Metaregistrar +- **[dnsprovider]** Add DNS provider for F5 XC +- **[dnsprovider]** Add INFOBLOX_CA_CERTIFICATE option +- **[dnsprovider]** route53: adds option to use private zone +- **[dnsprovider]** edgedns: add account switch key option +- **[dnsprovider]** infoblox: update API client to v2 +- **[lib,cli]** Add delay option for TLSALPN challenge + +### Changed + +- **[dnsprovider]** designate: speed up API requests by using filters +- **[dnsprovider]** cloudflare: make base URL configurable +- **[dnsprovider]** websupport: migrate to API v2 +- **[dnsprovider]** dnssimple: use GetZone + +### Fixed + +- **[ari]** Fix retry on `alreadyReplaced` error +- **[cli,log]** Fix malformed log messages +- **[cli]** Kill hook when the command is stuck +- **[dnsprovider]** pdns: fix TXT record cleanup for wildcard domains +- **[dnsprovider]** allinkl: remove `ReturnInfo` + ## [v4.22.2](https://github.com/go-acme/lego/releases/tag/v4.22.2) (2025-02-17) ### Fixed diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index ec6426456..3cbf3d8c3 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.22.2" + ourUserAgent = "xenolf-acme/4.23.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index b03a531a4..8f4761c17 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.22.2+dev-detach" +const defaultVersion = "v4.23.0+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 4fb495ca7..113204d46 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.22.2" + ourUserAgent = "goacme-lego/4.23.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From ca25f1c83a6b2d5bebd45e62a9ec3938479e8e36 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 16 Apr 2025 12:58:11 +0200 Subject: [PATCH 088/298] Detach v4.23.0 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 3cbf3d8c3..8eecaa303 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 8f4761c17..1cd258767 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.23.0+dev-release" +const defaultVersion = "v4.23.0+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 113204d46..bb8fdb630 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From 42c37d3779697e9f43c581edb60445182ce2a633 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 16 Apr 2025 16:29:34 +0200 Subject: [PATCH 089/298] Prepare release v4.23.1 --- CHANGELOG.md | 6 ++++++ acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3083c5775..4848675c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [v4.23.1](https://github.com/go-acme/lego/releases/tag/v4.23.1) (2025-04-16) + +Due to an error related to Snapcraft, some artifacts of the v4.23.0 release have not been published. + +This release contains the same things as v4.23.0. + ## [v4.23.0](https://github.com/go-acme/lego/releases/tag/v4.23.0) (2025-04-16) ### Added diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 8eecaa303..326cfb4b5 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.23.0" + ourUserAgent = "xenolf-acme/4.23.1" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 1cd258767..8c084b2ba 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.23.0+dev-detach" +const defaultVersion = "v4.23.1+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index bb8fdb630..02ce2303c 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.23.0" + ourUserAgent = "goacme-lego/4.23.1" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From 950d4a0201ebb54d07a4dab3cd12e3b8ceb82937 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 16 Apr 2025 16:35:38 +0200 Subject: [PATCH 090/298] Detach v4.23.1 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 326cfb4b5..a95836245 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 8c084b2ba..e5a0919b8 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.23.1+dev-release" +const defaultVersion = "v4.23.1+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 02ce2303c..2151ea3e0 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From c56d45486b4db4e9b549a84f23d205f75f64b438 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 23 Apr 2025 22:31:19 +0200 Subject: [PATCH 091/298] chore: stream command output for e2e tests (#2513) --- e2e/challenges_test.go | 56 +++++-------------------- e2e/dnschallenge/dns_challenges_test.go | 8 +--- e2e/loader/loader.go | 34 ++++++++++++++- 3 files changed, 45 insertions(+), 53 deletions(-) diff --git a/e2e/challenges_test.go b/e2e/challenges_test.go index 9b3812ce5..59930923b 100644 --- a/e2e/challenges_test.go +++ b/e2e/challenges_test.go @@ -38,7 +38,7 @@ func TestMain(m *testing.M) { } func TestHelp(t *testing.T) { - output, err := load.RunLego("-h") + output, err := load.RunLegoCombinedOutput("-h") if err != nil { fmt.Fprintf(os.Stderr, "%s\n", output) t.Fatal(err) @@ -50,7 +50,7 @@ func TestHelp(t *testing.T) { func TestChallengeHTTP_Run(t *testing.T) { loader.CleanLegoFiles() - output, err := load.RunLego( + err := load.RunLego( "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", @@ -58,10 +58,6 @@ func TestChallengeHTTP_Run(t *testing.T) { "--http", "--http.port", ":5002", "run") - - if len(output) > 0 { - fmt.Fprintf(os.Stdout, "%s\n", output) - } if err != nil { t.Fatal(err) } @@ -70,7 +66,7 @@ func TestChallengeHTTP_Run(t *testing.T) { func TestChallengeTLS_Run_Domains(t *testing.T) { loader.CleanLegoFiles() - output, err := load.RunLego( + err := load.RunLego( "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", @@ -78,10 +74,6 @@ func TestChallengeTLS_Run_Domains(t *testing.T) { "--tls", "--tls.port", ":5001", "run") - - if len(output) > 0 { - fmt.Fprintf(os.Stdout, "%s\n", output) - } if err != nil { t.Fatal(err) } @@ -90,7 +82,7 @@ func TestChallengeTLS_Run_Domains(t *testing.T) { func TestChallengeTLS_Run_IP(t *testing.T) { loader.CleanLegoFiles() - output, err := load.RunLego( + err := load.RunLego( "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", @@ -98,10 +90,6 @@ func TestChallengeTLS_Run_IP(t *testing.T) { "--tls", "--tls.port", ":5001", "run") - - if len(output) > 0 { - fmt.Fprintf(os.Stdout, "%s\n", output) - } if err != nil { t.Fatal(err) } @@ -110,7 +98,7 @@ func TestChallengeTLS_Run_IP(t *testing.T) { func TestChallengeTLS_Run_CSR(t *testing.T) { loader.CleanLegoFiles() - output, err := load.RunLego( + err := load.RunLego( "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", @@ -118,10 +106,6 @@ func TestChallengeTLS_Run_CSR(t *testing.T) { "--tls", "--tls.port", ":5001", "run") - - if len(output) > 0 { - fmt.Fprintf(os.Stdout, "%s\n", output) - } if err != nil { t.Fatal(err) } @@ -130,7 +114,7 @@ func TestChallengeTLS_Run_CSR(t *testing.T) { func TestChallengeTLS_Run_CSR_PEM(t *testing.T) { loader.CleanLegoFiles() - output, err := load.RunLego( + err := load.RunLego( "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", @@ -138,10 +122,6 @@ func TestChallengeTLS_Run_CSR_PEM(t *testing.T) { "--tls", "--tls.port", ":5001", "run") - - if len(output) > 0 { - fmt.Fprintf(os.Stdout, "%s\n", output) - } if err != nil { t.Fatal(err) } @@ -150,7 +130,7 @@ func TestChallengeTLS_Run_CSR_PEM(t *testing.T) { func TestChallengeTLS_Run_Revoke(t *testing.T) { loader.CleanLegoFiles() - output, err := load.RunLego( + err := load.RunLego( "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", @@ -159,15 +139,11 @@ func TestChallengeTLS_Run_Revoke(t *testing.T) { "--tls", "--tls.port", ":5001", "run") - - if len(output) > 0 { - fmt.Fprintf(os.Stdout, "%s\n", output) - } if err != nil { t.Fatal(err) } - output, err = load.RunLego( + err = load.RunLego( "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", @@ -175,10 +151,6 @@ func TestChallengeTLS_Run_Revoke(t *testing.T) { "--tls", "--tls.port", ":5001", "revoke") - - if len(output) > 0 { - fmt.Fprintf(os.Stdout, "%s\n", output) - } if err != nil { t.Fatal(err) } @@ -187,7 +159,7 @@ func TestChallengeTLS_Run_Revoke(t *testing.T) { func TestChallengeTLS_Run_Revoke_Non_ASCII(t *testing.T) { loader.CleanLegoFiles() - output, err := load.RunLego( + err := load.RunLego( "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", @@ -195,15 +167,11 @@ func TestChallengeTLS_Run_Revoke_Non_ASCII(t *testing.T) { "--tls", "--tls.port", ":5001", "run") - - if len(output) > 0 { - fmt.Fprintf(os.Stdout, "%s\n", output) - } if err != nil { t.Fatal(err) } - output, err = load.RunLego( + err = load.RunLego( "-m", "hubert@hubert.com", "--accept-tos", "-s", "https://localhost:14000/dir", @@ -211,10 +179,6 @@ func TestChallengeTLS_Run_Revoke_Non_ASCII(t *testing.T) { "--tls", "--tls.port", ":5001", "revoke") - - if len(output) > 0 { - fmt.Fprintf(os.Stdout, "%s\n", output) - } if err != nil { t.Fatal(err) } diff --git a/e2e/dnschallenge/dns_challenges_test.go b/e2e/dnschallenge/dns_challenges_test.go index 2c228230d..9ae07d46a 100644 --- a/e2e/dnschallenge/dns_challenges_test.go +++ b/e2e/dnschallenge/dns_challenges_test.go @@ -40,7 +40,7 @@ func TestMain(m *testing.M) { } func TestDNSHelp(t *testing.T) { - output, err := load.RunLego("dnshelp") + output, err := load.RunLegoCombinedOutput("dnshelp") if err != nil { fmt.Fprintf(os.Stderr, "%s\n", output) t.Fatal(err) @@ -52,7 +52,7 @@ func TestDNSHelp(t *testing.T) { func TestChallengeDNS_Run(t *testing.T) { loader.CleanLegoFiles() - output, err := load.RunLego( + err := load.RunLego( "-m", "hubert@hubert.com", "--accept-tos", "--dns", "exec", @@ -62,10 +62,6 @@ func TestChallengeDNS_Run(t *testing.T) { "-d", "*.légo.acme", "-d", "légo.acme", "run") - - if len(output) > 0 { - fmt.Fprintf(os.Stdout, "%s\n", output) - } if err != nil { t.Fatal(err) } diff --git a/e2e/loader/loader.go b/e2e/loader/loader.go index 7e8ff539f..5579bb523 100644 --- a/e2e/loader/loader.go +++ b/e2e/loader/loader.go @@ -1,6 +1,7 @@ package loader import ( + "bufio" "bytes" "crypto/tls" "errors" @@ -87,7 +88,7 @@ func (l *EnvLoader) MainTest(m *testing.M) int { return m.Run() } -func (l *EnvLoader) RunLego(arg ...string) ([]byte, error) { +func (l *EnvLoader) RunLegoCombinedOutput(arg ...string) ([]byte, error) { cmd := exec.Command(l.lego, arg...) cmd.Env = l.LegoOptions @@ -96,6 +97,37 @@ func (l *EnvLoader) RunLego(arg ...string) ([]byte, error) { return cmd.CombinedOutput() } +func (l *EnvLoader) RunLego(arg ...string) error { + cmd := exec.Command(l.lego, arg...) + cmd.Env = l.LegoOptions + + fmt.Printf("$ %s\n", strings.Join(cmd.Args, " ")) + + stdout, err := cmd.StdoutPipe() + if err != nil { + return fmt.Errorf("create pipe: %w", err) + } + + cmd.Stderr = cmd.Stdout + + err = cmd.Start() + if err != nil { + return fmt.Errorf("start command: %w", err) + } + + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + println(scanner.Text()) + } + + err = cmd.Wait() + if err != nil { + return fmt.Errorf("wait command: %w", err) + } + + return nil +} + func (l *EnvLoader) launchPebble() func() { if l.PebbleOptions == nil { return func() {} From 1cee2efbdc06481260c4ce306bc0ed0d49a5d953 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 29 Apr 2025 13:56:59 +0200 Subject: [PATCH 092/298] fix: check order identifiers difference between client and server (#2520) --- acme/api/identifier.go | 27 +++++++++ acme/api/identifier_test.go | 111 ++++++++++++++++++++++++++++++++++++ acme/api/order.go | 16 ++++++ 3 files changed, 154 insertions(+) create mode 100644 acme/api/identifier.go create mode 100644 acme/api/identifier_test.go diff --git a/acme/api/identifier.go b/acme/api/identifier.go new file mode 100644 index 000000000..27337ccba --- /dev/null +++ b/acme/api/identifier.go @@ -0,0 +1,27 @@ +package api + +import ( + "cmp" + "slices" + + "github.com/go-acme/lego/v4/acme" +) + +// compareIdentifiers compares 2 slices of [acme.Identifier]. +func compareIdentifiers(a, b []acme.Identifier) int { + // Clones slices to avoid modifying original slices. + right := slices.Clone(a) + left := slices.Clone(b) + + slices.SortStableFunc(right, compareIdentifier) + slices.SortStableFunc(left, compareIdentifier) + + return slices.CompareFunc(right, left, compareIdentifier) +} + +func compareIdentifier(right, left acme.Identifier) int { + return cmp.Or( + cmp.Compare(right.Type, left.Type), + cmp.Compare(right.Value, left.Value), + ) +} diff --git a/acme/api/identifier_test.go b/acme/api/identifier_test.go new file mode 100644 index 000000000..586a87986 --- /dev/null +++ b/acme/api/identifier_test.go @@ -0,0 +1,111 @@ +package api + +import ( + "testing" + + "github.com/go-acme/lego/v4/acme" + "github.com/stretchr/testify/assert" +) + +func Test_compareIdentifiers(t *testing.T) { + testCases := []struct { + desc string + a, b []acme.Identifier + expected int + }{ + { + desc: "identical identifiers", + a: []acme.Identifier{ + {Type: "dns", Value: "example.com"}, + {Type: "dns", Value: "*.example.com"}, + }, + b: []acme.Identifier{ + {Type: "dns", Value: "example.com"}, + {Type: "dns", Value: "*.example.com"}, + }, + expected: 0, + }, + { + desc: "identical identifiers but different order", + a: []acme.Identifier{ + {Type: "dns", Value: "example.com"}, + {Type: "dns", Value: "*.example.com"}, + }, + b: []acme.Identifier{ + {Type: "dns", Value: "*.example.com"}, + {Type: "dns", Value: "example.com"}, + }, + expected: 0, + }, + { + desc: "duplicate identifiers", + a: []acme.Identifier{ + {Type: "dns", Value: "example.com"}, + {Type: "dns", Value: "*.example.com"}, + }, + b: []acme.Identifier{ + {Type: "dns", Value: "example.com"}, + {Type: "dns", Value: "example.com"}, + }, + expected: -1, + }, + { + desc: "different identifier values", + a: []acme.Identifier{ + {Type: "dns", Value: "example.com"}, + {Type: "dns", Value: "*.example.com"}, + }, + b: []acme.Identifier{ + {Type: "dns", Value: "example.com"}, + {Type: "dns", Value: "*.example.org"}, + }, + expected: -1, + }, + { + desc: "different identifier types", + a: []acme.Identifier{ + {Type: "dns", Value: "example.com"}, + {Type: "dns", Value: "*.example.com"}, + }, + b: []acme.Identifier{ + {Type: "dns", Value: "example.com"}, + {Type: "ip", Value: "*.example.com"}, + }, + expected: -1, + }, + { + desc: "different number of identifiers a>b", + a: []acme.Identifier{ + {Type: "dns", Value: "example.com"}, + {Type: "dns", Value: "*.example.com"}, + {Type: "dns", Value: "example.org"}, + }, + b: []acme.Identifier{ + {Type: "dns", Value: "example.com"}, + {Type: "dns", Value: "*.example.com"}, + }, + expected: 1, + }, + { + desc: "different number of identifiers b>a", + a: []acme.Identifier{ + {Type: "dns", Value: "example.com"}, + {Type: "dns", Value: "*.example.com"}, + }, + b: []acme.Identifier{ + {Type: "dns", Value: "example.com"}, + {Type: "dns", Value: "*.example.com"}, + {Type: "dns", Value: "example.org"}, + }, + expected: -1, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + assert.Equal(t, test.expected, compareIdentifiers(test.a, test.b)) + }) + } +} diff --git a/acme/api/order.go b/acme/api/order.go index 4d310e040..dd42fb445 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net" + "slices" "time" "github.com/go-acme/lego/v4/acme" @@ -85,6 +86,21 @@ func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acm } } + // The elements of the "authorizations" and "identifiers" arrays are immutable once set. + // The server MUST NOT change the contents of either array after they are created. + // If a client observes a change in the contents of either array, + // then it SHOULD consider the order invalid. + // https://www.rfc-editor.org/rfc/rfc8555#section-7.1.3 + if compareIdentifiers(orderReq.Identifiers, order.Identifiers) != 0 { + // Sorts identifiers to avoid error message ambiguities about the order of the identifiers. + slices.SortStableFunc(orderReq.Identifiers, compareIdentifier) + slices.SortStableFunc(order.Identifiers, compareIdentifier) + + return acme.ExtendedOrder{}, + fmt.Errorf("order identifiers have been by the ACME server (RFC8555 §7.1.3): %+v != %+v", + orderReq.Identifiers, order.Identifiers) + } + return acme.ExtendedOrder{ Order: order, Location: resp.Header.Get("Location"), From d6df9462231fbea5ed4b42473a09613afd158592 Mon Sep 17 00:00:00 2001 From: Gregor Bigalke Date: Mon, 5 May 2025 00:33:10 +0200 Subject: [PATCH 093/298] cloudflare: add quotation marks to TXT record (#2521) Co-authored-by: Fernandez Ludovic --- providers/dns/cloudflare/cloudflare.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/dns/cloudflare/cloudflare.go b/providers/dns/cloudflare/cloudflare.go index 29cfe0f93..0fa52b34d 100644 --- a/providers/dns/cloudflare/cloudflare.go +++ b/providers/dns/cloudflare/cloudflare.go @@ -169,7 +169,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { dnsRecord := cloudflare.CreateDNSRecordParams{ Type: "TXT", Name: dns01.UnFqdn(info.EffectiveFQDN), - Content: info.Value, + Content: `"` + info.Value + `"`, TTL: d.config.TTL, } From b82e6d88e418ef209044c710ded14dae52159f25 Mon Sep 17 00:00:00 2001 From: msshtdev Date: Tue, 6 May 2025 05:45:56 +0900 Subject: [PATCH 094/298] Add DNS provider for ConoHa v3 (#2516) Co-authored-by: Fernandez Ludovic --- README.md | 68 +++--- cmd/zz_gen_cmd_dnshelp.go | 26 ++- docs/content/dns/zz_gen_conoha.md | 8 +- docs/content/dns/zz_gen_conohav3.md | 72 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/conoha/conoha.toml | 4 +- providers/dns/conoha/internal/client.go | 8 +- providers/dns/conoha/internal/identity.go | 2 +- providers/dns/conohav3/conohav3.go | 198 ++++++++++++++++ providers/dns/conohav3/conohav3.toml | 27 +++ providers/dns/conohav3/conohav3_test.go | 178 +++++++++++++++ providers/dns/conohav3/internal/client.go | 203 +++++++++++++++++ .../dns/conohav3/internal/client_test.go | 212 ++++++++++++++++++ .../fixtures/domains-records_GET.json | 43 ++++ .../fixtures/domains-records_POST.json | 13 ++ .../internal/fixtures/domains_GET.json | 25 +++ providers/dns/conohav3/internal/identity.go | 70 ++++++ .../dns/conohav3/internal/identity_test.go | 52 +++++ providers/dns/conohav3/internal/types.go | 65 ++++++ providers/dns/zz_gen_dns_providers.go | 3 + 20 files changed, 1232 insertions(+), 47 deletions(-) create mode 100644 docs/content/dns/zz_gen_conohav3.md create mode 100644 providers/dns/conohav3/conohav3.go create mode 100644 providers/dns/conohav3/conohav3.toml create mode 100644 providers/dns/conohav3/conohav3_test.go create mode 100644 providers/dns/conohav3/internal/client.go create mode 100644 providers/dns/conohav3/internal/client_test.go create mode 100644 providers/dns/conohav3/internal/fixtures/domains-records_GET.json create mode 100644 providers/dns/conohav3/internal/fixtures/domains-records_POST.json create mode 100644 providers/dns/conohav3/internal/fixtures/domains_GET.json create mode 100644 providers/dns/conohav3/internal/identity.go create mode 100644 providers/dns/conohav3/internal/identity_test.go create mode 100644 providers/dns/conohav3/internal/types.go diff --git a/README.md b/README.md index b22f67bfa..587c77c8d 100644 --- a/README.md +++ b/README.md @@ -84,169 +84,169 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). ClouDNS CloudXNS (Deprecated) - ConoHa + ConoHa v2 + ConoHa v3 Constellix - Core-Networks + Core-Networks CPanel/WHM Derak Cloud deSEC.io - Designate DNSaaS for Openstack + Designate DNSaaS for Openstack Digital Ocean DirectAdmin DNS Made Easy - dnsHome.de + dnsHome.de DNSimple DNSPod (deprecated) Domain Offensive (do.de) - Domeneshop + Domeneshop DreamHost Duck DNS Dyn - Dynu + Dynu EasyDNS Efficient IP Epik - Exoscale + Exoscale External program F5 XC freemyip.com - G-Core + G-Core Gandi Gandi Live DNS (v5) Glesys - Go Daddy + Go Daddy Google Cloud Google Domains Hetzner - Hosting.de + Hosting.de Hosttech HTTP request http.net - Huawei Cloud + Huawei Cloud Hurricane Electric DNS HyperOne IBM Cloud (SoftLayer) - IIJ DNS Platform Service + IIJ DNS Platform Service Infoblox Infomaniak Internet Initiative Japan - Internet.bs + Internet.bs INWX Ionos IPv64 - iwantmyname + iwantmyname Joker Joohoi's ACME-DNS Liara - Lima-City + Lima-City Linode (v4) Liquid Web Loopia - LuaDNS + LuaDNS Mail-in-a-Box ManageEngine CloudDNS Manual - Metaname + Metaname Metaregistrar mijn.host Mittwald - myaddr.{tools,dev,io} + myaddr.{tools,dev,io} MyDNS.jp MythicBeasts Name.com - Namecheap + Namecheap Namesilo NearlyFreeSpeech.NET Netcup - Netlify + Netlify Nicmanager NIFCloud Njalla - Nodion + Nodion NS1 Open Telekom Cloud Oracle Cloud - OVH + OVH plesk.com Porkbun PowerDNS - Rackspace + Rackspace Rain Yun/雨云 RcodeZero reg.ru - Regfish + Regfish RFC2136 RimuHosting Sakura Cloud - Scaleway + Scaleway Selectel Selectel v2 SelfHost.(de|eu) - Servercow + Servercow Shellrent Simply.com Sonic - Spaceship + Spaceship Stackpath Technitium 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 - West.cn/西部数码 + West.cn/西部数码 Yandex 360 Yandex Cloud Yandex PDD - Zone.ee + Zone.ee Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index a409d5ad9..e93c94e50 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -37,6 +37,7 @@ func allDNSCodes() string { "cloudru", "cloudxns", "conoha", + "conohav3", "constellix", "corenetworks", "cpanel", @@ -684,7 +685,7 @@ func displayDNSHelp(w io.Writer, name string) error { case "conoha": // generated from: providers/dns/conoha/conoha.toml - ew.writeln(`Configuration for ConoHa.`) + ew.writeln(`Configuration for ConoHa v2.`) ew.writeln(`Code: 'conoha'`) ew.writeln(`Since: 'v1.2.0'`) ew.writeln() @@ -705,6 +706,29 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/conoha`) + case "conohav3": + // generated from: providers/dns/conohav3/conohav3.toml + ew.writeln(`Configuration for ConoHa v3.`) + ew.writeln(`Code: 'conohav3'`) + ew.writeln(`Since: 'v4.24.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "CONOHAV3_API_PASSWORD": The API password`) + ew.writeln(` - "CONOHAV3_API_USER_ID": The API user ID`) + ew.writeln(` - "CONOHAV3_TENANT_ID": Tenant ID`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "CONOHAV3_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "CONOHAV3_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "CONOHAV3_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "CONOHAV3_REGION": The region (Default: c3j1)`) + ew.writeln(` - "CONOHAV3_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/conohav3`) + case "constellix": // generated from: providers/dns/constellix/constellix.toml ew.writeln(`Configuration for Constellix.`) diff --git a/docs/content/dns/zz_gen_conoha.md b/docs/content/dns/zz_gen_conoha.md index bf5c0582d..4d5f84660 100644 --- a/docs/content/dns/zz_gen_conoha.md +++ b/docs/content/dns/zz_gen_conoha.md @@ -1,5 +1,5 @@ --- -title: "ConoHa" +title: "ConoHa v2" date: 2019-03-03T16:39:46+01:00 draft: false slug: conoha @@ -14,7 +14,7 @@ dnsprovider: -Configuration for [ConoHa](https://www.conoha.jp/). +Configuration for [ConoHa v2](https://www.conoha.jp/). @@ -23,7 +23,7 @@ Configuration for [ConoHa](https://www.conoha.jp/). - Since: v1.2.0 -Here is an example bash command using the ConoHa provider: +Here is an example bash command using the ConoHa v2 provider: ```bash CONOHA_TENANT_ID=487727e3921d44e3bfe7ebb337bf085e \ @@ -65,7 +65,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). ## More information -- [API documentation](https://www.conoha.jp/docs/) +- [API documentation](https://doc.conoha.jp/reference/api-vps2/api-dns-vps2) diff --git a/docs/content/dns/zz_gen_conohav3.md b/docs/content/dns/zz_gen_conohav3.md new file mode 100644 index 000000000..208f2f91b --- /dev/null +++ b/docs/content/dns/zz_gen_conohav3.md @@ -0,0 +1,72 @@ +--- +title: "ConoHa v3" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: conohav3 +dnsprovider: + since: "v4.24.0" + code: "conohav3" + url: "https://www.conoha.jp/" +--- + + + + + + +Configuration for [ConoHa v3](https://www.conoha.jp/). + + + + +- Code: `conohav3` +- Since: v4.24.0 + + +Here is an example bash command using the ConoHa v3 provider: + +```bash +CONOHAV3_TENANT_ID=487727e3921d44e3bfe7ebb337bf085e \ +CONOHAV3_API_USER_ID=xxxx \ +CONOHAV3_API_PASSWORD=yyyy \ +lego --email you@example.com --dns conohav3 -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `CONOHAV3_API_PASSWORD` | The API password | +| `CONOHAV3_API_USER_ID` | The API user ID | +| `CONOHAV3_TENANT_ID` | Tenant ID | + +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 | +|--------------------------------|-------------| +| `CONOHAV3_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `CONOHAV3_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `CONOHAV3_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `CONOHAV3_REGION` | The region (Default: c3j1) | +| `CONOHAV3_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | + +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://doc.conoha.jp/reference/api-vps3/api-dns-vps3/) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index b15ba00fb..614d2f8b5 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -150,7 +150,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, 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, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi + acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/conoha/conoha.toml b/providers/dns/conoha/conoha.toml index 92c45a555..8bd83247e 100644 --- a/providers/dns/conoha/conoha.toml +++ b/providers/dns/conoha/conoha.toml @@ -1,4 +1,4 @@ -Name = "ConoHa" +Name = "ConoHa v2" Description = '''''' URL = "https://www.conoha.jp/" Code = "conoha" @@ -24,4 +24,4 @@ lego --email you@example.com --dns conoha -d '*.example.com' -d example.com run CONOHA_REGION = "The region (Default: tyo1)" [Links] - API = "https://www.conoha.jp/docs/" + API = "https://doc.conoha.jp/reference/api-vps2/api-dns-vps2" diff --git a/providers/dns/conoha/internal/client.go b/providers/dns/conoha/internal/client.go index 87fbe5a0b..a1487c9d7 100644 --- a/providers/dns/conoha/internal/client.go +++ b/providers/dns/conoha/internal/client.go @@ -54,7 +54,7 @@ func (c *Client) GetDomainID(ctx context.Context, domainName string) (string, er return "", fmt.Errorf("no such domain: %s", domainName) } -// https://www.conoha.jp/docs/paas-dns-list-domains.php +// https://doc.conoha.jp/reference/api-vps2/api-dns-vps2/paas-dns-list-domains-v2/?btn_id=reference-api-vps2--sidebar_reference-paas-dns-list-domains-v2 func (c *Client) getDomains(ctx context.Context) (*DomainListResponse, error) { endpoint := c.baseURL.JoinPath("v1", "domains") @@ -89,7 +89,7 @@ func (c *Client) GetRecordID(ctx context.Context, domainID, recordName, recordTy return "", errors.New("no such record") } -// https://www.conoha.jp/docs/paas-dns-list-records-in-a-domain.php +// https://doc.conoha.jp/reference/api-vps2/api-dns-vps2/paas-dns-list-records-in-a-domain-v2/?btn_id=reference-paas-dns-list-domains-v2--sidebar_reference-paas-dns-list-records-in-a-domain-v2 func (c *Client) getRecords(ctx context.Context, domainID string) (*RecordListResponse, error) { endpoint := c.baseURL.JoinPath("v1", "domains", domainID, "records") @@ -114,7 +114,7 @@ func (c *Client) CreateRecord(ctx context.Context, domainID string, record Recor return err } -// https://www.conoha.jp/docs/paas-dns-create-record.php +// https://doc.conoha.jp/reference/api-vps2/api-dns-vps2/paas-dns-create-record-v2/?btn_id=reference-paas-dns-list-records-in-a-domain-v2--sidebar_reference-paas-dns-create-record-v2 func (c *Client) createRecord(ctx context.Context, domainID string, record Record) (*Record, error) { endpoint := c.baseURL.JoinPath("v1", "domains", domainID, "records") @@ -133,7 +133,7 @@ func (c *Client) createRecord(ctx context.Context, domainID string, record Recor } // DeleteRecord removes specified record. -// https://www.conoha.jp/docs/paas-dns-delete-a-record.php +// https://doc.conoha.jp/reference/api-vps2/api-dns-vps2/paas-dns-delete-a-record-v2/?btn_id=reference-paas-dns-create-record-v2--sidebar_reference-paas-dns-delete-a-record-v2 func (c *Client) DeleteRecord(ctx context.Context, domainID, recordID string) error { endpoint := c.baseURL.JoinPath("v1", "domains", domainID, "records", recordID) diff --git a/providers/dns/conoha/internal/identity.go b/providers/dns/conoha/internal/identity.go index 995d55bb6..54fc46bc5 100644 --- a/providers/dns/conoha/internal/identity.go +++ b/providers/dns/conoha/internal/identity.go @@ -33,7 +33,7 @@ func NewIdentifier(region string) (*Identifier, error) { } // GetToken gets valid token information. -// https://www.conoha.jp/docs/identity-post_tokens.php +// https://doc.conoha.jp/reference/api-vps2/api-identity-vps2/identity-post_tokens-v2/?btn_id=reference-paas-dns-delete-a-record-v2--sidebar_reference-identity-post_tokens-v2 func (c *Identifier) GetToken(ctx context.Context, auth Auth) (*IdentityResponse, error) { endpoint := c.baseURL.JoinPath("v2.0", "tokens") diff --git a/providers/dns/conohav3/conohav3.go b/providers/dns/conohav3/conohav3.go new file mode 100644 index 000000000..a6cb12cb1 --- /dev/null +++ b/providers/dns/conohav3/conohav3.go @@ -0,0 +1,198 @@ +// Package conohav3 implements a DNS provider for solving the DNS-01 challenge using ConoHa VPS Ver 3.0 DNS. +package conohav3 + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/conohav3/internal" +) + +// Environment variables names. +const ( + envNamespace = "CONOHAV3_" + + EnvRegion = envNamespace + "REGION" + EnvTenantID = envNamespace + "TENANT_ID" + EnvAPIUserID = envNamespace + "API_USER_ID" + EnvAPIPassword = envNamespace + "API_PASSWORD" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + Region string + TenantID string + UserID string + Password string + TTL int + PropagationTimeout time.Duration + PollingInterval time.Duration + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + Region: env.GetOrDefaultString(EnvRegion, "c3j1"), + TTL: env.GetOrDefaultInt(EnvTTL, 60), + 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for ConoHa DNS. +// Credentials must be passed in the environment variables: +// CONOHAV3_TENANT_ID, CONOHAV3_API_USER_ID, CONOHAV3_API_PASSWORD. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvTenantID, EnvAPIUserID, EnvAPIPassword) + if err != nil { + return nil, fmt.Errorf("conohav3: %w", err) + } + + config := NewDefaultConfig() + config.TenantID = values[EnvTenantID] + config.UserID = values[EnvAPIUserID] + config.Password = values[EnvAPIPassword] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for ConoHa DNS. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("conohav3: the configuration of the DNS provider is nil") + } + + if config.TenantID == "" || config.UserID == "" || config.Password == "" { + return nil, errors.New("conohav3: some credentials information are missing") + } + + identifier, err := internal.NewIdentifier(config.Region) + if err != nil { + return nil, fmt.Errorf("conohav3: failed to create identity client: %w", err) + } + + if config.HTTPClient != nil { + identifier.HTTPClient = config.HTTPClient + } + + auth := internal.Auth{ + Identity: internal.Identity{ + Methods: []string{"password"}, + Password: internal.Password{ + User: internal.User{ + ID: config.UserID, + Password: config.Password, + }, + }, + }, + Scope: internal.Scope{ + Project: internal.Project{ + ID: config.TenantID, + }, + }, + } + + token, err := identifier.GetToken(context.Background(), auth) + if err != nil { + return nil, fmt.Errorf("conohav3: failed to log in: %w", err) + } + + client, err := internal.NewClient(config.Region, token) + if err != nil { + return nil, fmt.Errorf("conohav3: failed to create client: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{config: config, client: client}, nil +} + +// Present creates a TXT record to fulfill the dns-01 challenge. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("conohav3: could not find zone for domain %q: %w", domain, err) + } + + ctx := context.Background() + + id, err := d.client.GetDomainID(ctx, authZone) + if err != nil { + return fmt.Errorf("conohav3: failed to get domain ID: %w", err) + } + + record := internal.Record{ + Name: info.EffectiveFQDN, + Type: "TXT", + Data: info.Value, + TTL: d.config.TTL, + } + + err = d.client.CreateRecord(ctx, id, record) + if err != nil { + return fmt.Errorf("conohav3: failed to create record: %w", err) + } + + return nil +} + +// CleanUp clears ConoHa DNS TXT record. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("conohav3: could not find zone for domain %q: %w", domain, err) + } + + ctx := context.Background() + + domID, err := d.client.GetDomainID(ctx, authZone) + if err != nil { + return fmt.Errorf("conohav3: failed to get domain ID: %w", err) + } + + recID, err := d.client.GetRecordID(ctx, domID, info.EffectiveFQDN, "TXT", info.Value) + if err != nil { + return fmt.Errorf("conohav3: failed to get record ID: %w", err) + } + + err = d.client.DeleteRecord(ctx, domID, recID) + if err != nil { + return fmt.Errorf("conohav3: failed to delete record: %w", err) + } + + return nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} diff --git a/providers/dns/conohav3/conohav3.toml b/providers/dns/conohav3/conohav3.toml new file mode 100644 index 000000000..7608e6742 --- /dev/null +++ b/providers/dns/conohav3/conohav3.toml @@ -0,0 +1,27 @@ +Name = "ConoHa v3" +Description = '''''' +URL = "https://www.conoha.jp/" +Code = "conohav3" +Since = "v4.24.0" + +Example = ''' +CONOHAV3_TENANT_ID=487727e3921d44e3bfe7ebb337bf085e \ +CONOHAV3_API_USER_ID=xxxx \ +CONOHAV3_API_PASSWORD=yyyy \ +lego --email you@example.com --dns conohav3 -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + CONOHAV3_TENANT_ID = "Tenant ID" + CONOHAV3_API_USER_ID = "The API user ID" + CONOHAV3_API_PASSWORD = "The API password" + [Configuration.Additional] + CONOHAV3_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + CONOHAV3_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + CONOHAV3_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + CONOHAV3_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + CONOHAV3_REGION = "The region (Default: c3j1)" + +[Links] + API = "https://doc.conoha.jp/reference/api-vps3/api-dns-vps3/" diff --git a/providers/dns/conohav3/conohav3_test.go b/providers/dns/conohav3/conohav3_test.go new file mode 100644 index 000000000..7bba8f0b5 --- /dev/null +++ b/providers/dns/conohav3/conohav3_test.go @@ -0,0 +1,178 @@ +package conohav3 + +import ( + "testing" + "time" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest( + EnvTenantID, + EnvAPIUserID, + EnvAPIPassword). + WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "complete credentials, but login failed", + envVars: map[string]string{ + EnvTenantID: "tenant_id", + EnvAPIUserID: "api_user_id", + EnvAPIPassword: "api_password", + }, + expected: `conohav3: failed to log in: unexpected status code: [status code: 400] body: {"code": 400, "error": "user does not exist"}`, + }, + { + desc: "missing credentials", + envVars: map[string]string{ + EnvTenantID: "", + EnvAPIUserID: "", + EnvAPIPassword: "", + }, + expected: "conohav3: some credentials information are missing: CONOHAV3_TENANT_ID,CONOHAV3_API_USER_ID,CONOHAV3_API_PASSWORD", + }, + { + desc: "missing tenant id", + envVars: map[string]string{ + EnvTenantID: "", + EnvAPIUserID: "api_user_id", + EnvAPIPassword: "api_password", + }, + expected: "conohav3: some credentials information are missing: CONOHAV3_TENANT_ID", + }, + { + desc: "missing api user id", + envVars: map[string]string{ + EnvTenantID: "tenant_id", + EnvAPIUserID: "", + EnvAPIPassword: "api_password", + }, + expected: "conohav3: some credentials information are missing: CONOHAV3_API_USER_ID", + }, + { + desc: "missing api password", + envVars: map[string]string{ + EnvTenantID: "tenant_id", + EnvAPIUserID: "api_user_id", + EnvAPIPassword: "", + }, + expected: "conohav3: some credentials information are missing: CONOHAV3_API_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 test.expected == "" { + 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 + expected string + tenant string + userid string + password string + }{ + { + desc: "complete credentials, but login failed", + expected: `conohav3: failed to log in: unexpected status code: [status code: 400] body: {"code": 400, "error": "user does not exist"}`, + tenant: "tenant_id", + userid: "api_user_id", + password: "api_password", + }, + { + desc: "missing credentials", + expected: "conohav3: some credentials information are missing", + }, + { + desc: "missing tenant id", + expected: "conohav3: some credentials information are missing", + userid: "api_user_id", + password: "api_password", + }, + { + desc: "missing api user id", + expected: "conohav3: some credentials information are missing", + tenant: "tenant_id", + password: "api_password", + }, + { + desc: "missing api password", + expected: "conohav3: some credentials information are missing", + tenant: "tenant_id", + userid: "api_user_id", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.TenantID = test.tenant + config.UserID = test.userid + config.Password = test.password + + 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) + + time.Sleep(1 * time.Second) + + err = provider.CleanUp(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/conohav3/internal/client.go b/providers/dns/conohav3/internal/client.go new file mode 100644 index 000000000..9fac8f366 --- /dev/null +++ b/providers/dns/conohav3/internal/client.go @@ -0,0 +1,203 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const dnsServiceBaseURL = "https://dns-service.%s.conoha.io" + +// Client is a ConoHa API client. +type Client struct { + token string + + baseURL *url.URL + HTTPClient *http.Client +} + +// NewClient returns a client instance logged into the ConoHa service. +func NewClient(region string, token string) (*Client, error) { + baseURL, err := url.Parse(fmt.Sprintf(dnsServiceBaseURL, region)) + if err != nil { + return nil, err + } + + return &Client{ + token: token, + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 5 * time.Second}, + }, nil +} + +// GetDomainID returns an ID of specified domain. +func (c *Client) GetDomainID(ctx context.Context, domainName string) (string, error) { + domainList, err := c.getDomains(ctx) + if err != nil { + return "", err + } + + for _, domain := range domainList.Domains { + if domain.Name == domainName { + return domain.UUID, nil + } + } + + return "", fmt.Errorf("no such domain: %s", domainName) +} + +// https://doc.conoha.jp/reference/api-vps3/api-dns-vps3/dnsaas-get_domains_list-v3/?btn_id=reference-api-vps3--sidebar_reference-dnsaas-get_domains_list-v3 +func (c *Client) getDomains(ctx context.Context) (*DomainListResponse, error) { + endpoint := c.baseURL.JoinPath("v1", "domains") + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + domainList := &DomainListResponse{} + + err = c.do(req, domainList) + if err != nil { + return nil, err + } + + return domainList, nil +} + +// GetRecordID returns an ID of specified record. +func (c *Client) GetRecordID(ctx context.Context, domainID, recordName, recordType, data string) (string, error) { + recordList, err := c.getRecords(ctx, domainID) + if err != nil { + return "", err + } + + for _, record := range recordList.Records { + if record.Name == recordName && record.Type == recordType && record.Data == data { + return record.UUID, nil + } + } + + return "", errors.New("no such record") +} + +// https://doc.conoha.jp/reference/api-vps3/api-dns-vps3/dnsaas-get_records_list-v3/?btn_id=reference-dnsaas-get_domains_list-v3--sidebar_reference-dnsaas-get_records_list-v3 +func (c *Client) getRecords(ctx context.Context, domainID string) (*RecordListResponse, error) { + endpoint := c.baseURL.JoinPath("v1", "domains", domainID, "records") + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + recordList := &RecordListResponse{} + + err = c.do(req, recordList) + if err != nil { + return nil, err + } + + return recordList, nil +} + +// CreateRecord adds new record. +func (c *Client) CreateRecord(ctx context.Context, domainID string, record Record) error { + _, err := c.createRecord(ctx, domainID, record) + return err +} + +// https://doc.conoha.jp/reference/api-vps3/api-dns-vps3/dnsaas-create_record-v3/?btn_id=reference-dnsaas-get_records_list-v3--sidebar_reference-dnsaas-create_record-v3 +func (c *Client) createRecord(ctx context.Context, domainID string, record Record) (*Record, error) { + endpoint := c.baseURL.JoinPath("v1", "domains", domainID, "records") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) + if err != nil { + return nil, err + } + + newRecord := &Record{} + err = c.do(req, newRecord) + if err != nil { + return nil, err + } + + return newRecord, nil +} + +// DeleteRecord removes specified record. +// https://doc.conoha.jp/reference/api-vps3/api-dns-vps3/dnsaas-delete_record-v3/?btn_id=reference-dnsaas-create_record-v3--sidebar_reference-dnsaas-delete_record-v3 +func (c *Client) DeleteRecord(ctx context.Context, domainID, recordID string) error { + endpoint := c.baseURL.JoinPath("v1", "domains", domainID, "records", recordID) + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) do(req *http.Request, result any) error { + if c.token != "" { + req.Header.Set("X-Auth-Token", c.token) + } + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { + return errutils.NewUnexpectedResponseStatusCodeError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} diff --git a/providers/dns/conohav3/internal/client_test.go b/providers/dns/conohav3/internal/client_test.go new file mode 100644 index 000000000..171e7ba2f --- /dev/null +++ b/providers/dns/conohav3/internal/client_test.go @@ -0,0 +1,212 @@ +package internal + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupTest(t *testing.T) (*Client, *http.ServeMux) { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + client, err := NewClient("c3j1", "secret") + require.NoError(t, err) + + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) + + return client, mux +} + +func writeFixtureHandler(method, filename string) http.HandlerFunc { + return func(rw http.ResponseWriter, req *http.Request) { + if req.Method != method { + http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) + return + } + + writeFixture(rw, filename) + } +} + +func writeBodyHandler(method, content string) http.HandlerFunc { + return func(rw http.ResponseWriter, req *http.Request) { + if req.Method != method { + http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) + return + } + _, err := fmt.Fprint(rw, content) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + } +} + +func writeFixture(rw http.ResponseWriter, filename string) { + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + defer func() { _ = file.Close() }() + + _, _ = io.Copy(rw, file) +} + +func TestClient_GetDomainID(t *testing.T) { + type expected struct { + domainID string + error bool + } + + testCases := []struct { + desc string + domainName string + handler http.HandlerFunc + expected expected + }{ + { + desc: "success", + domainName: "domain1.com.", + handler: writeFixtureHandler(http.MethodGet, "domains_GET.json"), + expected: expected{domainID: "09494b72-b65b-4297-9efb-187f65a0553e"}, + }, + { + desc: "non existing domain", + domainName: "domain1.com.", + handler: writeBodyHandler(http.MethodGet, "{}"), + expected: expected{error: true}, + }, + { + desc: "marshaling error", + domainName: "domain1.com.", + handler: writeBodyHandler(http.MethodGet, "[]"), + expected: expected{error: true}, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + client, mux := setupTest(t) + + mux.Handle("/v1/domains", test.handler) + + domainID, err := client.GetDomainID(context.Background(), test.domainName) + + if test.expected.error { + require.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, test.expected.domainID, domainID) + } + }) + } +} + +func TestClient_CreateRecord(t *testing.T) { + testCases := []struct { + desc string + handler http.HandlerFunc + assert require.ErrorAssertionFunc + }{ + { + desc: "success", + handler: func(rw http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) + return + } + + raw, err := io.ReadAll(req.Body) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + defer func() { _ = req.Body.Close() }() + + if string(bytes.TrimSpace(raw)) != `{"name":"lego.com.","type":"TXT","data":"txtTXTtxt","ttl":300}` { + http.Error(rw, fmt.Sprintf("invalid request body: %s", string(raw)), http.StatusBadRequest) + return + } + + writeFixture(rw, "domains-records_POST.json") + }, + assert: require.NoError, + }, + { + desc: "bad request", + handler: func(rw http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) + return + } + + http.Error(rw, "OOPS", http.StatusBadRequest) + }, + assert: require.Error, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + client, mux := setupTest(t) + + mux.Handle("/v1/domains/lego/records", test.handler) + + domainID := "lego" + + record := Record{ + Name: "lego.com.", + Type: "TXT", + Data: "txtTXTtxt", + TTL: 300, + } + + err := client.CreateRecord(context.Background(), domainID, record) + test.assert(t, err) + }) + } +} + +func TestClient_GetRecordID(t *testing.T) { + client, mux := setupTest(t) + + mux.HandleFunc("/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records", + writeFixtureHandler(http.MethodGet, "domains-records_GET.json")) + + recordID, err := client.GetRecordID(context.Background(), "89acac79-38e7-497d-807c-a011e1310438", "www.example.com.", "A", "15.185.172.153") + require.NoError(t, err) + + assert.Equal(t, "2e32e609-3a4f-45ba-bdef-e50eacd345ad", recordID) +} + +func TestClient_DeleteRecord(t *testing.T) { + client, mux := setupTest(t) + + mux.HandleFunc("/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records/2e32e609-3a4f-45ba-bdef-e50eacd345ad", func(rw http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodDelete { + http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) + return + } + + rw.WriteHeader(http.StatusOK) + }) + + err := client.DeleteRecord(context.Background(), "89acac79-38e7-497d-807c-a011e1310438", "2e32e609-3a4f-45ba-bdef-e50eacd345ad") + require.NoError(t, err) +} diff --git a/providers/dns/conohav3/internal/fixtures/domains-records_GET.json b/providers/dns/conohav3/internal/fixtures/domains-records_GET.json new file mode 100644 index 000000000..f982c1911 --- /dev/null +++ b/providers/dns/conohav3/internal/fixtures/domains-records_GET.json @@ -0,0 +1,43 @@ +{ + "records": [ + { + "uuid": "2e32e609-3a4f-45ba-bdef-e50eacd345ad", + "name": "www.example.com.", + "type": "A", + "ttl": 3600, + "created_at": "2012-11-02T19:56:26.000000", + "updated_at": "2012-11-04T13:22:36.000000", + "data": "15.185.172.153", + "domain_id": "89acac79-38e7-497d-807c-a011e1310438", + "version": 1, + "gslb_region": "JP", + "gslb_weight": 250, + "gslb_check": 12300 + }, + { + "uuid": "8e9ecf3e-fb92-4a3a-a8ae-7596f167bea3", + "name": "host1.example.com.", + "type": "A", + "ttl": 3600, + "created_at": "2012-11-04T13:57:50.000000", + "updated_at": null, + "data": "15.185.172.154", + "domain_id": "89acac79-38e7-497d-807c-a011e1310438", + "version": 1, + "gslb_region": "US", + "gslb_weight": 220, + "gslb_check": 12200 + }, + { + "uuid": "4ad19089-3e62-40f8-9482-17cc8ccb92cb", + "name": "web.example.com.", + "type": "CNAME", + "ttl": 3600, + "created_at": "2012-11-04T13:58:16.393735", + "updated_at": null, + "data": "www.example.com.", + "domain_id": "89acac79-38e7-497d-807c-a011e1310438", + "version": 1 + } + ] +} diff --git a/providers/dns/conohav3/internal/fixtures/domains-records_POST.json b/providers/dns/conohav3/internal/fixtures/domains-records_POST.json new file mode 100644 index 000000000..d0f71c03e --- /dev/null +++ b/providers/dns/conohav3/internal/fixtures/domains-records_POST.json @@ -0,0 +1,13 @@ +{ + "uuid": "2e32e609-3a4f-45ba-bdef-e50eacd345ad", + "name": "www.example.com.", + "type": "A", + "created_at": "2012-11-02T19:56:26.366792", + "updated_at": null, + "domain_id": "89acac79-38e7-497d-807c-a011e1310438", + "ttl": null, + "data": "192.0.2.3", + "gslb_check": 1, + "gslb_region": "JP", + "gslb_weight": 250 +} diff --git a/providers/dns/conohav3/internal/fixtures/domains_GET.json b/providers/dns/conohav3/internal/fixtures/domains_GET.json new file mode 100644 index 000000000..6f8603a57 --- /dev/null +++ b/providers/dns/conohav3/internal/fixtures/domains_GET.json @@ -0,0 +1,25 @@ +{ + "domains": [ + { + "uuid": "09494b72-b65b-4297-9efb-187f65a0553e", + "name": "domain1.com.", + "project_id": "cf661142-e577-40b5-b3eb-75795cdc0cd7", + "serial": 1701909248, + "ttl": 3600, + "email": "nsadmin1@example.org", + "created_at": "2023-12-07T00:34:08Z", + "updated_at": "2023-12-07T00:34:08Z" + }, + { + "uuid": "cf661142-e577-40b5-b3eb-75795cdc0cd7", + "name": "domain2.com.", + "project_id": "cf661144-e578-39b6-b4eb-75794cdc1cd8", + "serial": 1351800670, + "ttl": 7200, + "email": "nsadmin2@example.org", + "created_at": "2012-11-01T20:11:08Z", + "updated_at": "2012-12-01T20:11:08Z" + } + ], + "total_count": 1 +} diff --git a/providers/dns/conohav3/internal/identity.go b/providers/dns/conohav3/internal/identity.go new file mode 100644 index 000000000..3bb7355ae --- /dev/null +++ b/providers/dns/conohav3/internal/identity.go @@ -0,0 +1,70 @@ +// internal/identity.go + +package internal + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const identityBaseURL = "https://identity.%s.conoha.io" + +type Identifier struct { + baseURL *url.URL + HTTPClient *http.Client +} + +// NewIdentifier creates a new Identifier. +func NewIdentifier(region string) (*Identifier, error) { + baseURL, err := url.Parse(fmt.Sprintf(identityBaseURL, region)) + if err != nil { + return nil, err + } + + return &Identifier{ + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 5 * time.Second}, + }, nil +} + +// GetToken returns the x-subject-token from Identity API. +// https://doc.conoha.jp/reference/api-vps3/api-identity-vps3/identity-post_tokens-v3/?btn_id=reference-api-guideline-v3--sidebar_reference-identity-post_tokens-v3 +func (c *Identifier) GetToken(ctx context.Context, auth Auth) (string, error) { + endpoint := c.baseURL.JoinPath("v3", "auth", "tokens") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, &IdentityRequest{Auth: auth}) + if err != nil { + return "", err + } + + return c.do(req) +} + +// do sends the request and returns the token from x-subject-token header. +func (c *Identifier) do(req *http.Request) (string, error) { + resp, err := c.HTTPClient.Do(req) + if err != nil { + return "", errutils.NewHTTPDoError(req, err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusCreated { + return "", errutils.NewUnexpectedResponseStatusCodeError(req, resp) + } + + token := resp.Header.Get("x-subject-token") + if token == "" { + return "", errors.New("x-subject-token header is missing in response") + } + + _, _ = io.Copy(io.Discard, resp.Body) + + return token, nil +} diff --git a/providers/dns/conohav3/internal/identity_test.go b/providers/dns/conohav3/internal/identity_test.go new file mode 100644 index 000000000..97a1e7e7e --- /dev/null +++ b/providers/dns/conohav3/internal/identity_test.go @@ -0,0 +1,52 @@ +package internal + +import ( + "context" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetToken_HeaderToken(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + identifier, err := NewIdentifier("c3j1") + require.NoError(t, err) + + identifier.HTTPClient = server.Client() + identifier.baseURL, _ = url.Parse(server.URL) + + mux.HandleFunc("/v3/auth/tokens", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("x-subject-token", "sample-header-token-123") + w.WriteHeader(http.StatusCreated) + _, _ = w.Write([]byte(`{}`)) + }) + + auth := Auth{ + Identity: Identity{ + Methods: []string{"password"}, + Password: Password{ + User: User{ + ID: "dummy-id", + Password: "dummy-password", + }, + }, + }, + Scope: Scope{ + Project: Project{ + ID: "dummy-project-id", + }, + }, + } + + token, err := identifier.GetToken(context.Background(), auth) + require.NoError(t, err) + + assert.Equal(t, "sample-header-token-123", token) +} diff --git a/providers/dns/conohav3/internal/types.go b/providers/dns/conohav3/internal/types.go new file mode 100644 index 000000000..99a162dd0 --- /dev/null +++ b/providers/dns/conohav3/internal/types.go @@ -0,0 +1,65 @@ +package internal + +// IdentityRequest is the top-level payload sent to the Identity v3. +type IdentityRequest struct { + Auth Auth `json:"auth"` +} + +// Auth authentication credentials (Identity) and scope (Scope). +type Auth struct { + Identity Identity `json:"identity"` + Scope Scope `json:"scope"` +} + +// Identity describes how the client will authenticate. +// In ConoHa v3.0, only support the "password" method. +type Identity struct { + Methods []string `json:"methods"` + Password Password `json:"password"` +} + +// Password nests the concrete user credentials used by the password auth method. +type Password struct { + User User `json:"user"` +} + +// User holds the API User ID and password that will be verified by the Identity service. +type User struct { + ID string `json:"id"` + Password string `json:"password"` +} + +// Scope specifies which tenant the issued token should be scoped to. +type Scope struct { + Project Project `json:"project"` +} + +// Project identifies the target tenant by UUID. +type Project struct { + ID string `json:"id"` +} + +// DomainListResponse is returned by `GET /v1/domains` and contains all DNS zones (domains) owned by the project. +type DomainListResponse struct { + Domains []Domain `json:"domains"` +} + +// Domain represents a single hosted DNS zone. +type Domain struct { + UUID string `json:"uuid"` + Name string `json:"name"` +} + +// RecordListResponse is returned by `GET /v1/domains/{domain_uuid}/records` and lists every record in the zone. +type RecordListResponse struct { + Records []Record `json:"records"` +} + +// Record represents a DNS record inside a zone. +type Record struct { + UUID string `json:"uuid,omitempty"` + Name string `json:"name"` + Type string `json:"type"` + Data string `json:"data"` + TTL int `json:"ttl"` +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 0b7149903..f276a9b3a 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -31,6 +31,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/cloudru" "github.com/go-acme/lego/v4/providers/dns/cloudxns" "github.com/go-acme/lego/v4/providers/dns/conoha" + "github.com/go-acme/lego/v4/providers/dns/conohav3" "github.com/go-acme/lego/v4/providers/dns/constellix" "github.com/go-acme/lego/v4/providers/dns/corenetworks" "github.com/go-acme/lego/v4/providers/dns/cpanel" @@ -214,6 +215,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return cloudxns.NewDNSProvider() case "conoha": return conoha.NewDNSProvider() + case "conohav3": + return conohav3.NewDNSProvider() case "constellix": return constellix.NewDNSProvider() case "corenetworks": From 65608d8bbff21776699e9c89c818c45f037f56fd Mon Sep 17 00:00:00 2001 From: Anton Dzyk <67167119+AntonDzyk@users.noreply.github.com> Date: Tue, 6 May 2025 00:11:00 +0300 Subject: [PATCH 095/298] Add DNS provider for RU Center (#1892) Co-authored-by: Fernandez Ludovic --- README.md | 20 +- cmd/zz_gen_cmd_dnshelp.go | 24 ++ docs/content/dns/zz_gen_nicru.md | 83 ++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/nicru/internal/client.go | 249 +++++++++++ providers/dns/nicru/internal/client_test.go | 398 ++++++++++++++++++ .../nicru/internal/fixtures/commit_POST.xml | 4 + .../dns/nicru/internal/fixtures/errors.xml | 7 + .../nicru/internal/fixtures/record_DELETE.xml | 4 + .../nicru/internal/fixtures/records_GET.xml | 55 +++ .../nicru/internal/fixtures/records_PUT.xml | 10 + .../nicru/internal/fixtures/services_GET.xml | 12 + .../dns/nicru/internal/fixtures/zones_GET.xml | 12 + .../nicru/internal/fixtures/zones_all_GET.xml | 12 + providers/dns/nicru/internal/identity.go | 64 +++ providers/dns/nicru/internal/types.go | 214 ++++++++++ providers/dns/nicru/nicru.go | 238 +++++++++++ providers/dns/nicru/nicru.toml | 41 ++ providers/dns/nicru/nicru_test.go | 192 +++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 20 files changed, 1633 insertions(+), 11 deletions(-) create mode 100644 docs/content/dns/zz_gen_nicru.md create mode 100644 providers/dns/nicru/internal/client.go create mode 100644 providers/dns/nicru/internal/client_test.go create mode 100644 providers/dns/nicru/internal/fixtures/commit_POST.xml create mode 100644 providers/dns/nicru/internal/fixtures/errors.xml create mode 100644 providers/dns/nicru/internal/fixtures/record_DELETE.xml create mode 100644 providers/dns/nicru/internal/fixtures/records_GET.xml create mode 100644 providers/dns/nicru/internal/fixtures/records_PUT.xml create mode 100644 providers/dns/nicru/internal/fixtures/services_GET.xml create mode 100644 providers/dns/nicru/internal/fixtures/zones_GET.xml create mode 100644 providers/dns/nicru/internal/fixtures/zones_all_GET.xml create mode 100644 providers/dns/nicru/internal/identity.go create mode 100644 providers/dns/nicru/internal/types.go create mode 100644 providers/dns/nicru/nicru.go create mode 100644 providers/dns/nicru/nicru.toml create mode 100644 providers/dns/nicru/nicru_test.go diff --git a/README.md b/README.md index 587c77c8d..c4d4fd9c2 100644 --- a/README.md +++ b/README.md @@ -201,52 +201,52 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Regfish RFC2136 RimuHosting - Sakura Cloud + RU CENTER + Sakura Cloud Scaleway Selectel Selectel v2 - SelfHost.(de|eu) + SelfHost.(de|eu) Servercow Shellrent Simply.com - Sonic + Sonic Spaceship Stackpath Technitium - Tencent Cloud DNS + Tencent Cloud DNS Timeweb Cloud TransIP UKFast SafeDNS - Ultradns + Ultradns Variomedia VegaDNS Vercel - Versio.[nl|eu|uk] + Versio.[nl|eu|uk] VinylDNS VK Cloud Volcano Engine/火山引擎 - Vscale + Vscale Vultr Webnames Websupport - WEDOS + WEDOS West.cn/西部数码 Yandex 360 Yandex Cloud - Yandex PDD + Yandex PDD Zone.ee Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index e93c94e50..1d332f27d 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -113,6 +113,7 @@ func allDNSCodes() string { "netcup", "netlify", "nicmanager", + "nicru", "nifcloud", "njalla", "nodion", @@ -2316,6 +2317,29 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/nicmanager`) + case "nicru": + // generated from: providers/dns/nicru/nicru.toml + ew.writeln(`Configuration for RU CENTER.`) + ew.writeln(`Code: 'nicru'`) + ew.writeln(`Since: 'v4.24.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "NICRU_PASSWORD": Password for an account in RU CENTER`) + ew.writeln(` - "NICRU_SECRET": Secret for application in DNS-hosting RU CENTER`) + ew.writeln(` - "NICRU_SERVICE_ID": Service ID for application in DNS-hosting RU CENTER`) + ew.writeln(` - "NICRU_SERVICE_NAME": Service Name for DNS-hosting RU CENTER`) + ew.writeln(` - "NICRU_USER": Agreement for an account in RU CENTER`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "NICRU_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 60)`) + ew.writeln(` - "NICRU_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 600)`) + ew.writeln(` - "NICRU_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 30)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/nicru`) + case "nifcloud": // generated from: providers/dns/nifcloud/nifcloud.toml ew.writeln(`Configuration for NIFCloud.`) diff --git a/docs/content/dns/zz_gen_nicru.md b/docs/content/dns/zz_gen_nicru.md new file mode 100644 index 000000000..d55477a32 --- /dev/null +++ b/docs/content/dns/zz_gen_nicru.md @@ -0,0 +1,83 @@ +--- +title: "RU CENTER" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: nicru +dnsprovider: + since: "v4.24.0" + code: "nicru" + url: "https://nic.ru/" +--- + + + + + + +Configuration for [RU CENTER](https://nic.ru/). + + + + +- Code: `nicru` +- Since: v4.24.0 + + +Here is an example bash command using the RU CENTER provider: + +```bash +NICRU_USER="" \ +NICRU_PASSWORD="" \ +NICRU_SERVICE_ID="" \ +NICRU_SECRET="" \ +lego --dns nicru --domains "*.example.com" --email you@example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `NICRU_PASSWORD` | Password for an account in RU CENTER | +| `NICRU_SECRET` | Secret for application in DNS-hosting RU CENTER | +| `NICRU_SERVICE_ID` | Service ID for application in DNS-hosting RU CENTER | +| `NICRU_SERVICE_NAME` | Service Name for DNS-hosting RU CENTER | +| `NICRU_USER` | Agreement for an account in RU CENTER | + +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 | +|--------------------------------|-------------| +| `NICRU_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 60) | +| `NICRU_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 600) | +| `NICRU_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 30) | + +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" %}}). + +## Credential information + +You can find information about service ID and secret https://www.nic.ru/manager/oauth.cgi?step=oauth.app_list + +| ENV Variable | Parameter from page | Example | +|---------------------|--------------------------------|-------------------| +| NICRU_USER | Username (Number of agreement) | NNNNNNN/NIC-D | +| NICRU_PASSWORD | Password account | | +| NICRU_SERVICE_ID | Application ID | hex-based, len 32 | +| NICRU_SECRET | Identity endpoint | string len 91 | + + + +## More information + +- [API documentation](https://www.nic.ru/help/api-dns-hostinga_3643.html) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 614d2f8b5..f06353e4c 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -150,7 +150,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi + acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/nicru/internal/client.go b/providers/dns/nicru/internal/client.go new file mode 100644 index 000000000..912de9692 --- /dev/null +++ b/providers/dns/nicru/internal/client.go @@ -0,0 +1,249 @@ +package internal + +import ( + "bytes" + "context" + "encoding/xml" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const ( + apiBaseURL = "https://api.nic.ru/dns-master" + tokenURL = "https://api.nic.ru/oauth/token" +) + +const successStatus = "success" + +// Trimmer trim all XML fields. +type Trimmer struct { + decoder *xml.Decoder +} + +func (tr Trimmer) Token() (xml.Token, error) { + t, err := tr.decoder.Token() + if cd, ok := t.(xml.CharData); ok { + t = xml.CharData(bytes.TrimSpace(cd)) + } + return t, err +} + +type Client struct { + baseURL *url.URL + httpClient *http.Client +} + +func NewClient(httpClient *http.Client) (*Client, error) { + if httpClient == nil { + httpClient = &http.Client{Timeout: 5 * time.Second} + } + + baseURL, _ := url.Parse(apiBaseURL) + + return &Client{ + baseURL: baseURL, + httpClient: httpClient, + }, nil +} + +func (c *Client) GetServices(ctx context.Context) ([]Service, error) { + endpoint := c.baseURL.JoinPath("services") + + req, err := newXMLRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + apiResponse, err := c.do(req) + if err != nil { + return nil, err + } + + if apiResponse.Data == nil { + return nil, nil + } + + return apiResponse.Data.Service, nil +} + +func (c *Client) ListZones(ctx context.Context) ([]Zone, error) { + endpoint := c.baseURL.JoinPath("zones") + + req, err := newXMLRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + apiResponse, err := c.do(req) + if err != nil { + return nil, err + } + + if apiResponse.Data == nil { + return nil, nil + } + + return apiResponse.Data.Zone, nil +} + +func (c *Client) GetZonesByService(ctx context.Context, serviceName string) ([]Zone, error) { + endpoint := c.baseURL.JoinPath("services", serviceName, "zones") + + req, err := newXMLRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + apiResponse, err := c.do(req) + if err != nil { + return nil, err + } + + if apiResponse.Data == nil { + return nil, nil + } + + return apiResponse.Data.Zone, nil +} + +func (c *Client) GetRecords(ctx context.Context, serviceName, zoneName string) ([]RR, error) { + endpoint := c.baseURL.JoinPath("services", serviceName, "zones", zoneName, "records") + + req, err := newXMLRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + apiResponse, err := c.do(req) + if err != nil { + return nil, err + } + + if apiResponse.Data == nil { + return nil, nil + } + + var records []RR + for _, zone := range apiResponse.Data.Zone { + records = append(records, zone.RR...) + } + + return records, nil +} + +func (c *Client) DeleteRecord(ctx context.Context, serviceName, zoneName string, id string) error { + endpoint := c.baseURL.JoinPath("services", serviceName, "zones", zoneName, "records", id) + + req, err := newXMLRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + _, err = c.do(req) + if err != nil { + return err + } + + return nil +} + +func (c *Client) CommitZone(ctx context.Context, serviceName, zoneName string) error { + endpoint := c.baseURL.JoinPath("services", serviceName, "zones", zoneName, "commit") + + req, err := newXMLRequest(ctx, http.MethodPost, endpoint, nil) + if err != nil { + return err + } + + _, err = c.do(req) + if err != nil { + return err + } + + return nil +} + +func (c *Client) AddRecords(ctx context.Context, serviceName, zoneName string, rrs []RR) ([]Zone, error) { + endpoint := c.baseURL.JoinPath("services", serviceName, "zones", zoneName, "records") + + payload := &Request{RRList: &RRList{RR: rrs}} + + req, err := newXMLRequest(ctx, http.MethodPut, endpoint, payload) + if err != nil { + return nil, err + } + + apiResponse, err := c.do(req) + if err != nil { + return nil, err + } + + if apiResponse.Data == nil { + return nil, nil + } + + return apiResponse.Data.Zone, nil +} + +func (c *Client) do(req *http.Request) (*Response, error) { + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + apiResponse := &Response{} + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return nil, errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + decoder := xml.NewTokenDecoder(Trimmer{decoder: xml.NewDecoder(bytes.NewReader(raw))}) + + err = decoder.Decode(apiResponse) + if err != nil { + return nil, fmt.Errorf("[status code=%d] decode XML response: %s", resp.StatusCode, string(raw)) + } + + if apiResponse.Status != successStatus { + return nil, fmt.Errorf("[status code=%d] %s: %w", resp.StatusCode, apiResponse.Status, apiResponse.Errors.Error) + } + + return apiResponse, nil +} + +func newXMLRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + body := new(bytes.Buffer) + + if payload != nil { + body.WriteString(xml.Header) + + encoder := xml.NewEncoder(body) + encoder.Indent("", " ") + + err := encoder.Encode(payload) + if err != nil { + return nil, err + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), body) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "text/xml") + + if payload != nil { + req.Header.Set("Content-Type", "text/xml") + } + + return req, nil +} diff --git a/providers/dns/nicru/internal/client_test.go b/providers/dns/nicru/internal/client_test.go new file mode 100644 index 000000000..461189e62 --- /dev/null +++ b/providers/dns/nicru/internal/client_test.go @@ -0,0 +1,398 @@ +package internal + +import ( + "context" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupTest(t *testing.T, pattern string, handler http.HandlerFunc) *Client { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.HandleFunc(pattern, handler) + + client, err := NewClient(server.Client()) + require.NoError(t, err) + + client.baseURL, _ = url.Parse(server.URL) + + return client +} + +func writeFixtures(method string, filename string, status int) http.HandlerFunc { + return func(rw http.ResponseWriter, req *http.Request) { + if req.Method != method { + http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) + return + } + + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + defer func() { _ = file.Close() }() + + rw.WriteHeader(status) + _, err = io.Copy(rw, file) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + } +} + +func TestClient_GetServices(t *testing.T) { + client := setupTest(t, "/services", + writeFixtures(http.MethodGet, "services_GET.xml", http.StatusOK)) + + zones, err := client.GetServices(context.Background()) + require.NoError(t, err) + + expected := []Service{ + { + Admin: "123/NIC-REG", + DomainsLimit: "12", + DomainsNum: "5", + Enable: "true", + HasPrimary: "false", + Name: "testservice", + Payer: "123/NIC-REG", + Tariff: "Secondary L", + }, + { + Admin: "123/NIC-REG", + DomainsLimit: "150", + DomainsNum: "10", + Enable: "true", + HasPrimary: "true", + Name: "myservice", + Payer: "123/NIC-REG", + Tariff: "DNS-master XXL", + RRLimit: "7500", + RRNum: "1000", + }, + } + + assert.Equal(t, expected, zones) +} + +func TestClient_ListZones(t *testing.T) { + client := setupTest(t, "/zones", + writeFixtures(http.MethodGet, "zones_all_GET.xml", http.StatusOK)) + + zones, err := client.ListZones(context.Background()) + require.NoError(t, err) + + expected := []Zone{ + { + Admin: "123/NIC-REG", + Enable: "true", + HasChanges: "false", + HasPrimary: "true", + ID: "227645", + IDNName: "тест.рф", + Name: "xn—e1aybc.xn--p1ai", + Payer: "123/NIC-REG", + Service: "myservice", + }, + { + Admin: "123/NIC-REG", + Enable: "true", + HasChanges: "false", + HasPrimary: "true", + ID: "227642", + IDNName: "example.ru", + Name: "example.ru", + Payer: "123/NIC-REG", + Service: "myservice", + }, + { + Admin: "123/NIC-REG", + Enable: "true", + HasChanges: "false", + HasPrimary: "true", + ID: "227643", + IDNName: "test.su", + Name: "test.su", + Payer: "123/NIC-REG", + Service: "myservice", + }, + } + + assert.Equal(t, expected, zones) +} + +func TestClient_ListZones_error(t *testing.T) { + client := setupTest(t, "/zones", + writeFixtures(http.MethodGet, "errors.xml", http.StatusOK)) + + _, err := client.ListZones(context.Background()) + require.ErrorIs(t, err, Error{ + Text: "Access token expired or not found", + Code: "4097", + }) +} + +func TestClient_GetZonesByService(t *testing.T) { + client := setupTest(t, "/services/test/zones", + writeFixtures(http.MethodGet, "zones_GET.xml", http.StatusOK)) + + zones, err := client.GetZonesByService(context.Background(), "test") + require.NoError(t, err) + + expected := []Zone{ + { + Admin: "123/NIC-REG", + Enable: "true", + HasChanges: "false", + HasPrimary: "true", + ID: "227645", + IDNName: "тест.рф", + Name: "xn—e1aybc.xn--p1ai", + Payer: "123/NIC-REG", + Service: "myservice", + }, + { + Admin: "123/NIC-REG", + Enable: "true", + HasChanges: "false", + HasPrimary: "true", + ID: "227642", + IDNName: "example.ru", + Name: "example.ru", + Payer: "123/NIC-REG", + Service: "myservice", + }, + { + Admin: "123/NIC-REG", + Enable: "true", + HasChanges: "false", + HasPrimary: "true", + ID: "227643", + IDNName: "test.su", + Name: "test.su", + Payer: "123/NIC-REG", + Service: "myservice", + }, + } + + assert.Equal(t, expected, zones) +} + +func TestClient_GetZonesByService_error(t *testing.T) { + client := setupTest(t, "/services/test/zones", + writeFixtures(http.MethodGet, "errors.xml", http.StatusOK)) + + _, err := client.GetZonesByService(context.Background(), "test") + require.ErrorIs(t, err, Error{ + Text: "Access token expired or not found", + Code: "4097", + }) +} + +func TestClient_GetRecords(t *testing.T) { + client := setupTest(t, "/services/test/zones/example.com./records", + writeFixtures(http.MethodGet, "records_GET.xml", http.StatusOK)) + + records, err := client.GetRecords(context.Background(), "test", "example.com.") + require.NoError(t, err) + + expected := []RR{ + { + ID: "210074", + Name: "@", + IDNName: "@", + TTL: "", + Type: "SOA", + SOA: &SOA{ + MName: &MName{ + Name: "ns3-l2.nic.ru.", + IDNName: "ns3-l2.nic.ru.", + }, + RName: &RName{ + Name: "dns.nic.ru.", + IDNName: "dns.nic.ru.", + }, + Serial: "2011112002", + Refresh: "1440", + Retry: "3600", + Expire: "2592000", + Minimum: "600", + }, + }, + { + ID: "210075", + Name: "@", + IDNName: "@", + Type: "NS", + NS: &NS{ + Name: "ns3-l2.nic.ru.", + IDNName: "ns3- l2.nic.ru.", + }, + }, + { + ID: "210076", + Name: "@", + IDNName: "@", + Type: "NS", + NS: &NS{ + Name: "ns4-l2.nic.ru.", + IDNName: "ns4-l2.nic.ru.", + }, + }, + { + ID: "210077", + Name: "@", + IDNName: "@", + Type: "NS", + NS: &NS{ + Name: "ns8-l2.nic.ru.", + IDNName: "ns8- l2.nic.ru.", + }, + }, + } + + assert.Equal(t, expected, records) +} + +func TestClient_GetRecords_error(t *testing.T) { + client := setupTest(t, "/services/test/zones/example.com./records", + writeFixtures(http.MethodGet, "errors.xml", http.StatusOK)) + + _, err := client.GetRecords(context.Background(), "test", "example.com.") + require.ErrorIs(t, err, Error{ + Text: "Access token expired or not found", + Code: "4097", + }) +} + +func TestClient_AddRecord(t *testing.T) { + client := setupTest(t, "/services/test/zones/example.com./records", + writeFixtures(http.MethodPut, "records_PUT.xml", http.StatusOK)) + + rrs := []RR{ + { + Name: "@", + Type: "NS", + NS: &NS{Name: "ns4-l2.nic.ru."}, + }, + { + Name: "@", + Type: "NS", + NS: &NS{Name: "ns8-l2.nic.ru."}, + }, + } + + response, err := client.AddRecords(context.Background(), "test", "example.com.", rrs) + require.NoError(t, err) + + expected := []Zone{ + { + Admin: "123/NIC-REG", + HasChanges: "true", + ID: "228095", + IDNName: "test.ru", + Name: "test.ru", + Service: "testservice", + RR: []RR{ + { + ID: "210076", + Name: "@", + IDNName: "@", + Type: "NS", + NS: &NS{ + Name: "ns4-l2.nic.ru.", + IDNName: "ns4-l2.nic.ru.", + }, + }, + { + ID: "210077", + Name: "@", + IDNName: "@", + Type: "NS", + NS: &NS{ + Name: "ns8-l2.nic.ru.", + IDNName: "ns8-l2.nic.ru.", + }, + }, + }, + }, + } + + assert.Equal(t, expected, response) +} + +func TestClient_AddRecord_error(t *testing.T) { + client := setupTest(t, "/services/test/zones/example.com./records", + writeFixtures(http.MethodPut, "errors.xml", http.StatusOK)) + + rrs := []RR{ + { + Name: "@", + Type: "NS", + NS: &NS{Name: "ns4-l2.nic.ru."}, + }, + { + Name: "@", + Type: "NS", + NS: &NS{Name: "ns8-l2.nic.ru."}, + }, + } + + _, err := client.AddRecords(context.Background(), "test", "example.com.", rrs) + require.ErrorIs(t, err, Error{ + Text: "Access token expired or not found", + Code: "4097", + }) +} + +func TestClient_DeleteRecord(t *testing.T) { + client := setupTest(t, "/services/test/zones/example.com./records/123", + writeFixtures(http.MethodDelete, "record_DELETE.xml", http.StatusUnauthorized)) + + err := client.DeleteRecord(context.Background(), "test", "example.com.", "123") + require.NoError(t, err) +} + +func TestClient_DeleteRecord_error(t *testing.T) { + client := setupTest(t, "/services/test/zones/example.com./records/123", + writeFixtures(http.MethodDelete, "errors.xml", http.StatusUnauthorized)) + + err := client.DeleteRecord(context.Background(), "test", "example.com.", "123") + require.ErrorIs(t, err, Error{ + Text: "Access token expired or not found", + Code: "4097", + }) +} + +func TestClient_CommitZone(t *testing.T) { + client := setupTest(t, "/services/test/zones/example.com./commit", writeFixtures(http.MethodPost, "commit_POST.xml", http.StatusOK)) + + err := client.CommitZone(context.Background(), "test", "example.com.") + require.NoError(t, err) +} + +func TestClient_CommitZone_error(t *testing.T) { + client := setupTest(t, "/services/test/zones/example.com./commit", writeFixtures(http.MethodPost, "errors.xml", http.StatusOK)) + + err := client.CommitZone(context.Background(), "test", "example.com.") + require.ErrorIs(t, err, Error{ + Text: "Access token expired or not found", + Code: "4097", + }) +} diff --git a/providers/dns/nicru/internal/fixtures/commit_POST.xml b/providers/dns/nicru/internal/fixtures/commit_POST.xml new file mode 100644 index 000000000..530a22d16 --- /dev/null +++ b/providers/dns/nicru/internal/fixtures/commit_POST.xml @@ -0,0 +1,4 @@ + + + success + diff --git a/providers/dns/nicru/internal/fixtures/errors.xml b/providers/dns/nicru/internal/fixtures/errors.xml new file mode 100644 index 000000000..961b9a495 --- /dev/null +++ b/providers/dns/nicru/internal/fixtures/errors.xml @@ -0,0 +1,7 @@ + + + fail + + Access token expired or not found + + diff --git a/providers/dns/nicru/internal/fixtures/record_DELETE.xml b/providers/dns/nicru/internal/fixtures/record_DELETE.xml new file mode 100644 index 000000000..530a22d16 --- /dev/null +++ b/providers/dns/nicru/internal/fixtures/record_DELETE.xml @@ -0,0 +1,4 @@ + + + success + diff --git a/providers/dns/nicru/internal/fixtures/records_GET.xml b/providers/dns/nicru/internal/fixtures/records_GET.xml new file mode 100644 index 000000000..a9df348f9 --- /dev/null +++ b/providers/dns/nicru/internal/fixtures/records_GET.xml @@ -0,0 +1,55 @@ + + + success + + + + @ + @ + SOA + + + ns3-l2.nic.ru. + ns3-l2.nic.ru. + + + dns.nic.ru. + dns.nic.ru. + + 2011112002 + 1440 + 3600 + 2592000 + 600 + + + + @ + @ + NS + + ns3-l2.nic.ru. + ns3- l2.nic.ru. + + + + @ + @ + NS + + ns4-l2.nic.ru. + ns4-l2.nic.ru. + + + + @ + @ + NS + + ns8-l2.nic.ru. + ns8- l2.nic.ru. + + + + + diff --git a/providers/dns/nicru/internal/fixtures/records_PUT.xml b/providers/dns/nicru/internal/fixtures/records_PUT.xml new file mode 100644 index 000000000..a3417a8f3 --- /dev/null +++ b/providers/dns/nicru/internal/fixtures/records_PUT.xml @@ -0,0 +1,10 @@ + + + success + + + @@NSns4-l2.nic.ru.ns4-l2.nic.ru. + @@NSns8-l2.nic.ru.ns8-l2.nic.ru. + + + diff --git a/providers/dns/nicru/internal/fixtures/services_GET.xml b/providers/dns/nicru/internal/fixtures/services_GET.xml new file mode 100644 index 000000000..9534b0b34 --- /dev/null +++ b/providers/dns/nicru/internal/fixtures/services_GET.xml @@ -0,0 +1,12 @@ + + + success + + + + + diff --git a/providers/dns/nicru/internal/fixtures/zones_GET.xml b/providers/dns/nicru/internal/fixtures/zones_GET.xml new file mode 100644 index 000000000..efa2da9a2 --- /dev/null +++ b/providers/dns/nicru/internal/fixtures/zones_GET.xml @@ -0,0 +1,12 @@ + + + success + + + + + + diff --git a/providers/dns/nicru/internal/fixtures/zones_all_GET.xml b/providers/dns/nicru/internal/fixtures/zones_all_GET.xml new file mode 100644 index 000000000..efa2da9a2 --- /dev/null +++ b/providers/dns/nicru/internal/fixtures/zones_all_GET.xml @@ -0,0 +1,12 @@ + + + success + + + + + + diff --git a/providers/dns/nicru/internal/identity.go b/providers/dns/nicru/internal/identity.go new file mode 100644 index 000000000..b4281adbe --- /dev/null +++ b/providers/dns/nicru/internal/identity.go @@ -0,0 +1,64 @@ +package internal + +import ( + "context" + "errors" + "fmt" + "net/http" + + "golang.org/x/oauth2" +) + +// OauthConfiguration credentials. +type OauthConfiguration struct { + OAuth2ClientID string + OAuth2SecretID string + Username string + Password string +} + +func (config *OauthConfiguration) Validate() error { + msg := " is missing in credentials information" + + if config.Username == "" { + return errors.New("username" + msg) + } + + if config.Password == "" { + return errors.New("password" + msg) + } + + if config.OAuth2ClientID == "" { + return errors.New("serviceID" + msg) + } + + if config.OAuth2SecretID == "" { + return errors.New("secret" + msg) + } + + return nil +} + +func NewOauthClient(ctx context.Context, config *OauthConfiguration) (*http.Client, error) { + err := config.Validate() + if err != nil { + return nil, err + } + + oauth2Config := oauth2.Config{ + ClientID: config.OAuth2ClientID, + ClientSecret: config.OAuth2SecretID, + Endpoint: oauth2.Endpoint{ + TokenURL: tokenURL, + AuthStyle: oauth2.AuthStyleInParams, + }, + Scopes: []string{".+:/dns-master/.+"}, + } + + oauth2Token, err := oauth2Config.PasswordCredentialsToken(ctx, config.Username, config.Password) + if err != nil { + return nil, fmt.Errorf("failed to create oauth2 token: %w", err) + } + + return oauth2Config.Client(ctx, oauth2Token), nil +} diff --git a/providers/dns/nicru/internal/types.go b/providers/dns/nicru/internal/types.go new file mode 100644 index 000000000..ad3f8cc9a --- /dev/null +++ b/providers/dns/nicru/internal/types.go @@ -0,0 +1,214 @@ +package internal + +import ( + "encoding/xml" + "fmt" +) + +type Request struct { + XMLName xml.Name `xml:"request"` + Text string `xml:",chardata"` + RRList *RRList `xml:"rr-list"` +} + +type RRList struct { + Text string `xml:",chardata"` + RR []RR `xml:"rr"` +} + +type RR struct { + Text string `xml:",chardata"` + ID string `xml:"id,attr,omitempty"` + Name string `xml:"name"` + IDNName string `xml:"idn-name"` + TTL string `xml:"ttl"` + Type string `xml:"type"` + SOA *SOA `xml:"soa,omitempty"` + A string `xml:"a,omitempty"` + AAAA string `xml:"aaaa,omitempty"` + CName *CName `xml:"cname,omitempty"` + NS *NS `xml:"ns,omitempty"` + MX *MX `xml:"mx,omitempty"` + SRV *SRV `xml:"srv,omitempty"` + PTR *PTR `xml:"ptr,omitempty"` + TXT *TXT `xml:"txt,omitempty"` + DName *DName `xml:"dname,omitempty"` + HInfo *HInfo `xml:"hinfo,omitempty"` + NAPTR *NAPTR `xml:"naptr,omitempty"` + RP *RP `xml:"rp,omitempty"` +} + +type SOA struct { + Text string `xml:",chardata"` + MName *MName `xml:"mname"` + RName *RName `xml:"rname"` + Serial string `xml:"serial"` + Refresh string `xml:"refresh"` + Retry string `xml:"retry"` + Expire string `xml:"expire"` + Minimum string `xml:"minimum"` +} + +type MName struct { + Text string `xml:",chardata"` + Name string `xml:"name"` + IDNName string `xml:"idn-name,omitempty"` +} + +type RName struct { + Text string `xml:",chardata"` + Name string `xml:"name"` + IDNName string `xml:"idn-name,omitempty"` +} + +type NS struct { + Text string `xml:",chardata"` + Name string `xml:"name"` + IDNName string `xml:"idn-name,omitempty"` +} + +type MX struct { + Text string `xml:",chardata"` + Preference string `xml:"preference"` + Exchange *Exchange `xml:"exchange"` +} + +type Exchange struct { + Name string `xml:"name"` +} + +type SRV struct { + Text string `xml:",chardata"` + Priority string `xml:"priority"` + Weight string `xml:"weight"` + Port string `xml:"port"` + Target *Target `xml:"target"` +} + +type Target struct { + Text string `xml:",chardata"` + Name string `xml:"name"` +} + +type PTR struct { + Text string `xml:",chardata"` + Name string `xml:"name"` +} + +type HInfo struct { + Text string `xml:",chardata"` + Hardware string `xml:"hardware"` + OS string `xml:"os"` +} + +type NAPTR struct { + Text string `xml:",chardata"` + Order string `xml:"order"` + Preference string `xml:"preference"` + Flags string `xml:"flags"` + Service string `xml:"service"` + Regexp string `xml:"regexp"` + Replacement *Replacement `xml:"replacement"` +} + +type Replacement struct { + Text string `xml:",chardata"` + Name string `xml:"name"` +} + +type RP struct { + Text string `xml:",chardata"` + MboxDName *MboxDName `xml:"mbox-dname"` + TxtDName *TxtDName `xml:"txt-dname"` +} + +type MboxDName struct { + Text string `xml:",chardata"` + Name string `xml:"name"` +} + +type TxtDName struct { + Text string `xml:",chardata"` + Name string `xml:"name"` +} + +type CName struct { + Text string `xml:",chardata"` + Name string `xml:"name"` + IDNName string `xml:"idn-name,omitempty"` +} + +type DName struct { + Text string `xml:",chardata"` + Name string `xml:"name"` +} + +type TXT struct { + Text string `xml:",chardata"` + String string `xml:"string"` +} + +type Response struct { + XMLName xml.Name `xml:"response"` + Text string `xml:",chardata"` + Status string `xml:"status"` + Data *Data `xml:"data"` + Errors Errors `xml:"errors"` +} + +type Data struct { + Text string `xml:",chardata"` + Service []Service `xml:"service"` + Zone []Zone `xml:"zone"` + Address []string `xml:"address"` + Revision []Revision `xml:"revision"` +} + +type Errors struct { + Text string `xml:",chardata"` + Error Error `xml:"error"` +} + +type Error struct { + Text string `xml:",chardata"` + Code string `xml:"code,attr"` +} + +func (e Error) Error() string { + return fmt.Sprintf("%s (code %s)", e.Text, e.Code) +} + +type Service struct { + Text string `xml:",chardata"` + Admin string `xml:"admin,attr"` + DomainsLimit string `xml:"domains-limit,attr"` + DomainsNum string `xml:"domains-num,attr"` + Enable string `xml:"enable,attr"` + HasPrimary string `xml:"has-primary,attr"` + Name string `xml:"name,attr"` + Payer string `xml:"payer,attr"` + Tariff string `xml:"tariff,attr"` + RRLimit string `xml:"rr-limit,attr"` + RRNum string `xml:"rr-num,attr"` +} + +type Zone struct { + Text string `xml:",chardata"` + Admin string `xml:"admin,attr"` + Enable string `xml:"enable,attr"` + HasChanges string `xml:"has-changes,attr"` + HasPrimary string `xml:"has-primary,attr"` + ID string `xml:"id,attr"` + IDNName string `xml:"idn-name,attr"` + Name string `xml:"name,attr"` + Payer string `xml:"payer,attr"` + Service string `xml:"service,attr"` + RR []RR `xml:"rr"` +} + +type Revision struct { + Text string `xml:",chardata"` + Date string `xml:"date,attr"` + IP string `xml:"ip,attr"` + Number string `xml:"number,attr"` +} diff --git a/providers/dns/nicru/nicru.go b/providers/dns/nicru/nicru.go new file mode 100644 index 000000000..9320f94c2 --- /dev/null +++ b/providers/dns/nicru/nicru.go @@ -0,0 +1,238 @@ +// Package nicru implements a DNS provider for solving the DNS-01 challenge using RU Center. +package nicru + +import ( + "context" + "errors" + "fmt" + "strconv" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/nicru/internal" +) + +// Environment variables names. +const ( + envNamespace = "NICRU_" + + EnvUsername = envNamespace + "USER" + EnvPassword = envNamespace + "PASSWORD" + EnvServiceID = envNamespace + "SERVICE_ID" + EnvSecret = envNamespace + "SECRET" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + TTL int + Username string + Password string + ServiceID string + Secret string + PropagationTimeout time.Duration + PollingInterval time.Duration +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + TTL: env.GetOrDefaultInt(EnvTTL, 30), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 10*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 1*time.Minute), + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + client *internal.Client + config *Config +} + +// NewDNSProvider returns a DNSProvider instance configured for RU Center. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvUsername, EnvPassword, EnvServiceID, EnvSecret) + if err != nil { + return nil, fmt.Errorf("nicru: %w", err) + } + + config := NewDefaultConfig() + config.Username = values[EnvUsername] + config.Password = values[EnvPassword] + config.ServiceID = values[EnvServiceID] + config.Secret = values[EnvSecret] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for RU Center. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("nicru: the configuration of the DNS provider is nil") + } + + clientCfg := &internal.OauthConfiguration{ + OAuth2ClientID: config.ServiceID, + OAuth2SecretID: config.Secret, + Username: config.Username, + Password: config.Password, + } + + oauthClient, err := internal.NewOauthClient(context.Background(), clientCfg) + if err != nil { + return nil, fmt.Errorf("nicru: %w", err) + } + + client, err := internal.NewClient(oauthClient) + if err != nil { + return nil, fmt.Errorf("nicru: unable to build API client: %w", err) + } + + return &DNSProvider{ + client: client, + config: config, + }, nil +} + +// Present creates a TXT record to fulfill the dns-01 challenge. +func (d *DNSProvider) Present(domain, _, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("nicru: could not find zone for domain %q: %w", domain, err) + } + + authZone = dns01.UnFqdn(authZone) + + zone, err := d.findZone(ctx, authZone) + if err != nil { + return fmt.Errorf("nicru: find zone: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("nicru: %w", err) + } + + records, err := d.client.GetRecords(ctx, zone.Service, authZone) + if err != nil { + return fmt.Errorf("nicru: get records: %w", err) + } + + for _, record := range records { + if record.TXT == nil { + continue + } + + if record.TXT.Text == subDomain && record.TXT.String == info.Value { + return nil + } + } + + rrs := []internal.RR{{ + Name: subDomain, + TTL: strconv.Itoa(d.config.TTL), + Type: "TXT", + TXT: &internal.TXT{String: info.Value}, + }} + + _, err = d.client.AddRecords(ctx, zone.Service, authZone, rrs) + if err != nil { + return fmt.Errorf("nicru: add records: %w", err) + } + + err = d.client.CommitZone(ctx, zone.Service, authZone) + if err != nil { + return fmt.Errorf("nicru: commit zone: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("nicru: could not find zone for domain %q: %w", domain, err) + } + + authZone = dns01.UnFqdn(authZone) + + zone, err := d.findZone(ctx, authZone) + if err != nil { + return fmt.Errorf("nicru: find zone: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("nicru: %w", err) + } + + records, err := d.client.GetRecords(ctx, zone.Service, authZone) + if err != nil { + return fmt.Errorf("nicru: get records: %w", err) + } + + subDomain = dns01.UnFqdn(subDomain) + + for _, record := range records { + if record.TXT == nil { + continue + } + + if record.Name != subDomain || record.TXT.String != info.Value { + continue + } + + err = d.client.DeleteRecord(ctx, zone.Service, authZone, record.ID) + if err != nil { + return fmt.Errorf("nicru: delete record: %w", err) + } + } + + err = d.client.CommitZone(ctx, zone.Service, authZone) + if err != nil { + return fmt.Errorf("nicru: commit zone: %w", err) + } + + 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 +} + +func (d *DNSProvider) findZone(ctx context.Context, authZone string) (*internal.Zone, error) { + zones, err := d.client.ListZones(ctx) + if err != nil { + return nil, fmt.Errorf("unable to fetch dns zones: %w", err) + } + + if len(zones) == 0 { + return nil, errors.New("no zones found") + } + + for _, zone := range zones { + if zone.Name == authZone { + return &zone, nil + } + } + + return nil, fmt.Errorf("zone not found for %s", authZone) +} diff --git a/providers/dns/nicru/nicru.toml b/providers/dns/nicru/nicru.toml new file mode 100644 index 000000000..6bffe74a5 --- /dev/null +++ b/providers/dns/nicru/nicru.toml @@ -0,0 +1,41 @@ +Name = "RU CENTER" +Description = '''''' +URL = "https://nic.ru/" +Code = "nicru" +Since = "v4.24.0" + +Example = ''' +NICRU_USER="" \ +NICRU_PASSWORD="" \ +NICRU_SERVICE_ID="" \ +NICRU_SECRET="" \ +lego --dns nicru --domains "*.example.com" --email you@example.com run +''' + +Additional = ''' +## Credential information + +You can find information about service ID and secret https://www.nic.ru/manager/oauth.cgi?step=oauth.app_list + +| ENV Variable | Parameter from page | Example | +|---------------------|--------------------------------|-------------------| +| NICRU_USER | Username (Number of agreement) | NNNNNNN/NIC-D | +| NICRU_PASSWORD | Password account | | +| NICRU_SERVICE_ID | Application ID | hex-based, len 32 | +| NICRU_SECRET | Identity endpoint | string len 91 | +''' + +[Configuration] + [Configuration.Credentials] + NICRU_USER = "Agreement for an account in RU CENTER" + NICRU_PASSWORD = "Password for an account in RU CENTER" + NICRU_SERVICE_ID = "Service ID for application in DNS-hosting RU CENTER" + NICRU_SECRET = "Secret for application in DNS-hosting RU CENTER" + NICRU_SERVICE_NAME = "Service Name for DNS-hosting RU CENTER" + [Configuration.Additional] + NICRU_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 60)" + NICRU_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 600)" + NICRU_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 30)" + +[Links] + API = "https://www.nic.ru/help/api-dns-hostinga_3643.html" diff --git a/providers/dns/nicru/nicru_test.go b/providers/dns/nicru/nicru_test.go new file mode 100644 index 000000000..12db3a4ff --- /dev/null +++ b/providers/dns/nicru/nicru_test.go @@ -0,0 +1,192 @@ +package nicru + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const ( + fakeServiceID = "2519234972459cdfa23423adf143324f" + fakeSecret = "oo5ahrie0aiPho3Vee4siupoPhahdahCh1thiesohru" + fakeUsername = "1234567/NIC-D" + fakePassword = "einge8Goo2eBaiXievuj" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvUsername, EnvPassword, EnvServiceID, EnvSecret).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvServiceID: fakeServiceID, + EnvSecret: fakeSecret, + EnvUsername: fakeUsername, + EnvPassword: fakePassword, + }, + expected: "nicru: failed to create oauth2 token: oauth2: \"unauthorized_client\"", + }, + { + desc: "missing serviceID", + envVars: map[string]string{ + EnvSecret: fakeSecret, + EnvUsername: fakeUsername, + EnvPassword: fakePassword, + }, + expected: "nicru: some credentials information are missing: NICRU_SERVICE_ID", + }, + { + desc: "missing secret", + envVars: map[string]string{ + EnvServiceID: fakeServiceID, + EnvUsername: fakeUsername, + EnvPassword: fakePassword, + }, + expected: "nicru: some credentials information are missing: NICRU_SECRET", + }, + { + desc: "missing username", + envVars: map[string]string{ + EnvServiceID: fakeServiceID, + EnvSecret: fakeSecret, + EnvPassword: fakePassword, + }, + expected: "nicru: some credentials information are missing: NICRU_USER", + }, + { + desc: "missing password", + envVars: map[string]string{ + EnvServiceID: fakeServiceID, + EnvSecret: fakeSecret, + EnvUsername: fakeUsername, + }, + expected: "nicru: some credentials information are missing: NICRU_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 test.expected == "" { + 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 + config *Config + expected string + }{ + { + desc: "success", + config: &Config{ + ServiceID: fakeServiceID, + Secret: fakeSecret, + Username: fakeUsername, + Password: fakePassword, + }, + expected: "nicru: failed to create oauth2 token: oauth2: \"unauthorized_client\"", + }, + { + desc: "nil config", + config: nil, + expected: "nicru: the configuration of the DNS provider is nil", + }, + { + desc: "missing username", + config: &Config{ + ServiceID: fakeServiceID, + Password: fakePassword, + }, + expected: "nicru: username is missing in credentials information", + }, + { + desc: "missing password", + config: &Config{ + ServiceID: fakeServiceID, + Secret: fakeSecret, + Username: fakeUsername, + }, + expected: "nicru: password is missing in credentials information", + }, + { + desc: "missing secret", + config: &Config{ + ServiceID: fakeServiceID, + Username: fakeUsername, + Password: fakePassword, + }, + expected: "nicru: secret is missing in credentials information", + }, + { + desc: "missing serviceID", + config: &Config{ + Secret: fakeSecret, + Username: fakeUsername, + Password: fakePassword, + }, + expected: "nicru: serviceID is missing in credentials information", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + p, err := NewDNSProviderConfig(test.config) + + if test.expected == "" { + 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) + + 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 f276a9b3a..eba28d83e 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -107,6 +107,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/netcup" "github.com/go-acme/lego/v4/providers/dns/netlify" "github.com/go-acme/lego/v4/providers/dns/nicmanager" + "github.com/go-acme/lego/v4/providers/dns/nicru" "github.com/go-acme/lego/v4/providers/dns/nifcloud" "github.com/go-acme/lego/v4/providers/dns/njalla" "github.com/go-acme/lego/v4/providers/dns/nodion" @@ -367,6 +368,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return netlify.NewDNSProvider() case "nicmanager": return nicmanager.NewDNSProvider() + case "nicru": + return nicru.NewDNSProvider() case "nifcloud": return nifcloud.NewDNSProvider() case "njalla": From 2f10624b11204aee6a342638c1e324ba1c1f57db Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 8 May 2025 13:13:48 +0200 Subject: [PATCH 096/298] chore: bump dnsimple, namedotcom, and selectel to major versions (#2523) --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- providers/dns/dnsimple/dnsimple.go | 2 +- providers/dns/namedotcom/namedotcom.go | 2 +- providers/dns/selectelv2/selectelv2.go | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 740641193..e39d268da 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 github.com/civo/civogo v0.3.11 github.com/cloudflare/cloudflare-go v0.115.0 - github.com/dnsimple/dnsimple-go v1.7.0 + github.com/dnsimple/dnsimple-go/v4 v4.0.0 github.com/exoscale/egoscale/v3 v3.1.13 github.com/go-jose/go-jose/v4 v4.0.5 github.com/go-viper/mapstructure/v2 v2.2.1 @@ -46,7 +46,7 @@ require ( github.com/mattn/go-isatty v0.0.20 github.com/miekg/dns v1.1.64 github.com/mimuret/golang-iij-dpf v0.9.1 - github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 + github.com/namedotcom/go/v4 v4.0.2 github.com/nrdcg/auroradns v1.1.0 github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 github.com/nrdcg/desec v0.10.0 @@ -68,7 +68,7 @@ require ( github.com/sacloud/iaas-api-go v1.14.0 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32 github.com/selectel/domains-go v1.1.0 - github.com/selectel/go-selvpcclient/v3 v3.2.1 + github.com/selectel/go-selvpcclient/v4 v4.1.0 github.com/softlayer/softlayer-go v1.1.7 github.com/stretchr/testify v1.10.0 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1128 @@ -172,7 +172,7 @@ require ( github.com/sacloud/packages-go v0.0.10 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/shopspring/decimal v1.3.1 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartystreets/assertions v1.0.1 // indirect github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect diff --git a/go.sum b/go.sum index 707d92038..14925505a 100644 --- a/go.sum +++ b/go.sum @@ -226,8 +226,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= -github.com/dnsimple/dnsimple-go v1.7.0 h1:JKu9xJtZ3SqOC+BuYgAWeab7+EEx0sz422vu8j611ZY= -github.com/dnsimple/dnsimple-go v1.7.0/go.mod h1:EKpuihlWizqYafSnQHGCd/gyvy3HkEQJ7ODB4KdV8T8= +github.com/dnsimple/dnsimple-go/v4 v4.0.0 h1:nUCICZSyZDiiqimAAL+E8XL+0sKGks5VRki5S8XotRo= +github.com/dnsimple/dnsimple-go/v4 v4.0.0/go.mod h1:AXT2yfAFOntJx6iMeo1J/zKBw0ggXFYBt4e97dqqPnc= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= @@ -621,8 +621,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g= -github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= +github.com/namedotcom/go/v4 v4.0.2 h1:4gNkPaPRG/2tqFNUUof7jAVsA6vDutFutEOd7ivnDwA= +github.com/namedotcom/go/v4 v4.0.2/go.mod h1:J6sVueHMb0qbarPgdhrzEVhEaYp+R1SCaTGl2s6/J1Q= github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= github.com/nats-io/nats-server/v2 v2.5.0/go.mod h1:Kj86UtrXAL6LwYRA6H4RqzkHhK0Vcv2ZnKD5WbQ1t3g= @@ -779,10 +779,10 @@ github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32/go.mod h1:kzh+BSAvpoyHHdHBCDh github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/selectel/domains-go v1.1.0 h1:futG50J43ALLKQAnZk9H9yOtLGnSUh7c5hSvuC5gSHo= github.com/selectel/domains-go v1.1.0/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA= -github.com/selectel/go-selvpcclient/v3 v3.2.1 h1:ny6WIAMiHzKxOgOEnwcWE79wIQij1AHHylzPA41MXCw= -github.com/selectel/go-selvpcclient/v3 v3.2.1/go.mod h1:3EfSf8aEWyhspOGbvZ6mvnFg7JN5uckxNyBFPGWsXNQ= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/selectel/go-selvpcclient/v4 v4.1.0 h1:22lBp+rzg9g2MP4iiGhpVAcCt0kMv7I7uV1W3taLSvQ= +github.com/selectel/go-selvpcclient/v4 v4.1.0/go.mod h1:eFhL1KUW159KOJVeGO7k/Uxl0TYd/sBkWXjuF5WxmYk= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= diff --git a/providers/dns/dnsimple/dnsimple.go b/providers/dns/dnsimple/dnsimple.go index 3cdbe73b4..737f214e9 100644 --- a/providers/dns/dnsimple/dnsimple.go +++ b/providers/dns/dnsimple/dnsimple.go @@ -8,7 +8,7 @@ import ( "strconv" "time" - "github.com/dnsimple/dnsimple-go/dnsimple" + "github.com/dnsimple/dnsimple-go/v4/dnsimple" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" diff --git a/providers/dns/namedotcom/namedotcom.go b/providers/dns/namedotcom/namedotcom.go index 5b2bbaf21..789599552 100644 --- a/providers/dns/namedotcom/namedotcom.go +++ b/providers/dns/namedotcom/namedotcom.go @@ -10,7 +10,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/namedotcom/go/namecom" + "github.com/namedotcom/go/v4/namecom" ) // Environment variables names. diff --git a/providers/dns/selectelv2/selectelv2.go b/providers/dns/selectelv2/selectelv2.go index f638b0a3f..19e352d7f 100644 --- a/providers/dns/selectelv2/selectelv2.go +++ b/providers/dns/selectelv2/selectelv2.go @@ -13,7 +13,7 @@ import ( "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" selectelapi "github.com/selectel/domains-go/pkg/v2" - "github.com/selectel/go-selvpcclient/v3/selvpcclient" + "github.com/selectel/go-selvpcclient/v4/selvpcclient" "golang.org/x/net/idna" ) From e9a255df9b04195245545ae8b15c520c54beb369 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 9 May 2025 20:27:42 +0200 Subject: [PATCH 097/298] pdns: improve error messages (#2526) --- providers/dns/pdns/pdns.go | 55 +++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/providers/dns/pdns/pdns.go b/providers/dns/pdns/pdns.go index c2f780ba8..ec0ae2a70 100644 --- a/providers/dns/pdns/pdns.go +++ b/providers/dns/pdns/pdns.go @@ -121,6 +121,8 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) @@ -128,11 +130,9 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("pdns: could not find zone for domain %q: %w", domain, err) } - ctx := context.Background() - zone, err := d.client.GetHostedZone(ctx, authZone) if err != nil { - return fmt.Errorf("pdns: %w", err) + return fmt.Errorf("pdns: get hosted zone for %s: %w", authZone, err) } name := info.EffectiveFQDN @@ -144,13 +144,12 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // Look for existing records. existingRRSet := findTxtRecord(zone, info.EffectiveFQDN) - // merge the existing and new records var records []internal.Record if existingRRSet != nil { records = existingRRSet.Records } - rec := internal.Record{ + records = append(records, internal.Record{ Content: strconv.Quote(info.Value), Disabled: false, @@ -158,31 +157,36 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Type: "TXT", Name: name, TTL: d.config.TTL, - } + }) rrSets := internal.RRSets{ - RRSets: []internal.RRSet{ - { - Name: name, - ChangeType: "REPLACE", - Type: "TXT", - Kind: "Master", - TTL: d.config.TTL, - Records: append(records, rec), - }, - }, + RRSets: []internal.RRSet{{ + Name: name, + ChangeType: "REPLACE", + Type: "TXT", + Kind: "Master", + TTL: d.config.TTL, + Records: records, + }}, } err = d.client.UpdateRecords(ctx, zone, rrSets) if err != nil { - return fmt.Errorf("pdns: %w", err) + return fmt.Errorf("pdns: update records: %w", err) } - return d.client.Notify(ctx, zone) + err = d.client.Notify(ctx, zone) + if err != nil { + return fmt.Errorf("pdns: notify: %w", err) + } + + return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) @@ -190,15 +194,13 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("pdns: could not find zone for domain %q: %w", domain, err) } - ctx := context.Background() - zone, err := d.client.GetHostedZone(ctx, authZone) if err != nil { - return fmt.Errorf("pdns: %w", err) + return fmt.Errorf("pdns: get hosted zone for %s: %w", authZone, err) } + // Look for existing records. set := findTxtRecord(zone, info.EffectiveFQDN) - if set == nil { return fmt.Errorf("pdns: no existing record found for %s", info.EffectiveFQDN) } @@ -225,10 +227,15 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { err = d.client.UpdateRecords(ctx, zone, internal.RRSets{RRSets: []internal.RRSet{rrSet}}) if err != nil { - return fmt.Errorf("pdns: %w", err) + return fmt.Errorf("pdns: update records: %w", err) } - return d.client.Notify(ctx, zone) + err = d.client.Notify(ctx, zone) + if err != nil { + return fmt.Errorf("pdns: notify: %w", err) + } + + return nil } func findTxtRecord(zone *internal.HostedZone, fqdn string) *internal.RRSet { From 476f9ed9100ed25d7a4c37eb6946d7831e3a9e09 Mon Sep 17 00:00:00 2001 From: Andrew Imeson Date: Mon, 19 May 2025 16:22:10 -0400 Subject: [PATCH 098/298] docs(cPanel): fix examples (#2529) --- docs/content/dns/zz_gen_cpanel.md | 14 +++++++------- providers/dns/cpanel/cpanel.toml | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/content/dns/zz_gen_cpanel.md b/docs/content/dns/zz_gen_cpanel.md index 2cbc5030c..48cb229e7 100644 --- a/docs/content/dns/zz_gen_cpanel.md +++ b/docs/content/dns/zz_gen_cpanel.md @@ -28,17 +28,17 @@ Here is an example bash command using the CPanel/WHM provider: ```bash ### CPANEL (default) -CPANEL_USERNAME = "yyyy" -CPANEL_TOKEN = "xxxx" -CPANEL_BASE_URL = "https://example.com:2083" \ +CPANEL_USERNAME="yyyy" \ +CPANEL_TOKEN="xxxx" \ +CPANEL_BASE_URL="https://example.com:2083" \ lego --email you@example.com --dns cpanel -d '*.example.com' -d example.com run ## WHM -CPANEL_MODE = whm -CPANEL_USERNAME = "yyyy" -CPANEL_TOKEN = "xxxx" -CPANEL_BASE_URL = "https://example.com:2087" \ +CPANEL_MODE=whm \ +CPANEL_USERNAME="yyyy" \ +CPANEL_TOKEN="xxxx" \ +CPANEL_BASE_URL="https://example.com:2087" \ lego --email you@example.com --dns cpanel -d '*.example.com' -d example.com run ``` diff --git a/providers/dns/cpanel/cpanel.toml b/providers/dns/cpanel/cpanel.toml index 6b7359e41..faed2abe2 100644 --- a/providers/dns/cpanel/cpanel.toml +++ b/providers/dns/cpanel/cpanel.toml @@ -7,17 +7,17 @@ Since = "v4.16.0" Example = ''' ### CPANEL (default) -CPANEL_USERNAME = "yyyy" -CPANEL_TOKEN = "xxxx" -CPANEL_BASE_URL = "https://example.com:2083" \ +CPANEL_USERNAME="yyyy" \ +CPANEL_TOKEN="xxxx" \ +CPANEL_BASE_URL="https://example.com:2083" \ lego --email you@example.com --dns cpanel -d '*.example.com' -d example.com run ## WHM -CPANEL_MODE = whm -CPANEL_USERNAME = "yyyy" -CPANEL_TOKEN = "xxxx" -CPANEL_BASE_URL = "https://example.com:2087" \ +CPANEL_MODE=whm \ +CPANEL_USERNAME="yyyy" \ +CPANEL_TOKEN="xxxx" \ +CPANEL_BASE_URL="https://example.com:2087" \ lego --email you@example.com --dns cpanel -d '*.example.com' -d example.com run ''' From f05362515a524dc9be5e8195ff6ff5bf6e263c08 Mon Sep 17 00:00:00 2001 From: Benjamin Schwarze Date: Fri, 6 Jun 2025 12:49:46 +0200 Subject: [PATCH 099/298] nicmanager: fix mode env var name and value (#2534) Co-authored-by: Fernandez Ludovic --- cmd/zz_gen_cmd_dnshelp.go | 2 +- docs/content/dns/zz_gen_nicmanager.md | 4 ++-- providers/dns/nicmanager/internal/client.go | 2 +- providers/dns/nicmanager/nicmanager.go | 4 ++-- providers/dns/nicmanager/nicmanager.toml | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 1d332f27d..c160bd826 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -2307,7 +2307,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "NICMANAGER_API_MODE": mode: 'anycast' or 'zone' (default: 'anycast')`) + ew.writeln(` - "NICMANAGER_API_MODE": mode: 'anycast' or 'zones' (for FreeDNS) (default: 'anycast')`) ew.writeln(` - "NICMANAGER_API_OTP": TOTP Secret (optional)`) ew.writeln(` - "NICMANAGER_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) ew.writeln(` - "NICMANAGER_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) diff --git a/docs/content/dns/zz_gen_nicmanager.md b/docs/content/dns/zz_gen_nicmanager.md index db06fb2a6..0b6e1b2cb 100644 --- a/docs/content/dns/zz_gen_nicmanager.md +++ b/docs/content/dns/zz_gen_nicmanager.md @@ -68,7 +68,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `NICMANAGER_API_MODE` | mode: 'anycast' or 'zone' (default: 'anycast') | +| `NICMANAGER_API_MODE` | mode: 'anycast' or 'zones' (for FreeDNS) (default: 'anycast') | | `NICMANAGER_API_OTP` | TOTP Secret (optional) | | `NICMANAGER_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | | `NICMANAGER_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | @@ -81,7 +81,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). ## Description You can log in using your account name + username or using your email address. -Optionally if TOTP is configured for your account, set `NICMANAGER_API_OTP`. +Optionally, if TOTP is configured for your account, set `NICMANAGER_API_OTP`. diff --git a/providers/dns/nicmanager/internal/client.go b/providers/dns/nicmanager/internal/client.go index 3134fc4fd..5632ae9ad 100644 --- a/providers/dns/nicmanager/internal/client.go +++ b/providers/dns/nicmanager/internal/client.go @@ -23,7 +23,7 @@ const ( // Modes. const ( ModeAnycast = "anycast" - ModeZone = "zone" + ModeZone = "zones" ) // Options the Client options. diff --git a/providers/dns/nicmanager/nicmanager.go b/providers/dns/nicmanager/nicmanager.go index f9307d8c1..2a5742373 100644 --- a/providers/dns/nicmanager/nicmanager.go +++ b/providers/dns/nicmanager/nicmanager.go @@ -24,7 +24,7 @@ const ( EnvEmail = envNamespace + "API_EMAIL" EnvPassword = envNamespace + "API_PASSWORD" EnvOTP = envNamespace + "API_OTP" - EnvMode = envNamespace + "MODE" + EnvMode = envNamespace + "API_MODE" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -85,7 +85,7 @@ func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() config.Password = values[EnvPassword] - config.Mode = env.GetOrDefaultString(EnvMode, internal.ModeAnycast) + config.Mode = env.GetOneWithFallback(EnvMode, internal.ModeAnycast, env.ParseString, envNamespace+"MODE") config.Username = env.GetOrFile(EnvUsername) config.Login = env.GetOrFile(EnvLogin) config.Email = env.GetOrFile(EnvEmail) diff --git a/providers/dns/nicmanager/nicmanager.toml b/providers/dns/nicmanager/nicmanager.toml index cfe41455c..7fdf296c4 100644 --- a/providers/dns/nicmanager/nicmanager.toml +++ b/providers/dns/nicmanager/nicmanager.toml @@ -31,7 +31,7 @@ Additional = ''' ## Description You can log in using your account name + username or using your email address. -Optionally if TOTP is configured for your account, set `NICMANAGER_API_OTP`. +Optionally, if TOTP is configured for your account, set `NICMANAGER_API_OTP`. ''' [Configuration] @@ -42,7 +42,7 @@ Optionally if TOTP is configured for your account, set `NICMANAGER_API_OTP`. NICMANAGER_API_PASSWORD = "Password, always required" [Configuration.Additional] NICMANAGER_API_OTP = "TOTP Secret (optional)" - NICMANAGER_API_MODE = "mode: 'anycast' or 'zone' (default: 'anycast')" + NICMANAGER_API_MODE = "mode: 'anycast' or 'zones' (for FreeDNS) (default: 'anycast')" NICMANAGER_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" NICMANAGER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" NICMANAGER_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 900)" From 375300f969cf56133e10ebdbf33ffba831c8e9fe Mon Sep 17 00:00:00 2001 From: mlec <42201667+mlec1@users.noreply.github.com> Date: Sun, 8 Jun 2025 23:17:45 +0200 Subject: [PATCH 100/298] exoscale: fix find record (#2535) Co-authored-by: Fernandez Ludovic --- providers/dns/exoscale/exoscale.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/providers/dns/exoscale/exoscale.go b/providers/dns/exoscale/exoscale.go index 4038ee4d4..8fd39eaaf 100644 --- a/providers/dns/exoscale/exoscale.go +++ b/providers/dns/exoscale/exoscale.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "time" egoscale "github.com/exoscale/egoscale/v3" @@ -215,7 +216,8 @@ func (d *DNSProvider) findExistingRecordID(zoneID egoscale.UUID, recordName stri } for _, record := range records.DNSDomainRecords { - if record.Name == recordName && record.Type == egoscale.DNSDomainRecordTypeTXT && record.Content == value { + if record.Name == recordName && record.Type == egoscale.DNSDomainRecordTypeTXT && + (record.Content == value || record.Content == strconv.Quote(value)) { return record.ID, nil } } From 7571c0bd314855a0bdb61a7bbfd7fd9921826070 Mon Sep 17 00:00:00 2001 From: Joel Strasser Date: Thu, 19 Jun 2025 13:33:38 +0200 Subject: [PATCH 101/298] Add DNS provider for DynDnsFree.de (#2540) Co-authored-by: Fernandez Ludovic --- README.md | 56 +++---- cmd/zz_gen_cmd_dnshelp.go | 21 +++ docs/content/dns/zz_gen_dyndnsfree.md | 68 +++++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/dyndnsfree/dyndnsfree.go | 118 +++++++++++++++ providers/dns/dyndnsfree/dyndnsfree.toml | 23 +++ providers/dns/dyndnsfree/dyndnsfree_test.go | 143 ++++++++++++++++++ providers/dns/dyndnsfree/internal/client.go | 78 ++++++++++ .../dns/dyndnsfree/internal/client_test.go | 58 +++++++ providers/dns/zz_gen_dns_providers.go | 3 + 10 files changed, 541 insertions(+), 29 deletions(-) create mode 100644 docs/content/dns/zz_gen_dyndnsfree.md create mode 100644 providers/dns/dyndnsfree/dyndnsfree.go create mode 100644 providers/dns/dyndnsfree/dyndnsfree.toml create mode 100644 providers/dns/dyndnsfree/dyndnsfree_test.go create mode 100644 providers/dns/dyndnsfree/internal/client.go create mode 100644 providers/dns/dyndnsfree/internal/client_test.go diff --git a/README.md b/README.md index c4d4fd9c2..90e60436a 100644 --- a/README.md +++ b/README.md @@ -108,145 +108,145 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Duck DNS Dyn + DynDnsFree.de Dynu EasyDNS Efficient IP - Epik + Epik Exoscale External program F5 XC - freemyip.com + freemyip.com G-Core Gandi Gandi Live DNS (v5) - Glesys + Glesys Go Daddy Google Cloud Google Domains - Hetzner + Hetzner Hosting.de Hosttech HTTP request - http.net + http.net Huawei Cloud Hurricane Electric DNS HyperOne - IBM Cloud (SoftLayer) + IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox Infomaniak - Internet Initiative Japan + Internet Initiative Japan Internet.bs INWX Ionos - IPv64 + IPv64 iwantmyname Joker Joohoi's ACME-DNS - Liara + Liara Lima-City Linode (v4) Liquid Web - Loopia + Loopia LuaDNS Mail-in-a-Box ManageEngine CloudDNS - Manual + Manual Metaname Metaregistrar mijn.host - Mittwald + Mittwald myaddr.{tools,dev,io} MyDNS.jp MythicBeasts - Name.com + Name.com Namecheap Namesilo NearlyFreeSpeech.NET - Netcup + Netcup Netlify Nicmanager NIFCloud - Njalla + Njalla Nodion NS1 Open Telekom Cloud - Oracle Cloud + Oracle Cloud OVH plesk.com Porkbun - PowerDNS + PowerDNS Rackspace Rain Yun/雨云 RcodeZero - reg.ru + reg.ru Regfish RFC2136 RimuHosting - RU CENTER + RU CENTER Sakura Cloud Scaleway Selectel - Selectel v2 + Selectel v2 SelfHost.(de|eu) Servercow Shellrent - Simply.com + Simply.com Sonic Spaceship Stackpath - Technitium + Technitium Tencent Cloud DNS Timeweb Cloud TransIP - UKFast SafeDNS + UKFast SafeDNS Ultradns Variomedia VegaDNS - Vercel + Vercel Versio.[nl|eu|uk] VinylDNS VK Cloud - Volcano Engine/火山引擎 + Volcano Engine/火山引擎 Vscale Vultr Webnames - Websupport + Websupport WEDOS West.cn/西部数码 Yandex 360 - Yandex Cloud + Yandex Cloud Yandex PDD Zone.ee Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index c160bd826..ee6f726c4 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -55,6 +55,7 @@ func allDNSCodes() string { "dreamhost", "duckdns", "dyn", + "dyndnsfree", "dynu", "easydns", "edgedns", @@ -1096,6 +1097,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/dyn`) + case "dyndnsfree": + // generated from: providers/dns/dyndnsfree/dyndnsfree.toml + ew.writeln(`Configuration for DynDnsFree.de.`) + ew.writeln(`Code: 'dyndnsfree'`) + ew.writeln(`Since: 'v4.23.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "DYNDNSFREE_PASSWORD": Password`) + ew.writeln(` - "DYNDNSFREE_USERNAME": Username`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "DYNDNSFREE_HTTP_TIMEOUT": Request timeout in seconds (Default: 30)`) + ew.writeln(` - "DYNDNSFREE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "DYNDNSFREE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/dyndnsfree`) + case "dynu": // generated from: providers/dns/dynu/dynu.toml ew.writeln(`Configuration for Dynu.`) diff --git a/docs/content/dns/zz_gen_dyndnsfree.md b/docs/content/dns/zz_gen_dyndnsfree.md new file mode 100644 index 000000000..6f4cf46ff --- /dev/null +++ b/docs/content/dns/zz_gen_dyndnsfree.md @@ -0,0 +1,68 @@ +--- +title: "DynDnsFree.de" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: dyndnsfree +dnsprovider: + since: "v4.23.0" + code: "dyndnsfree" + url: "https://www.dyndnsfree.de" +--- + + + + + + +Configuration for [DynDnsFree.de](https://www.dyndnsfree.de). + + + + +- Code: `dyndnsfree` +- Since: v4.23.0 + + +Here is an example bash command using the DynDnsFree.de provider: + +```bash +DYNDNSFREE_USERNAME="xxx" \ +DYNDNSFREE_PASSWORD="yyy" \ +lego --email you@example.com --dns dyndnsfree -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `DYNDNSFREE_PASSWORD` | Password | +| `DYNDNSFREE_USERNAME` | Username | + +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 | +|--------------------------------|-------------| +| `DYNDNSFREE_HTTP_TIMEOUT` | Request timeout in seconds (Default: 30) | +| `DYNDNSFREE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `DYNDNSFREE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | + +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://www.dyndnsfree.de/user/hilfe.php?hsm=2) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index f06353e4c..8fcb47ba6 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -150,7 +150,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi + acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/dyndnsfree/dyndnsfree.go b/providers/dns/dyndnsfree/dyndnsfree.go new file mode 100644 index 000000000..8c1d87aaa --- /dev/null +++ b/providers/dns/dyndnsfree/dyndnsfree.go @@ -0,0 +1,118 @@ +// Package dyndnsfree implements a DNS provider for solving the DNS-01 challenge using DynDnsFree.de API. +package dyndnsfree + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/dyndnsfree/internal" +) + +// Environment variables names. +const ( + envNamespace = "DYNDNSFREE_" + + EnvUsername = envNamespace + "USERNAME" + EnvPassword = envNamespace + "PASSWORD" + + 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 + + PropagationTimeout time.Duration + PollingInterval time.Duration + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for DynDNSFree. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvUsername, EnvPassword) + if err != nil { + return nil, fmt.Errorf("dyndnsfree: %w", err) + } + + config := NewDefaultConfig() + config.Username = values[EnvUsername] + config.Password = values[EnvPassword] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for DynDNSFree. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("dyndnsfree: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.Username, config.Password) + if err != nil { + return nil, fmt.Errorf("dyndnsfree: new client: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("dyndnsforfree: could not find zone for domain %q: %w", domain, err) + } + + err = d.client.AddTXTRecord(context.Background(), dns01.UnFqdn(authZone), dns01.UnFqdn(info.EffectiveFQDN), info.Value) + if err != nil { + return fmt.Errorf("dyndnsfree: add record: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + // Records are deleted automatically. + + 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/dyndnsfree/dyndnsfree.toml b/providers/dns/dyndnsfree/dyndnsfree.toml new file mode 100644 index 000000000..dd354fb33 --- /dev/null +++ b/providers/dns/dyndnsfree/dyndnsfree.toml @@ -0,0 +1,23 @@ +Name = "DynDnsFree.de" +Description = '''''' +URL = "https://www.dyndnsfree.de" +Code = "dyndnsfree" +Since = "v4.23.0" + +Example = ''' +DYNDNSFREE_USERNAME="xxx" \ +DYNDNSFREE_PASSWORD="yyy" \ +lego --email you@example.com --dns dyndnsfree -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + DYNDNSFREE_USERNAME = "Username" + DYNDNSFREE_PASSWORD = "Password" + [Configuration.Additional] + DYNDNSFREE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + DYNDNSFREE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + DYNDNSFREE_HTTP_TIMEOUT = "Request timeout in seconds (Default: 30)" + +[Links] + API = "https://www.dyndnsfree.de/user/hilfe.php?hsm=2" diff --git a/providers/dns/dyndnsfree/dyndnsfree_test.go b/providers/dns/dyndnsfree/dyndnsfree_test.go new file mode 100644 index 000000000..cb063a029 --- /dev/null +++ b/providers/dns/dyndnsfree/dyndnsfree_test.go @@ -0,0 +1,143 @@ +package dyndnsfree + +import ( + "testing" + + "github.com/go-acme/lego/v4/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: "user", + EnvPassword: "secret", + }, + }, + { + desc: "missing username", + envVars: map[string]string{ + EnvUsername: "", + EnvPassword: "secret", + }, + expected: "dyndnsfree: some credentials information are missing: DYNDNSFREE_USERNAME", + }, + { + desc: "missing password", + envVars: map[string]string{ + EnvUsername: "user", + EnvPassword: "", + }, + expected: "dyndnsfree: some credentials information are missing: DYNDNSFREE_PASSWORD", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "dyndnsfree: some credentials information are missing: DYNDNSFREE_USERNAME,DYNDNSFREE_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 test.expected == "" { + 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: "user", + password: "secret", + }, + { + desc: "missing username", + username: "", + password: "secret", + expected: "dyndnsfree: new client: credentials missing", + }, + { + desc: "missing password", + username: "user", + password: "", + expected: "dyndnsfree: new client: credentials missing", + }, + { + desc: "missing credentials", + expected: "dyndnsfree: new client: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Username = test.username + config.Password = test.password + + p, err := NewDNSProviderConfig(config) + + if test.expected == "" { + 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) + + err = provider.CleanUp(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/dyndnsfree/internal/client.go b/providers/dns/dyndnsfree/internal/client.go new file mode 100644 index 000000000..02a1f1607 --- /dev/null +++ b/providers/dns/dyndnsfree/internal/client.go @@ -0,0 +1,78 @@ +package internal + +import ( + "bytes" + "context" + "errors" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const defaultBaseURL = "https://dynup.de/acme.php" + +type Client struct { + username string + password string + + baseURL string + HTTPClient *http.Client +} + +func NewClient(username, password string) (*Client, error) { + if username == "" || password == "" { + return nil, errors.New("credentials missing") + } + + return &Client{ + username: username, + password: password, + baseURL: defaultBaseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) AddTXTRecord(ctx context.Context, zone, hostname, value string) error { + baseURL, err := url.Parse(c.baseURL) + if err != nil { + return err + } + + query := baseURL.Query() + query.Set("username", c.username) + query.Set("password", c.password) + query.Set("hostname", zone) + query.Set("add_hostname", hostname) + query.Set("txt", value) + baseURL.RawQuery = query.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL.String(), http.NoBody) + if err != nil { + return err + } + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return errutils.NewUnexpectedResponseStatusCodeError(req, resp) + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + if !bytes.Equal(raw, []byte("success")) { + return errors.New(string(raw)) + } + + return nil +} diff --git a/providers/dns/dyndnsfree/internal/client_test.go b/providers/dns/dyndnsfree/internal/client_test.go new file mode 100644 index 000000000..17ac89b0f --- /dev/null +++ b/providers/dns/dyndnsfree/internal/client_test.go @@ -0,0 +1,58 @@ +package internal + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" +) + +func setupTest(t *testing.T, message string) *Client { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + client, err := NewClient("user", "secret") + require.NoError(t, err) + + client.HTTPClient = server.Client() + client.baseURL = server.URL + + mux.HandleFunc("GET /", func(rw http.ResponseWriter, req *http.Request) { + query := req.URL.Query() + + username := query.Get("username") + if username != "user" { + http.Error(rw, "invalid username: "+username, http.StatusUnauthorized) + return + } + + password := query.Get("password") + if password != "secret" { + http.Error(rw, "invalid password: "+password, http.StatusUnauthorized) + return + } + + _, _ = rw.Write([]byte(message)) + }) + + return client +} + +func TestAddTXTRecord(t *testing.T) { + client := setupTest(t, "success") + + err := client.AddTXTRecord(context.Background(), "example.com", "sub.example.com", "value") + require.NoError(t, err) +} + +func TestAddTXTRecord_error(t *testing.T) { + client := setupTest(t, "error: authentification failed") + + err := client.AddTXTRecord(context.Background(), "example.com", "sub.example.com", "value") + require.EqualError(t, err, "error: authentification failed") +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index eba28d83e..ef9dcba28 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -49,6 +49,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/dreamhost" "github.com/go-acme/lego/v4/providers/dns/duckdns" "github.com/go-acme/lego/v4/providers/dns/dyn" + "github.com/go-acme/lego/v4/providers/dns/dyndnsfree" "github.com/go-acme/lego/v4/providers/dns/dynu" "github.com/go-acme/lego/v4/providers/dns/easydns" "github.com/go-acme/lego/v4/providers/dns/edgedns" @@ -252,6 +253,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return duckdns.NewDNSProvider() case "dyn": return dyn.NewDNSProvider() + case "dyndnsfree": + return dyndnsfree.NewDNSProvider() case "dynu": return dynu.NewDNSProvider() case "easydns": From a528e280f9e95982ef0c4f0771d99ed51028c5b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Colombaro?= Date: Thu, 19 Jun 2025 16:31:33 +0200 Subject: [PATCH 102/298] docs: update reference ACME ARI RFC 9773 in place of the draft (#2541) --- README.md | 2 +- acme/api/order.go | 4 ++-- acme/api/renewal.go | 2 +- acme/commons.go | 10 +++++----- acme/errors.go | 2 +- certificate/certificates.go | 4 ++-- certificate/renewal.go | 10 +++++----- cmd/cmd_renew.go | 2 +- docs/content/_index.md | 2 +- docs/data/zz_cli_help.toml | 2 +- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 90e60436a..7f149109b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Let's Encrypt client and ACME library written in Go. - ACME v2 [RFC 8555](https://www.rfc-editor.org/rfc/rfc8555.html) - Support [RFC 8737](https://www.rfc-editor.org/rfc/rfc8737.html): TLS Application‑Layer Protocol Negotiation (ALPN) Challenge Extension - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): certificates for IP addresses - - Support [draft-ietf-acme-ari-03](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/): Renewal Information (ARI) Extension + - Support [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html): Renewal Information (ARI) Extension - Support [draft-aaron-acme-profiles-00](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/): Profiles Extension - Comes with about [150 DNS providers](https://go-acme.github.io/lego/dns) - Register with CA diff --git a/acme/api/order.go b/acme/api/order.go index dd42fb445..96cd4d287 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -23,7 +23,7 @@ type OrderOptions struct { // A string uniquely identifying a previously-issued certificate which this // order is intended to replace. - // - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5 + // - https://www.rfc-editor.org/rfc/rfc9773.html#section-5 ReplacesCertID string } @@ -77,7 +77,7 @@ func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acm // If the Server rejects the request because the identified certificate has already been marked as replaced, // it MUST return an HTTP 409 (Conflict) with a problem document of type "alreadyReplaced" (see Section 7.4). - // https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-08#section-5 + // https://www.rfc-editor.org/rfc/rfc9773.html#section-5 orderReq.Replaces = "" resp, err = o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order) diff --git a/acme/api/renewal.go b/acme/api/renewal.go index 5b4046c69..aca3d8def 100644 --- a/acme/api/renewal.go +++ b/acme/api/renewal.go @@ -14,7 +14,7 @@ var ErrNoARI = errors.New("renewalInfo[get/post]: server does not advertise a re // Note: this endpoint is part of a draft specification, not all ACME servers will implement it. // This method will return api.ErrNoARI if the server does not advertise a renewal info endpoint. // -// https://datatracker.ietf.org/doc/draft-ietf-acme-ari +// https://www.rfc-editor.org/rfc/rfc9773.html func (c *CertificateService) GetRenewalInfo(certID string) (*http.Response, error) { if c.core.GetDirectory().RenewalInfo == "" { return nil, ErrNoARI diff --git a/acme/commons.go b/acme/commons.go index d22ea96ae..489c74938 100644 --- a/acme/commons.go +++ b/acme/commons.go @@ -38,7 +38,7 @@ const ( // Directory the ACME directory object. // - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.1 -// - https://datatracker.ietf.org/doc/draft-ietf-acme-ari/ +// - https://www.rfc-editor.org/rfc/rfc9773.html type Directory struct { NewNonceURL string `json:"newNonce"` NewAccountURL string `json:"newAccount"` @@ -196,7 +196,7 @@ type Order struct { // replaces (optional, string): // replaces (string, optional): A string uniquely identifying a // previously-issued certificate which this order is intended to replace. - // - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5 + // - https://www.rfc-editor.org/rfc/rfc9773.html#section-5 Replaces string `json:"replaces,omitempty"` } @@ -349,7 +349,7 @@ type Window struct { } // RenewalInfoResponse is the response to GET requests made the renewalInfo endpoint. -// - (4.1. Getting Renewal Information) https://datatracker.ietf.org/doc/draft-ietf-acme-ari/ +// - (4.1. Getting Renewal Information) https://www.rfc-editor.org/rfc/rfc9773.html type RenewalInfoResponse struct { // SuggestedWindow contains two fields, start and end, // whose values are timestamps which bound the window of time in which the CA recommends renewing the certificate. @@ -362,11 +362,11 @@ type RenewalInfoResponse struct { } // RenewalInfoUpdateRequest is the JWS payload for POST requests made to the renewalInfo endpoint. -// - (4.2. RenewalInfo Objects) https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-4.2 +// - (4.2. RenewalInfo Objects) https://www.rfc-editor.org/rfc/rfc9773.html#section-4.2 type RenewalInfoUpdateRequest struct { // CertID is a composite string in the format: base64url(AKI) || '.' || base64url(Serial), where AKI is the // certificate's authority key identifier and Serial is the certificate's serial number. For details, see: - // https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-4.1 + // https://www.rfc-editor.org/rfc/rfc9773.html#section-4.1 CertID string `json:"certID"` // Replaced is required and indicates whether or not the client considers the certificate to have been replaced. // A certificate is considered replaced when its revocation would not disrupt any ongoing services, diff --git a/acme/errors.go b/acme/errors.go index a9d6353d0..9a255468d 100644 --- a/acme/errors.go +++ b/acme/errors.go @@ -60,7 +60,7 @@ type NonceError struct { // AlreadyReplacedError represents the error which is returned // If the Server rejects the request because the identified certificate has already been marked as replaced. -// - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-08#section-5 +// - https://www.rfc-editor.org/rfc/rfc9773.html#section-5 type AlreadyReplacedError struct { *ProblemDetails } diff --git a/certificate/certificates.go b/certificate/certificates.go index d03e99c43..44931dd50 100644 --- a/certificate/certificates.go +++ b/certificate/certificates.go @@ -84,7 +84,7 @@ type ObtainRequest struct { // A string uniquely identifying a previously-issued certificate which this // order is intended to replace. - // - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5 + // - https://www.rfc-editor.org/rfc/rfc9773.html#section-5 ReplacesCertID string } @@ -113,7 +113,7 @@ type ObtainForCSRRequest struct { // A string uniquely identifying a previously-issued certificate which this // order is intended to replace. - // - https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5 + // - https://www.rfc-editor.org/rfc/rfc9773.html#section-5 ReplacesCertID string } diff --git a/certificate/renewal.go b/certificate/renewal.go index ab215923d..0a9059501 100644 --- a/certificate/renewal.go +++ b/certificate/renewal.go @@ -25,15 +25,15 @@ type RenewalInfoResponse struct { // RetryAfter header indicating the polling interval that the ACME server recommends. // Conforming clients SHOULD query the renewalInfo URL again after the RetryAfter period has passed, // as the server may provide a different suggestedWindow. - // https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-4.2 + // https://www.rfc-editor.org/rfc/rfc9773.html#section-4.2 RetryAfter time.Duration } // ShouldRenewAt determines the optimal renewal time based on the current time (UTC),renewal window suggest by ARI, and the client's willingness to sleep. // It returns a pointer to a time.Time value indicating when the renewal should be attempted or nil if deferred until the next normal wake time. -// This method implements the RECOMMENDED algorithm described in draft-ietf-acme-ari. +// This method implements the RECOMMENDED algorithm described in RFC 9773. // -// - (4.1-11. Getting Renewal Information) https://datatracker.ietf.org/doc/draft-ietf-acme-ari/ +// - (4.1-11. Getting Renewal Information) https://www.rfc-editor.org/rfc/rfc9773.html func (r *RenewalInfoResponse) ShouldRenewAt(now time.Time, willingToSleep time.Duration) *time.Time { // Explicitly convert all times to UTC. now = now.UTC() @@ -71,7 +71,7 @@ func (r *RenewalInfoResponse) ShouldRenewAt(now time.Time, willingToSleep time.D // Note: this endpoint is part of a draft specification, not all ACME servers will implement it. // This method will return api.ErrNoARI if the server does not advertise a renewal info endpoint. // -// https://datatracker.ietf.org/doc/draft-ietf-acme-ari +// https://www.rfc-editor.org/rfc/rfc9773.html func (c *Certifier) GetRenewalInfo(req RenewalInfoRequest) (*RenewalInfoResponse, error) { certID, err := MakeARICertID(req.Cert) if err != nil { @@ -100,7 +100,7 @@ func (c *Certifier) GetRenewalInfo(req RenewalInfoRequest) (*RenewalInfoResponse return &info, nil } -// MakeARICertID constructs a certificate identifier as described in draft-ietf-acme-ari-03, section 4.1. +// MakeARICertID constructs a certificate identifier as described in RFC 9773, section 4.1. func MakeARICertID(leaf *x509.Certificate) (string, error) { if leaf == nil { return "", errors.New("leaf certificate is nil") diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index 6589fbbdd..a46de187c 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -58,7 +58,7 @@ func createRenew() *cli.Command { }, &cli.BoolFlag{ Name: flgARIDisable, - Usage: "Do not use the renewalInfo endpoint (draft-ietf-acme-ari) to check if a certificate should be renewed.", + Usage: "Do not use the renewalInfo endpoint (RFC9773) to check if a certificate should be renewed.", }, &cli.DurationFlag{ Name: flgARIWaitToRenewDuration, diff --git a/docs/content/_index.md b/docs/content/_index.md index 497e7e168..a211c5d62 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -12,7 +12,7 @@ Let's Encrypt client and ACME library written in Go. - ACME v2 [RFC 8555](https://www.rfc-editor.org/rfc/rfc8555.html) - Support [RFC 8737](https://www.rfc-editor.org/rfc/rfc8737.html): TLS Application‑Layer Protocol Negotiation (ALPN) Challenge Extension - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): issues certificates for IP addresses - - Support [draft-ietf-acme-ari-03](https://datatracker.ietf.org/doc/draft-ietf-acme-ari/): Renewal Information (ARI) Extension + - Support [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html): Renewal Information (ARI) Extension - Support [draft-aaron-acme-profiles-00](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/): Profiles Extension - Comes with about [150 DNS providers]({{% ref "dns" %}}) - Register with CA diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 8fcb47ba6..9bebcd31e 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -93,7 +93,7 @@ USAGE: OPTIONS: --days value The number of days left on a certificate to renew it. (default: 30) - --ari-disable Do not use the renewalInfo endpoint (draft-ietf-acme-ari) to check if a certificate should be renewed. (default: false) + --ari-disable Do not use the renewalInfo endpoint (RFC9773) to check if a certificate should be renewed. (default: false) --ari-wait-to-renew-duration value The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint. (default: 0s) --reuse-key Used to indicate you want to reuse your current private key for the new certificate. (default: false) --no-bundle Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false) From 8d7ed798a73138a81f5928e1f699e47c8638597a Mon Sep 17 00:00:00 2001 From: Andrew Johnson <96792784+git-johnson@users.noreply.github.com> Date: Mon, 23 Jun 2025 09:22:55 -0400 Subject: [PATCH 103/298] gcloud: add service account impersonation (#2544) Co-authored-by: Fernandez Ludovic --- cmd/zz_gen_cmd_dnshelp.go | 1 + docs/content/dns/zz_gen_gcloud.md | 20 +++- go.sum | 2 + providers/dns/gcloud/gcloud.toml | 23 ++++- providers/dns/gcloud/googlecloud.go | 116 ++++++++++++++++------- providers/dns/gcloud/googlecloud_test.go | 3 +- 6 files changed, 130 insertions(+), 35 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index ee6f726c4..79c768fe2 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -1361,6 +1361,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "GCE_ALLOW_PRIVATE_ZONE": Allows requested domain to be in private DNS zone, works only with a private ACME server (by default: false)`) + ew.writeln(` - "GCE_IMPERSONATE_SERVICE_ACCOUNT": Service account email to impersonate`) ew.writeln(` - "GCE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 5)`) ew.writeln(` - "GCE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 180)`) ew.writeln(` - "GCE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) diff --git a/docs/content/dns/zz_gen_gcloud.md b/docs/content/dns/zz_gen_gcloud.md index ec830956e..ff228a1c8 100644 --- a/docs/content/dns/zz_gen_gcloud.md +++ b/docs/content/dns/zz_gen_gcloud.md @@ -26,9 +26,21 @@ Configuration for [Google Cloud](https://cloud.google.com). Here is an example bash command using the Google Cloud provider: ```bash +# Using a service account file GCE_PROJECT="gc-project-id" \ GCE_SERVICE_ACCOUNT_FILE="/path/to/svc/account/file.json" \ -lego --email you@email.com --dns gcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run + +# Using default credentials with impersonation +GCE_PROJECT="gc-project-id" \ +GCE_IMPERSONATE_SERVICE_ACCOUNT="target-sa@gc-project-id.iam.gserviceaccount.com" \ +lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run + +# Using service account key with impersonation +GCE_PROJECT="gc-project-id" \ +GCE_SERVICE_ACCOUNT_FILE="/path/to/svc/account/file.json" \ +GCE_IMPERSONATE_SERVICE_ACCOUNT="target-sa@gc-project-id.iam.gserviceaccount.com" \ +lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run ``` @@ -52,6 +64,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `GCE_ALLOW_PRIVATE_ZONE` | Allows requested domain to be in private DNS zone, works only with a private ACME server (by default: false) | +| `GCE_IMPERSONATE_SERVICE_ACCOUNT` | Service account email to impersonate | | `GCE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 5) | | `GCE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 180) | | `GCE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | @@ -60,6 +73,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). 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" %}}). +Supports service account impersonation to access Google Cloud DNS resources across different projects or with restricted permissions. + +When using impersonation, the source service account must have: +1. The "Service Account Token Creator" role on the source service account +2. The "https://www.googleapis.com/auth/cloud-platform" scope diff --git a/go.sum b/go.sum index 14925505a..18a3f3616 100644 --- a/go.sum +++ b/go.sum @@ -914,6 +914,8 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= diff --git a/providers/dns/gcloud/gcloud.toml b/providers/dns/gcloud/gcloud.toml index 07a264dae..471e2e9d1 100644 --- a/providers/dns/gcloud/gcloud.toml +++ b/providers/dns/gcloud/gcloud.toml @@ -5,9 +5,29 @@ Code = "gcloud" Since = "v0.3.0" Example = ''' +# Using a service account file GCE_PROJECT="gc-project-id" \ GCE_SERVICE_ACCOUNT_FILE="/path/to/svc/account/file.json" \ -lego --email you@email.com --dns gcloud -d '*.example.com' -d example.com run +lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run + +# Using default credentials with impersonation +GCE_PROJECT="gc-project-id" \ +GCE_IMPERSONATE_SERVICE_ACCOUNT="target-sa@gc-project-id.iam.gserviceaccount.com" \ +lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run + +# Using service account key with impersonation +GCE_PROJECT="gc-project-id" \ +GCE_SERVICE_ACCOUNT_FILE="/path/to/svc/account/file.json" \ +GCE_IMPERSONATE_SERVICE_ACCOUNT="target-sa@gc-project-id.iam.gserviceaccount.com" \ +lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run +''' + +Additional = ''' +Supports service account impersonation to access Google Cloud DNS resources across different projects or with restricted permissions. + +When using impersonation, the source service account must have: +1. The "Service Account Token Creator" role on the source service account +2. The "https://www.googleapis.com/auth/cloud-platform" scope ''' [Configuration] @@ -19,6 +39,7 @@ lego --email you@email.com --dns gcloud -d '*.example.com' -d example.com run [Configuration.Additional] GCE_ALLOW_PRIVATE_ZONE = "Allows requested domain to be in private DNS zone, works only with a private ACME server (by default: false)" GCE_ZONE_ID = "Allows to skip the automatic detection of the zone" + GCE_IMPERSONATE_SERVICE_ACCOUNT = "Service account email to impersonate" GCE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 5)" GCE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 180)" GCE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" diff --git a/providers/dns/gcloud/googlecloud.go b/providers/dns/gcloud/googlecloud.go index 99c716b62..30a028d61 100644 --- a/providers/dns/gcloud/googlecloud.go +++ b/providers/dns/gcloud/googlecloud.go @@ -17,9 +17,11 @@ import ( "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" "golang.org/x/net/context" + "golang.org/x/oauth2" "golang.org/x/oauth2/google" "google.golang.org/api/dns/v1" "google.golang.org/api/googleapi" + "google.golang.org/api/impersonate" "google.golang.org/api/option" ) @@ -27,11 +29,12 @@ import ( const ( envNamespace = "GCE_" - EnvServiceAccount = envNamespace + "SERVICE_ACCOUNT" - EnvProject = envNamespace + "PROJECT" - EnvZoneID = envNamespace + "ZONE_ID" - EnvAllowPrivateZone = envNamespace + "ALLOW_PRIVATE_ZONE" - EnvDebug = envNamespace + "DEBUG" + EnvServiceAccount = envNamespace + "SERVICE_ACCOUNT" + EnvProject = envNamespace + "PROJECT" + EnvZoneID = envNamespace + "ZONE_ID" + EnvAllowPrivateZone = envNamespace + "ALLOW_PRIVATE_ZONE" + EnvDebug = envNamespace + "DEBUG" + EnvImpersonateServiceAccount = envNamespace + "IMPERSONATE_SERVICE_ACCOUNT" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -44,25 +47,27 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - Debug bool - Project string - ZoneID string - AllowPrivateZone bool - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client + Debug bool + Project string + ZoneID string + AllowPrivateZone bool + ImpersonateServiceAccount 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{ - Debug: env.GetOrDefaultBool(EnvDebug, false), - ZoneID: env.GetOrDefaultString(EnvZoneID, ""), - AllowPrivateZone: env.GetOrDefaultBool(EnvAllowPrivateZone, false), - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 180*time.Second), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 5*time.Second), + Debug: env.GetOrDefaultBool(EnvDebug, false), + ZoneID: env.GetOrDefaultString(EnvZoneID, ""), + AllowPrivateZone: env.GetOrDefaultBool(EnvAllowPrivateZone, false), + ImpersonateServiceAccount: env.GetOrDefaultString(EnvImpersonateServiceAccount, ""), + TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 180*time.Second), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 5*time.Second), } } @@ -95,14 +100,14 @@ func NewDNSProviderCredentials(project string) (*DNSProvider, error) { return nil, errors.New("googlecloud: project name missing") } - client, err := google.DefaultClient(context.Background(), dns.NdevClouddnsReadwriteScope) - if err != nil { - return nil, fmt.Errorf("googlecloud: unable to get Google Cloud client: %w", err) - } - config := NewDefaultConfig() config.Project = project - config.HTTPClient = client + + var err error + config.HTTPClient, err = newClientFromCredentials(context.Background(), config) + if err != nil { + return nil, fmt.Errorf("googlecloud: %w", err) + } return NewDNSProviderConfig(config) } @@ -129,15 +134,14 @@ func NewDNSProviderServiceAccountKey(saKey []byte) (*DNSProvider, error) { project = datJSON.ProjectID } - conf, err := google.JWTConfigFromJSON(saKey, dns.NdevClouddnsReadwriteScope) - if err != nil { - return nil, fmt.Errorf("googlecloud: unable to acquire config: %w", err) - } - client := conf.Client(context.Background()) - config := NewDefaultConfig() config.Project = project - config.HTTPClient = client + + var err error + config.HTTPClient, err = newClientFromServiceAccountKey(context.Background(), config, saKey) + if err != nil { + return nil, fmt.Errorf("googlecloud: %w", err) + } return NewDNSProviderConfig(config) } @@ -384,6 +388,54 @@ func (d *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSe return recs.Rrsets, nil } +func newClientFromCredentials(ctx context.Context, config *Config) (*http.Client, error) { + if config.ImpersonateServiceAccount != "" { + ts, err := google.DefaultTokenSource(ctx, "https://www.googleapis.com/auth/cloud-platform") + if err != nil { + return nil, fmt.Errorf("unable to get default token source: %w", err) + } + + return newImpersonateClient(ctx, config.ImpersonateServiceAccount, ts) + } + + client, err := google.DefaultClient(ctx, dns.NdevClouddnsReadwriteScope) + if err != nil { + return nil, fmt.Errorf("unable to get Google Cloud client: %w", err) + } + + return client, nil +} + +func newClientFromServiceAccountKey(ctx context.Context, config *Config, saKey []byte) (*http.Client, error) { + if config.ImpersonateServiceAccount != "" { + conf, err := google.JWTConfigFromJSON(saKey, "https://www.googleapis.com/auth/cloud-platform") + if err != nil { + return nil, fmt.Errorf("unable to acquire config: %w", err) + } + + return newImpersonateClient(ctx, config.ImpersonateServiceAccount, conf.TokenSource(ctx)) + } + + conf, err := google.JWTConfigFromJSON(saKey, dns.NdevClouddnsReadwriteScope) + if err != nil { + return nil, fmt.Errorf("unable to acquire config: %w", err) + } + + return conf.Client(ctx), nil +} + +func newImpersonateClient(ctx context.Context, impersonateServiceAccount string, ts oauth2.TokenSource) (*http.Client, error) { + impersonatedTS, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{ + TargetPrincipal: impersonateServiceAccount, + Scopes: []string{dns.NdevClouddnsReadwriteScope}, + }, option.WithTokenSource(ts)) + if err != nil { + return nil, fmt.Errorf("unable to create impersonated credentials: %w", err) + } + + return oauth2.NewClient(ctx, impersonatedTS), nil +} + func mustUnquote(raw string) string { clean, err := strconv.Unquote(raw) if err != nil { diff --git a/providers/dns/gcloud/googlecloud_test.go b/providers/dns/gcloud/googlecloud_test.go index 453fdd5ed..b42eab8c2 100644 --- a/providers/dns/gcloud/googlecloud_test.go +++ b/providers/dns/gcloud/googlecloud_test.go @@ -30,7 +30,8 @@ var envTest = tester.NewEnvTest( envServiceAccountFile, envGoogleApplicationCredentials, envMetadataHost, - EnvServiceAccount). + EnvServiceAccount, + EnvImpersonateServiceAccount). WithDomain(envDomain). WithLiveTestExtra(func() bool { _, err := google.DefaultClient(context.Background(), dns.NdevClouddnsReadwriteScope) From 45790d3c6858adfcb9c5f9b846278c43e9c97182 Mon Sep 17 00:00:00 2001 From: George Melikov Date: Sat, 28 Jun 2025 16:01:49 +0300 Subject: [PATCH 104/298] chore: update link to create issue about new provider (#2549) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f149109b..10791925c 100644 --- a/README.md +++ b/README.md @@ -251,4 +251,4 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). -If your DNS provider is not supported, please open an [issue](https://github.com/go-acme/lego/issues/new?assignees=&labels=enhancement%2C+new-provider&template=new_dns_provider.md). +If your DNS provider is not supported, please open an [issue](https://github.com/go-acme/lego/issues/new?assignees=&labels=enhancement%2C+new-provider&template=new_dns_provider.yml). From 6ecdde77f0172cbfce33b37251b56d1f9b1b60e1 Mon Sep 17 00:00:00 2001 From: Marcus Grando Date: Sun, 29 Jun 2025 22:30:00 -0300 Subject: [PATCH 105/298] Add DNS provider for Azion (#2550) Co-authored-by: Fernandez Ludovic --- README.md | 77 ++++--- cmd/zz_gen_cmd_dnshelp.go | 21 ++ docs/content/dns/zz_gen_azion.md | 68 ++++++ docs/data/zz_cli_help.toml | 2 +- go.mod | 1 + go.sum | 3 + providers/dns/azion/azion.go | 309 ++++++++++++++++++++++++++ providers/dns/azion/azion.toml | 23 ++ providers/dns/azion/azion_test.go | 115 ++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 10 files changed, 585 insertions(+), 37 deletions(-) create mode 100644 docs/content/dns/zz_gen_azion.md create mode 100644 providers/dns/azion/azion.go create mode 100644 providers/dns/azion/azion.toml create mode 100644 providers/dns/azion/azion_test.go diff --git a/README.md b/README.md index 10791925c..f527a082d 100644 --- a/README.md +++ b/README.md @@ -65,188 +65,193 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Autodns Axelname + Azion Azure (deprecated) - Azure DNS + Azure DNS Baidu Cloud Bindman Bluecat - BookMyName + BookMyName Brandit (deprecated) Bunny Checkdomain - Civo + Civo Cloud.ru CloudDNS Cloudflare - ClouDNS + ClouDNS CloudXNS (Deprecated) ConoHa v2 ConoHa v3 - Constellix + Constellix Core-Networks CPanel/WHM Derak Cloud - deSEC.io + deSEC.io Designate DNSaaS for Openstack Digital Ocean DirectAdmin - DNS Made Easy + DNS Made Easy dnsHome.de DNSimple DNSPod (deprecated) - Domain Offensive (do.de) + Domain Offensive (do.de) Domeneshop DreamHost Duck DNS - Dyn + Dyn DynDnsFree.de Dynu EasyDNS - Efficient IP + Efficient IP Epik Exoscale External program - F5 XC + F5 XC freemyip.com G-Core Gandi - Gandi Live DNS (v5) + Gandi Live DNS (v5) Glesys Go Daddy Google Cloud - Google Domains + Google Domains Hetzner Hosting.de Hosttech - HTTP request + HTTP request http.net Huawei Cloud Hurricane Electric DNS - HyperOne + HyperOne IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox - Infomaniak + Infomaniak Internet Initiative Japan Internet.bs INWX - Ionos + Ionos IPv64 iwantmyname Joker - Joohoi's ACME-DNS + Joohoi's ACME-DNS Liara Lima-City Linode (v4) - Liquid Web + Liquid Web Loopia LuaDNS Mail-in-a-Box - ManageEngine CloudDNS + ManageEngine CloudDNS Manual Metaname Metaregistrar - mijn.host + mijn.host Mittwald myaddr.{tools,dev,io} MyDNS.jp - MythicBeasts + MythicBeasts Name.com Namecheap Namesilo - NearlyFreeSpeech.NET + NearlyFreeSpeech.NET Netcup Netlify Nicmanager - NIFCloud + NIFCloud Njalla Nodion NS1 - Open Telekom Cloud + Open Telekom Cloud Oracle Cloud OVH plesk.com - Porkbun + Porkbun PowerDNS Rackspace Rain Yun/雨云 - RcodeZero + RcodeZero reg.ru Regfish RFC2136 - RimuHosting + RimuHosting RU CENTER Sakura Cloud Scaleway - Selectel + Selectel Selectel v2 SelfHost.(de|eu) Servercow - Shellrent + Shellrent Simply.com Sonic Spaceship - Stackpath + Stackpath Technitium Tencent Cloud DNS Timeweb Cloud - TransIP + TransIP UKFast SafeDNS Ultradns Variomedia - VegaDNS + VegaDNS Vercel Versio.[nl|eu|uk] VinylDNS - VK Cloud + VK Cloud Volcano Engine/火山引擎 Vscale Vultr - Webnames + Webnames Websupport WEDOS West.cn/西部数码 - 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 79c768fe2..285e3cd80 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -21,6 +21,7 @@ func allDNSCodes() string { "auroradns", "autodns", "axelname", + "azion", "azure", "azuredns", "baiducloud", @@ -346,6 +347,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/axelname`) + case "azion": + // generated from: providers/dns/azion/azion.toml + ew.writeln(`Configuration for Azion.`) + ew.writeln(`Code: 'azion'`) + ew.writeln(`Since: 'v4.24.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "AZION_PERSONAL_TOKEN": Your Azion personal token.`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "AZION_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "AZION_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "AZION_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "AZION_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/azion`) + case "azure": // generated from: providers/dns/azure/azure.toml ew.writeln(`Configuration for Azure (deprecated).`) diff --git a/docs/content/dns/zz_gen_azion.md b/docs/content/dns/zz_gen_azion.md new file mode 100644 index 000000000..be1f5ca17 --- /dev/null +++ b/docs/content/dns/zz_gen_azion.md @@ -0,0 +1,68 @@ +--- +title: "Azion" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: azion +dnsprovider: + since: "v4.24.0" + code: "azion" + url: "https://www.azion.com/en/products/edge-dns/" +--- + + + + + + +Configuration for [Azion](https://www.azion.com/en/products/edge-dns/). + + + + +- Code: `azion` +- Since: v4.24.0 + + +Here is an example bash command using the Azion provider: + +```bash +AZION_PERSONAL_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ +lego --email you@example.com --dns azion -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `AZION_PERSONAL_TOKEN` | Your Azion personal token. | + +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 | +|--------------------------------|-------------| +| `AZION_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `AZION_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `AZION_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `AZION_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://api.azion.com/) +- [Go client](https://github.com/aziontech/azionapi-go-sdk) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 9bebcd31e..726392df5 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -150,7 +150,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi + acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/go.mod b/go.mod index e39d268da..bf5a18e1b 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 + github.com/aziontech/azionapi-go-sdk v0.142.0 github.com/baidubce/bce-sdk-go v0.9.223 github.com/cenkalti/backoff/v4 v4.3.0 github.com/civo/civogo v0.3.11 diff --git a/go.sum b/go.sum index 18a3f3616..7ee19cea2 100644 --- a/go.sum +++ b/go.sum @@ -166,6 +166,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjK github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/aziontech/azionapi-go-sdk v0.142.0 h1:1NOHXlC0/7VgbfbTIGVpsYn1THCugM4/SCOXVdUHQ+A= +github.com/aziontech/azionapi-go-sdk v0.142.0/go.mod h1:cA5DY/VP4X5Eu11LpQNzNn83ziKjja7QVMIl4J45feA= github.com/baidubce/bce-sdk-go v0.9.223 h1:vvDeIemf7ePPP59nLHCntQ/vS++ok2HKbRPgmz1VZKU= github.com/baidubce/bce-sdk-go v0.9.223/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -1068,6 +1070,7 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= diff --git a/providers/dns/azion/azion.go b/providers/dns/azion/azion.go new file mode 100644 index 000000000..5ea9a0c60 --- /dev/null +++ b/providers/dns/azion/azion.go @@ -0,0 +1,309 @@ +// Package azion implements a DNS provider for solving the DNS-01 challenge using Azion Edge DNS. +package azion + +import ( + "context" + "errors" + "fmt" + "net/http" + "sync" + "time" + + "github.com/aziontech/azionapi-go-sdk/idns" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" +) + +// Environment variables names. +const ( + envNamespace = "AZION_" + + EnvPersonalToken = envNamespace + "PERSONAL_TOKEN" + + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" + EnvTTL = envNamespace + "TTL" +) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + PersonalToken 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 *idns.APIClient + + recordIDs map[string]int32 + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for Azion. +// Credentials must be passed in the environment variable: AZION_PERSONAL_TOKEN. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvPersonalToken) + if err != nil { + return nil, fmt.Errorf("azion: %w", err) + } + + config := NewDefaultConfig() + config.PersonalToken = values[EnvPersonalToken] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Azion. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("azion: the configuration of the DNS provider is nil") + } + + if config.PersonalToken == "" { + return nil, errors.New("azion: missing credentials") + } + + clientConfig := idns.NewConfiguration() + clientConfig.AddDefaultHeader("Accept", "application/json; version=3") + clientConfig.UserAgent = "lego-dns/azion" + + if config.HTTPClient != nil { + clientConfig.HTTPClient = config.HTTPClient + } + + client := idns.NewAPIClient(clientConfig) + + return &DNSProvider{ + config: config, + client: client, + recordIDs: make(map[string]int32), + }, nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctxAuth := authContext(context.Background(), d.config.PersonalToken) + + zone, err := d.findZone(ctxAuth, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("azion: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := extractSubDomain(info, zone) + if err != nil { + return fmt.Errorf("azion: %w", err) + } + + // Check if a TXT record with the same name already exists + existingRecord, err := d.findExistingTXTRecord(ctxAuth, zone.GetId(), subDomain) + if err != nil { + return fmt.Errorf("azion: check existing records: %w", err) + } + + record := idns.NewRecordPostOrPut() + record.SetEntry(subDomain) + record.SetRecordType("TXT") + record.SetTtl(int32(d.config.TTL)) + + var resp *idns.PostOrPutRecordResponse + if existingRecord != nil { + // Update existing record by adding the new value to the existing ones + record.SetAnswersList(append(existingRecord.GetAnswersList(), info.Value)) + + // Use PUT to update the existing record + resp, _, err = d.client.RecordsAPI.PutZoneRecord(ctxAuth, zone.GetId(), existingRecord.GetRecordId()).RecordPostOrPut(*record).Execute() + if err != nil { + return fmt.Errorf("azion: update existing record: %w", err) + } + } else { + // Create a new record + record.SetAnswersList([]string{info.Value}) + + resp, _, err = d.client.RecordsAPI.PostZoneRecord(ctxAuth, zone.GetId()).RecordPostOrPut(*record).Execute() + if err != nil { + return fmt.Errorf("azion: create new zone record: %w", err) + } + } + + if resp == nil || resp.Results == nil { + return errors.New("azion: create zone record error") + } + + results := resp.GetResults() + d.recordIDsMu.Lock() + d.recordIDs[token] = results.GetId() + 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) + + ctxAuth := authContext(context.Background(), d.config.PersonalToken) + + zone, err := d.findZone(ctxAuth, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("azion: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := extractSubDomain(info, zone) + if err != nil { + return fmt.Errorf("azion: %w", err) + } + + defer func() { + // Remove the record ID from our map + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + }() + + // Find the existing TXT record + existingRecord, err := d.findExistingTXTRecord(ctxAuth, zone.GetId(), subDomain) + if err != nil { + return fmt.Errorf("azion: find existing record: %w", err) + } + + if existingRecord == nil { + return nil + } + + // Get current answers and remove the specific value + currentAnswers := existingRecord.GetAnswersList() + + var updatedAnswers []string + for _, answer := range currentAnswers { + if answer != info.Value { + updatedAnswers = append(updatedAnswers, answer) + } + } + + // If no answers remain, delete the entire record + if len(updatedAnswers) == 0 { + _, resp, errDelete := d.client.RecordsAPI.DeleteZoneRecord(ctxAuth, zone.GetId(), existingRecord.GetRecordId()).Execute() + if errDelete != nil { + // If a record doesn't exist (404), consider cleanup successful + if resp != nil && resp.StatusCode == http.StatusNotFound { + return nil + } + + return fmt.Errorf("azion: delete record: %w", errDelete) + } + + return nil + } + + // Update the record with remaining answers + record := idns.NewRecordPostOrPut() + record.SetEntry(subDomain) + record.SetRecordType("TXT") + record.SetAnswersList(updatedAnswers) + record.SetTtl(existingRecord.GetTtl()) + + _, _, err = d.client.RecordsAPI.PutZoneRecord(ctxAuth, zone.GetId(), existingRecord.GetRecordId()).RecordPostOrPut(*record).Execute() + if err != nil { + return fmt.Errorf("azion: update record: %w", err) + } + + return nil +} + +func (d *DNSProvider) findZone(ctx context.Context, fqdn string) (*idns.Zone, error) { + authZone, err := dns01.FindZoneByFqdn(fqdn) + if err != nil { + return nil, fmt.Errorf("could not find a zone for domain %q: %w", fqdn, err) + } + + resp, _, err := d.client.ZonesAPI.GetZones(ctx).Execute() + if err != nil { + return nil, fmt.Errorf("get zones: %w", err) + } + + if resp == nil { + return nil, errors.New("get zones: no results") + } + + targetZone := dns01.UnFqdn(authZone) + for _, zone := range resp.GetResults() { + if zone.GetName() == targetZone { + return &zone, nil + } + } + + return nil, fmt.Errorf("zone %q not found (fqdn: %q)", authZone, fqdn) +} + +// findExistingTXTRecord searches for an existing TXT record with the given name in the specified zone. +func (d *DNSProvider) findExistingTXTRecord(ctx context.Context, zoneID int32, recordName string) (*idns.RecordGet, error) { + resp, _, err := d.client.RecordsAPI.GetZoneRecords(ctx, zoneID).Execute() + if err != nil { + return nil, fmt.Errorf("get zone records: %w", err) + } + + if resp == nil { + return nil, errors.New("get zone records: no results") + } + + results, ok := resp.GetResultsOk() + if !ok || results == nil { + return nil, errors.New("get zone records: empty") + } + + // Search for existing TXT record with the same name + for _, record := range results.GetRecords() { + if record.GetRecordType() == "TXT" && record.GetEntry() == recordName { + return &record, nil + } + } + + // No existing record found + return nil, nil +} + +func authContext(ctx context.Context, key string) context.Context { + return context.WithValue(ctx, idns.ContextAPIKeys, map[string]idns.APIKey{ + "tokenAuth": { + Key: key, + Prefix: "Token", + }, + }) +} + +func extractSubDomain(info dns01.ChallengeInfo, zone *idns.Zone) (string, error) { + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone.GetName()) + if err != nil { + return "", err + } + + if subDomain != "" { + return subDomain, nil + } + + return "@", nil +} diff --git a/providers/dns/azion/azion.toml b/providers/dns/azion/azion.toml new file mode 100644 index 000000000..c2ba510f2 --- /dev/null +++ b/providers/dns/azion/azion.toml @@ -0,0 +1,23 @@ +Name = "Azion" +Description = '''''' +Code = "azion" +Since = "v4.24.0" +URL = "https://www.azion.com/en/products/edge-dns/" + +Example = ''' +AZION_PERSONAL_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ +lego --email you@example.com --dns azion -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + AZION_PERSONAL_TOKEN = "Your Azion personal token." + [Configuration.Additional] + AZION_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + AZION_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + AZION_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + AZION_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://api.azion.com/" + GoClient = "https://github.com/aziontech/azionapi-go-sdk" diff --git a/providers/dns/azion/azion_test.go b/providers/dns/azion/azion_test.go new file mode 100644 index 000000000..e96efb56e --- /dev/null +++ b/providers/dns/azion/azion_test.go @@ -0,0 +1,115 @@ +package azion + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvPersonalToken).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvPersonalToken: "token", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{ + EnvPersonalToken: "", + }, + expected: "azion: some credentials information are missing: AZION_PERSONAL_TOKEN", + }, + } + + 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 + token string + expected string + }{ + { + desc: "success", + token: "token", + }, + { + desc: "missing credentials", + expected: "azion: missing credentials", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.PersonalToken = test.token + + 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 ef9dcba28..5eee04faf 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -15,6 +15,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/auroradns" "github.com/go-acme/lego/v4/providers/dns/autodns" "github.com/go-acme/lego/v4/providers/dns/axelname" + "github.com/go-acme/lego/v4/providers/dns/azion" "github.com/go-acme/lego/v4/providers/dns/azure" "github.com/go-acme/lego/v4/providers/dns/azuredns" "github.com/go-acme/lego/v4/providers/dns/baiducloud" @@ -185,6 +186,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return autodns.NewDNSProvider() case "axelname": return axelname.NewDNSProvider() + case "azion": + return azion.NewDNSProvider() case "azure": return azure.NewDNSProvider() case "azuredns": From 9531f9e9c925508b3451a17efd4803d5312ff41e Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 1 Jul 2025 17:10:27 +0200 Subject: [PATCH 106/298] chore: update linter (#2552) --- .github/workflows/pr.yml | 2 +- .golangci.yml | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 49a684366..eddb5422f 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: stable - GOLANGCI_LINT_VERSION: v2.1.1 + GOLANGCI_LINT_VERSION: v2.2.1 HUGO_VERSION: 0.131.0 CGO_ENABLED: 0 LEGO_E2E_TESTS: CI diff --git a/.golangci.yml b/.golangci.yml index ef19b99bf..c7530d3b5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -10,6 +10,7 @@ formatters: linters: default: all disable: + - wsl # Deprecated - bodyclose - canonicalheader - contextcheck @@ -33,6 +34,7 @@ linters: - nilnil # not relevant - nlreturn # not relevant - noctx + - noinlineerr # too strict - nonamedreturns - paralleltest # not relevant - prealloc # too many false-positive @@ -44,7 +46,8 @@ linters: - usestdlibvars # false-positive https://github.com/sashamelentyev/usestdlibvars/issues/96 - varnamelen # not relevant - wrapcheck - - wsl # should be enabled it the future. + - wsl_v5 # should be enabled the future. + - embeddedstructfieldcheck # should be enabled the future. settings: depguard: From 08316e47a6f68ace8e9f3d93445945908d8e9a4c Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 2 Jul 2025 22:03:17 +0200 Subject: [PATCH 107/298] googledomains: provider deprecation (#2554) --- providers/dns/googledomains/googledomains.go | 95 ++------------- .../dns/googledomains/googledomains.toml | 6 +- .../dns/googledomains/googledomains_test.go | 111 ------------------ 3 files changed, 12 insertions(+), 200 deletions(-) delete mode 100644 providers/dns/googledomains/googledomains_test.go diff --git a/providers/dns/googledomains/googledomains.go b/providers/dns/googledomains/googledomains.go index d931b7539..b5eed0b03 100644 --- a/providers/dns/googledomains/googledomains.go +++ b/providers/dns/googledomains/googledomains.go @@ -2,17 +2,12 @@ package googledomains import ( - "context" "errors" - "fmt" "net/http" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/config/env" - "google.golang.org/api/acmedns/v1" - "google.golang.org/api/option" ) // Environment variables names. @@ -37,103 +32,29 @@ type Config struct { // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { - return &Config{ - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), - }, - } + return &Config{} } -type DNSProvider struct { - config *Config - acmedns *acmedns.Service -} +type DNSProvider struct{} // NewDNSProvider returns the Google Domains DNS provider with a default configuration. func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAccessToken) - if err != nil { - return nil, fmt.Errorf("googledomains: %w", err) - } - - config := NewDefaultConfig() - config.AccessToken = values[EnvAccessToken] - - return NewDNSProviderConfig(config) + return NewDNSProviderConfig(&Config{}) } // NewDNSProviderConfig returns the Google Domains DNS provider with the provided config. -func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("googledomains: the configuration of the DNS provider is nil") - } - - if config.AccessToken == "" { - return nil, errors.New("googledomains: access token is missing") - } - - service, err := acmedns.NewService(context.Background(), option.WithHTTPClient(config.HTTPClient)) - if err != nil { - return nil, fmt.Errorf("googledomains: error creating acme dns service: %w", err) - } - - return &DNSProvider{ - config: config, - acmedns: service, - }, nil +func NewDNSProviderConfig(_ *Config) (*DNSProvider, error) { + return nil, errors.New("googledomains: provider has shut down") } -func (d *DNSProvider) Present(domain, token, keyAuth string) error { - zone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain)) - if err != nil { - return fmt.Errorf("googledomains: could not find zone for domain %q: %w", domain, err) - } - - rotateReq := acmedns.RotateChallengesRequest{ - AccessToken: d.config.AccessToken, - RecordsToAdd: []*acmedns.AcmeTxtRecord{getAcmeTxtRecord(domain, keyAuth)}, - KeepExpiredRecords: false, - } - - call := d.acmedns.AcmeChallengeSets.RotateChallenges(zone, &rotateReq) - _, err = call.Do() - if err != nil { - return fmt.Errorf("googledomains: error adding challenge for domain %s: %w", domain, err) - } +func (d *DNSProvider) Present(_, _, _ string) error { return nil } -func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - zone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain)) - if err != nil { - return fmt.Errorf("googledomains: could not find zone for domain %q: %w", domain, err) - } - - rotateReq := acmedns.RotateChallengesRequest{ - AccessToken: d.config.AccessToken, - RecordsToRemove: []*acmedns.AcmeTxtRecord{getAcmeTxtRecord(domain, keyAuth)}, - KeepExpiredRecords: false, - } - - call := d.acmedns.AcmeChallengeSets.RotateChallenges(zone, &rotateReq) - _, err = call.Do() - if err != nil { - return fmt.Errorf("googledomains: error cleaning up challenge for domain %s: %w", domain, err) - } +func (d *DNSProvider) CleanUp(_, _, _ string) error { return nil } func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval -} - -func getAcmeTxtRecord(domain, keyAuth string) *acmedns.AcmeTxtRecord { - challengeInfo := dns01.GetChallengeInfo(domain, keyAuth) - - return &acmedns.AcmeTxtRecord{ - Fqdn: challengeInfo.EffectiveFQDN, - Digest: challengeInfo.Value, - } + return dns01.DefaultPropagationTimeout, dns01.DefaultPollingInterval } diff --git a/providers/dns/googledomains/googledomains.toml b/providers/dns/googledomains/googledomains.toml index 580d05dbf..1ac7e5e54 100644 --- a/providers/dns/googledomains/googledomains.toml +++ b/providers/dns/googledomains/googledomains.toml @@ -1,6 +1,8 @@ Name = "Google Domains" -Description = '''''' -URL = "https://domains.google" +Description = ''' +The Google Domains DNS provider has shut down. +''' +URL = "https://github.com/go-acme/lego/issues/2553" Code = "googledomains" Since = "v4.11.0" diff --git a/providers/dns/googledomains/googledomains_test.go b/providers/dns/googledomains/googledomains_test.go deleted file mode 100644 index 038fb5346..000000000 --- a/providers/dns/googledomains/googledomains_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package googledomains - -import ( - "testing" - - "github.com/go-acme/lego/v4/platform/tester" - "github.com/stretchr/testify/require" -) - -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest(EnvAccessToken). - WithDomain(envDomain). - WithLiveTestRequirements(EnvAccessToken, envDomain) - -func TestNewDNSProvider(t *testing.T) { - testCases := []struct { - desc string - envVars map[string]string - expected string - }{ - { - desc: "success", - envVars: map[string]string{ - EnvAccessToken: "abc", - }, - expected: "", - }, - { - desc: "missing credentials", - envVars: map[string]string{}, - expected: "googledomains: some credentials information are missing: GOOGLE_DOMAINS_ACCESS_TOKEN", - }, - } - - 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) - } else { - require.Error(t, err) - require.Contains(t, err.Error(), test.expected) - } - }) - } -} - -func TestNewDNSProviderConfig(t *testing.T) { - testCases := []struct { - desc string - accessToken string - expected string - }{ - { - desc: "success", - accessToken: "abc", - }, - { - desc: "missing credentials", - expected: "googledomains: access token is missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.AccessToken = test.accessToken - - p, err := NewDNSProviderConfig(config) - - if test.expected == "" { - 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) - - err = provider.CleanUp(envTest.GetDomain(), "", "123d==") - require.NoError(t, err) -} From b28d1ac67a52b0ea7ef643bc8a821e1b024184b5 Mon Sep 17 00:00:00 2001 From: Marcus Grando Date: Wed, 2 Jul 2025 17:44:09 -0300 Subject: [PATCH 108/298] azion: add pagination support (#2555) Co-authored-by: Fernandez Ludovic --- cmd/zz_gen_cmd_dnshelp.go | 1 + docs/content/dns/zz_gen_azion.md | 1 + providers/dns/azion/azion.go | 66 ++++++++++++++++++++------------ providers/dns/azion/azion.toml | 1 + 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 285e3cd80..91c69ab9d 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -360,6 +360,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "AZION_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "AZION_PAGE_SIZE": The page size for the API request (Default: 50)`) ew.writeln(` - "AZION_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "AZION_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln(` - "AZION_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) diff --git a/docs/content/dns/zz_gen_azion.md b/docs/content/dns/zz_gen_azion.md index be1f5ca17..af2a281b0 100644 --- a/docs/content/dns/zz_gen_azion.md +++ b/docs/content/dns/zz_gen_azion.md @@ -48,6 +48,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `AZION_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `AZION_PAGE_SIZE` | The page size for the API request (Default: 50) | | `AZION_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `AZION_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | | `AZION_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | diff --git a/providers/dns/azion/azion.go b/providers/dns/azion/azion.go index 5ea9a0c60..660c08073 100644 --- a/providers/dns/azion/azion.go +++ b/providers/dns/azion/azion.go @@ -19,18 +19,21 @@ const ( envNamespace = "AZION_" EnvPersonalToken = envNamespace + "PERSONAL_TOKEN" + EnvPageSize = envNamespace + "PAGE_SIZE" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" EnvTTL = envNamespace + "TTL" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) // Config is used to configure the creation of the DNSProvider. type Config struct { - PersonalToken string - PropagationTimeout time.Duration + PersonalToken string + PageSize int + PollingInterval time.Duration + PropagationTimeout time.Duration TTL int HTTPClient *http.Client } @@ -38,9 +41,10 @@ type Config struct { // 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), + PageSize: env.GetOrDefaultInt(EnvPageSize, 50), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), + TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), }, @@ -260,29 +264,41 @@ func (d *DNSProvider) findZone(ctx context.Context, fqdn string) (*idns.Zone, er } // findExistingTXTRecord searches for an existing TXT record with the given name in the specified zone. +// It handles pagination to search through all pages of results. func (d *DNSProvider) findExistingTXTRecord(ctx context.Context, zoneID int32, recordName string) (*idns.RecordGet, error) { - resp, _, err := d.client.RecordsAPI.GetZoneRecords(ctx, zoneID).Execute() - if err != nil { - return nil, fmt.Errorf("get zone records: %w", err) - } + var page int64 = 1 - if resp == nil { - return nil, errors.New("get zone records: no results") - } - - results, ok := resp.GetResultsOk() - if !ok || results == nil { - return nil, errors.New("get zone records: empty") - } - - // Search for existing TXT record with the same name - for _, record := range results.GetRecords() { - if record.GetRecordType() == "TXT" && record.GetEntry() == recordName { - return &record, nil + for { + resp, _, err := d.client.RecordsAPI.GetZoneRecords(ctx, zoneID).Page(page).PageSize(int64(d.config.PageSize)).Execute() + if err != nil { + return nil, fmt.Errorf("get zone records (page %d): %w", page, err) } + + if resp == nil { + return nil, errors.New("get zone records: no results") + } + + results, ok := resp.GetResultsOk() + if !ok || results == nil { + return nil, errors.New("get zone records: empty") + } + + // Search for existing TXT record with the same name in current page + for _, record := range results.GetRecords() { + if record.GetRecordType() == "TXT" && record.GetEntry() == recordName { + return &record, nil + } + } + + // Check if there are more pages to search + if page >= int64(resp.GetTotalPages()) { + break + } + + page++ } - // No existing record found + // No existing record found in any page return nil, nil } diff --git a/providers/dns/azion/azion.toml b/providers/dns/azion/azion.toml index c2ba510f2..eacfe74a6 100644 --- a/providers/dns/azion/azion.toml +++ b/providers/dns/azion/azion.toml @@ -13,6 +13,7 @@ lego --email you@example.com --dns azion -d '*.example.com' -d example.com run [Configuration.Credentials] AZION_PERSONAL_TOKEN = "Your Azion personal token." [Configuration.Additional] + AZION_PAGE_SIZE = "The page size for the API request (Default: 50)" AZION_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" AZION_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" AZION_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" From 94d871230d88abf54052c0a9c4c129d004b4e83e Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 3 Jul 2025 18:13:08 +0200 Subject: [PATCH 109/298] oraclecloud: replace oci-go-sdk by a modular fork (#2556) --- go.mod | 6 ++++-- go.sum | 13 ++++++++----- providers/dns/oraclecloud/configprovider.go | 2 +- providers/dns/oraclecloud/oraclecloud.go | 4 ++-- providers/dns/oraclecloud/oraclecloud_test.go | 2 +- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index bf5a18e1b..b6154e8c8 100644 --- a/go.mod +++ b/go.mod @@ -58,9 +58,10 @@ require ( github.com/nrdcg/mailinabox v0.2.0 github.com/nrdcg/namesilo v0.2.1 github.com/nrdcg/nodion v0.1.0 + github.com/nrdcg/oci-go-sdk/common/v1065 v1065.95.0 + github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.95.0 github.com/nrdcg/porkbun v0.4.0 github.com/nzdjb/go-metaname v1.0.0 - github.com/oracle/oci-go-sdk/v65 v65.87.0 github.com/ovh/go-ovh v1.7.0 github.com/pquerna/otp v1.4.0 github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2 @@ -178,7 +179,7 @@ require ( github.com/smartystreets/assertions v1.0.1 // indirect github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect - github.com/sony/gobreaker v0.5.0 // indirect + github.com/sony/gobreaker v1.0.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect @@ -188,6 +189,7 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect diff --git a/go.sum b/go.sum index 7ee19cea2..4665ef251 100644 --- a/go.sum +++ b/go.sum @@ -315,7 +315,6 @@ github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVr github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -654,6 +653,10 @@ github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg= github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.95.0 h1:COzvb58Oc7/ADt3699jzLMJd0HOW/v2tG9b+74IFhrA= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.95.0/go.mod h1:O6osg9dPzXq7H2ib/1qzimzG5oXSJFgccR7iawg7SwA= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.95.0 h1:KWGOjA6n7R2VSarQsQNakP8gGCZNFgnv3zxc4edqLjo= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.95.0/go.mod h1:iM3irKzFqd/xGZUWA+k93N3Sy+G+4KBqqQp2GBeBqeg= github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -686,8 +689,6 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= -github.com/oracle/oci-go-sdk/v65 v65.87.0 h1:CeVuK8t0dYODGT3P9IDhz4vyXF8poYE1ijoiO5vrKl0= -github.com/oracle/oci-go-sdk/v65 v65.87.0/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0= github.com/ovh/go-ovh v1.7.0 h1:V14nF7FwDjQrZt9g7jzcvAAQ3HN6DNShRFRMC3jLoPw= github.com/ovh/go-ovh v1.7.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -806,8 +807,8 @@ github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPc github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg= -github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ= +github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -895,6 +896,8 @@ github.com/yandex-cloud/go-genproto v0.0.0-20250319153614-fb9d3e5eb01a/go.mod h1 github.com/yandex-cloud/go-sdk v0.0.0-20250320143332-9cbcfc5de4ae h1:x+uGuST05LVlgCxF5TsP8kQCCTW7uIeAQJ1dKtSmWqE= github.com/yandex-cloud/go-sdk v0.0.0-20250320143332-9cbcfc5de4ae/go.mod h1:V71iJlJnS/NtNNdg/B7SwccBS19aXxwY3fv/wut9D74= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/providers/dns/oraclecloud/configprovider.go b/providers/dns/oraclecloud/configprovider.go index 43d0cecc3..7a51811a6 100644 --- a/providers/dns/oraclecloud/configprovider.go +++ b/providers/dns/oraclecloud/configprovider.go @@ -8,7 +8,7 @@ import ( "os" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/oracle/oci-go-sdk/v65/common" + "github.com/nrdcg/oci-go-sdk/common/v1065" ) type configProvider struct { diff --git a/providers/dns/oraclecloud/oraclecloud.go b/providers/dns/oraclecloud/oraclecloud.go index bb3a03b67..2fb392e3e 100644 --- a/providers/dns/oraclecloud/oraclecloud.go +++ b/providers/dns/oraclecloud/oraclecloud.go @@ -11,8 +11,8 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/oracle/oci-go-sdk/v65/common" - "github.com/oracle/oci-go-sdk/v65/dns" + "github.com/nrdcg/oci-go-sdk/common/v1065" + "github.com/nrdcg/oci-go-sdk/dns/v1065" ) // Environment variables names. diff --git a/providers/dns/oraclecloud/oraclecloud_test.go b/providers/dns/oraclecloud/oraclecloud_test.go index 34a0ed6da..5d35c01a8 100644 --- a/providers/dns/oraclecloud/oraclecloud_test.go +++ b/providers/dns/oraclecloud/oraclecloud_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/go-acme/lego/v4/platform/tester" - "github.com/oracle/oci-go-sdk/v65/common" + "github.com/nrdcg/oci-go-sdk/common/v1065" "github.com/stretchr/testify/require" ) From 1fecd31d3d5a8adede4053ce6a0d3a4bfc38d9f5 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 3 Jul 2025 18:17:33 +0200 Subject: [PATCH 110/298] alidns: migrate to SDK v2 (#2558) --- cmd/zz_gen_cmd_dnshelp.go | 2 +- docs/content/dns/zz_gen_alidns.md | 4 +- docs/content/dns/zz_gen_googledomains.md | 4 +- go.mod | 15 +++- go.sum | 92 +++++++++++++++++--- providers/dns/alidns/alidns.go | 106 +++++++++++++---------- providers/dns/alidns/alidns.toml | 5 +- 7 files changed, 159 insertions(+), 69 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 91c69ab9d..1a057f3ae 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -228,7 +228,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Credentials:`) ew.writeln(` - "ALICLOUD_ACCESS_KEY": Access key ID`) - ew.writeln(` - "ALICLOUD_RAM_ROLE": Your instance RAM role (https://www.alibabacloud.com/help/doc-detail/54579.htm)`) + ew.writeln(` - "ALICLOUD_RAM_ROLE": Your instance RAM role (https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance)`) ew.writeln(` - "ALICLOUD_SECRET_KEY": Access Key secret`) ew.writeln(` - "ALICLOUD_SECURITY_TOKEN": STS Security Token (optional)`) ew.writeln() diff --git a/docs/content/dns/zz_gen_alidns.md b/docs/content/dns/zz_gen_alidns.md index bb55ba4fc..7a7a36e8a 100644 --- a/docs/content/dns/zz_gen_alidns.md +++ b/docs/content/dns/zz_gen_alidns.md @@ -45,7 +45,7 @@ lego --email you@example.com --dns alidns - -d '*.example.com' -d example.com ru | Environment Variable Name | Description | |-----------------------|-------------| | `ALICLOUD_ACCESS_KEY` | Access key ID | -| `ALICLOUD_RAM_ROLE` | Your instance RAM role (https://www.alibabacloud.com/help/doc-detail/54579.htm) | +| `ALICLOUD_RAM_ROLE` | Your instance RAM role (https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance) | | `ALICLOUD_SECRET_KEY` | Access Key secret | | `ALICLOUD_SECURITY_TOKEN` | STS Security Token (optional) | @@ -71,7 +71,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). ## More information - [API documentation](https://www.alibabacloud.com/help/en/alibaba-cloud-dns/latest/api-alidns-2015-01-09-dir-parsing-records) -- [Go client](https://github.com/aliyun/alibaba-cloud-sdk-go) +- [Go client](https://github.com/alibabacloud-go/alidns-20150109) diff --git a/docs/content/dns/zz_gen_googledomains.md b/docs/content/dns/zz_gen_googledomains.md index b1eba25f1..c6f6d0577 100644 --- a/docs/content/dns/zz_gen_googledomains.md +++ b/docs/content/dns/zz_gen_googledomains.md @@ -6,15 +6,15 @@ slug: googledomains dnsprovider: since: "v4.11.0" code: "googledomains" - url: "https://domains.google" + url: "https://github.com/go-acme/lego/issues/2553" --- +The Google Domains DNS provider has shut down. -Configuration for [Google Domains](https://domains.google). diff --git a/go.mod b/go.mod index b6154e8c8..4d78443e9 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,9 @@ require ( github.com/BurntSushi/toml v1.5.0 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 - github.com/aliyun/alibaba-cloud-sdk-go v1.63.100 + github.com/alibabacloud-go/alidns-20150109/v4 v4.5.10 + github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.11 + github.com/aliyun/credentials-go v1.4.5 github.com/aws/aws-sdk-go-v2 v1.36.3 github.com/aws/aws-sdk-go-v2/config v1.29.9 github.com/aws/aws-sdk-go-v2/credentials v1.17.62 @@ -106,6 +108,13 @@ require ( github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect + github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect + github.com/alibabacloud-go/debug v1.0.1 // indirect + github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect + github.com/alibabacloud-go/openapi-util v0.1.1 // indirect + github.com/alibabacloud-go/tea v1.2.2 // indirect + github.com/alibabacloud-go/tea-utils/v2 v2.0.6 // indirect + github.com/alibabacloud-go/tea-xml v1.1.3 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect @@ -121,6 +130,7 @@ require ( github.com/aws/smithy-go v1.22.2 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect + github.com/clbanning/mxj/v2 v2.5.5 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dimchansky/utfbom v1.1.1 // indirect @@ -149,7 +159,6 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect @@ -162,7 +171,6 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/peterhellberg/link v1.2.0 // indirect @@ -176,7 +184,6 @@ require ( github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/smartystreets/assertions v1.0.1 // indirect github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect github.com/sony/gobreaker v1.0.0 // indirect diff --git a/go.sum b/go.sum index 4665ef251..e30416eb7 100644 --- a/go.sum +++ b/go.sum @@ -113,8 +113,53 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/aliyun/alibaba-cloud-sdk-go v1.63.100 h1:yUkCbrSM1cWtgBfRVKMQtdt22KhDvKY7g4V+92eG9wA= -github.com/aliyun/alibaba-cloud-sdk-go v1.63.100/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= +github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA= +github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g= +github.com/alibabacloud-go/alidns-20150109/v4 v4.5.10 h1:lEYfSDh8puQigWN2pyOxE1gaI6o2bxFhJSSeX+ZJSf4= +github.com/alibabacloud-go/alidns-20150109/v4 v4.5.10/go.mod h1:EdHRU3Y2j8OXc2ljp00A0zMLQ8sORHxI4yPnODNztRc= +github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY= +github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI= +github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE= +github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= +github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= +github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.11 h1:GkVQ9AphMCmgAYakcTpH/OuFz0mQUypO/JiOvo0wgVA= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.11/go.mod h1:wHxkgZT1ClZdcwEVP/pDgYK/9HucsnCfMipmJgCz4xY= +github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= +github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= +github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= +github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= +github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= +github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg= +github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q= +github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= +github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= +github.com/alibabacloud-go/openapi-util v0.1.1 h1:ujGErJjG8ncRW6XtBBMphzHTvCxn4DjrVw4m04HsS28= +github.com/alibabacloud-go/openapi-util v0.1.1/go.mod h1:/UehBSE2cf1gYT43GV4E+RxTdLRzURImCYY0aRmlXpw= +github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg= +github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU= +github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= +github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= +github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= +github.com/alibabacloud-go/tea-utils/v2 v2.0.6 h1:ZkmUlhlQbaDC+Eba/GARMPy6hKdCLiSke5RsN5LcyQ0= +github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= +github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0= +github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= +github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= +github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= +github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= +github.com/aliyun/credentials-go v1.4.5 h1:O76WYKgdy1oQYYiJkERjlA2dxGuvLRrzuO2ScrtGWSk= +github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -201,6 +246,8 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp github.com/civo/civogo v0.3.11 h1:mON/fyrV946Sbk6paRtOSGsN+asCgCmHCgArf5xmGxM= github.com/civo/civogo v0.3.11/go.mod h1:7+GeeFwc4AYTULaEshpT2vIcl3Qq8HPoxA17viX3l6g= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= +github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.115.0 h1:84/dxeeXweCc0PN5Cto44iTA8AkG1fyT11yPO5ZB7sM= github.com/cloudflare/cloudflare-go v0.115.0/go.mod h1:Ds6urDwn/TF2uIU24mu7H91xkKP8gSAHxQ44DSZgVmU= @@ -320,7 +367,6 @@ github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeH github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -418,6 +464,7 @@ github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfg github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 h1:sH7xkTfYzxIEgzq1tDHIMKRh1vThOEOGNsettdEeLbE= github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56/go.mod h1:VSalo4adEk+3sNkmVJLnhHoOyOYYS8sTWLG4mv5BKto= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -500,9 +547,7 @@ github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/U github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 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= @@ -686,8 +731,6 @@ github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= -github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= github.com/ovh/go-ovh v1.7.0 h1:V14nF7FwDjQrZt9g7jzcvAAQ3HN6DNShRFRMC3jLoPw= github.com/ovh/go-ovh v1.7.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= @@ -794,8 +837,8 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= -github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0= +github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -835,6 +878,7 @@ github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1Sd github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= @@ -859,16 +903,13 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1128 h1:NGn github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1128/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128 h1:mrJ5Fbkd7sZIJ5F6oRfh5zebPQaudPH9Y0+GUmFytYU= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128/go.mod h1:zbsYIBT+VTX4z4ocjTAdLBIWyNYj3z0BRqd0iPdnjsk= +github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/transip/gotransip/v6 v6.26.0 h1:Aejfvh8rSp8Mj2GX/RpdBjMCv+Iy/DmgfNgczPDP550= github.com/transip/gotransip/v6 v6.26.0/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= -github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= -github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec h1:2s/ghQ8wKE+UzD/hf3P4Gd1j0JI9ncbxv+nsypPoUYI= github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7Qs3ku1ckpqed8tCRSqTlp8NAeZfAVpfx4OzXMss= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= @@ -900,6 +941,7 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zU github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -957,7 +999,9 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -971,7 +1015,11 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1064,8 +1112,13 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1133,6 +1186,7 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1166,7 +1220,11 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1174,7 +1232,11 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1191,6 +1253,7 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= @@ -1242,6 +1305,7 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -1377,6 +1441,7 @@ gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0= gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1399,7 +1464,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/providers/dns/alidns/alidns.go b/providers/dns/alidns/alidns.go index 9129eef09..7aa4028b8 100644 --- a/providers/dns/alidns/alidns.go +++ b/providers/dns/alidns/alidns.go @@ -6,14 +6,13 @@ import ( "fmt" "time" - "github.com/aliyun/alibaba-cloud-sdk-go/sdk" - "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth" - "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials" - "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" - "github.com/aliyun/alibaba-cloud-sdk-go/services/alidns" + alidns "github.com/alibabacloud-go/alidns-20150109/v4/client" + openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" + "github.com/aliyun/credentials-go/credentials" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" "golang.org/x/net/idna" ) @@ -102,23 +101,42 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { config.RegionID = defaultRegionID } - var credential auth.Credential + cfg := new(openapi.Config). + SetRegionId(config.RegionID). + SetReadTimeout(int(config.HTTPTimeout.Milliseconds())) + switch { case config.RAMRole != "": - credential = credentials.NewEcsRamRoleCredential(config.RAMRole) + // https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance + credentialsCfg := new(credentials.Config). + SetType("ecs_ram_role"). + SetRoleName(config.RAMRole) + + credentialClient, err := credentials.NewCredential(credentialsCfg) + if err != nil { + return nil, fmt.Errorf("alicloud: new credential: %w", err) + } + + cfg = cfg.SetCredential(credentialClient) + case config.APIKey != "" && config.SecretKey != "" && config.SecurityToken != "": - credential = credentials.NewStsTokenCredential(config.APIKey, config.SecretKey, config.SecurityToken) + cfg = cfg. + SetAccessKeyId(config.APIKey). + SetAccessKeySecret(config.SecretKey). + SetSecurityToken(config.SecurityToken) + case config.APIKey != "" && config.SecretKey != "": - credential = credentials.NewAccessKeyCredential(config.APIKey, config.SecretKey) + cfg = cfg. + SetAccessKeyId(config.APIKey). + SetAccessKeySecret(config.SecretKey) + default: return nil, errors.New("alicloud: ram role or credentials missing") } - conf := sdk.NewConfig().WithTimeout(config.HTTPTimeout) - - client, err := alidns.NewClientWithOptions(config.RegionID, conf, credential) + client, err := alidns.NewClient(cfg) if err != nil { - return nil, fmt.Errorf("alicloud: credentials failed: %w", err) + return nil, fmt.Errorf("alicloud: new client: %w", err) } return &DNSProvider{config: config, client: client}, nil @@ -139,12 +157,12 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("alicloud: %w", err) } - recordAttributes, err := d.newTxtRecord(zoneName, info.EffectiveFQDN, info.Value) + recordRequest, err := d.newTxtRecord(zoneName, info.EffectiveFQDN, info.Value) if err != nil { return err } - _, err = d.client.AddDomainRecord(recordAttributes) + _, err = d.client.AddDomainRecord(recordRequest) if err != nil { return fmt.Errorf("alicloud: API call failed: %w", err) } @@ -166,8 +184,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } for _, rec := range records { - request := alidns.CreateDeleteDomainRecordRequest() - request.RecordId = rec.RecordId + request := &alidns.DeleteDomainRecordRequest{ + RecordId: rec.RecordId, + } + _, err = d.client.DeleteDomainRecord(request) if err != nil { return fmt.Errorf("alicloud: %w", err) @@ -177,22 +197,23 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } func (d *DNSProvider) getHostedZone(domain string) (string, error) { - request := alidns.CreateDescribeDomainsRequest() + request := new(alidns.DescribeDomainsRequest) - var domains []alidns.DomainInDescribeDomains - startPage := 1 + var domains []*alidns.DescribeDomainsResponseBodyDomainsDomain + + var startPage int64 = 1 for { - request.PageNumber = requests.NewInteger(startPage) + request.SetPageNumber(startPage) response, err := d.client.DescribeDomains(request) if err != nil { return "", fmt.Errorf("API call failed: %w", err) } - domains = append(domains, response.Domains.Domain...) + domains = append(domains, response.Body.Domains.Domain...) - if response.PageNumber*response.PageSize >= response.TotalCount { + if ptr.Deref(response.Body.PageNumber)*ptr.Deref(response.Body.PageSize) >= ptr.Deref(response.Body.TotalCount) { break } @@ -204,48 +225,45 @@ func (d *DNSProvider) getHostedZone(domain string) (string, error) { return "", fmt.Errorf("could not find zone: %w", err) } - var hostedZone alidns.DomainInDescribeDomains + var hostedZone *alidns.DescribeDomainsResponseBodyDomainsDomain for _, zone := range domains { - if zone.DomainName == dns01.UnFqdn(authZone) || zone.PunyCode == dns01.UnFqdn(authZone) { + if ptr.Deref(zone.DomainName) == dns01.UnFqdn(authZone) || ptr.Deref(zone.PunyCode) == dns01.UnFqdn(authZone) { hostedZone = zone } } - if hostedZone.DomainId == "" { + if hostedZone == nil || ptr.Deref(hostedZone.DomainId) == "" { return "", fmt.Errorf("zone %s not found in AliDNS for domain %s", authZone, domain) } - return hostedZone.DomainName, nil + return ptr.Deref(hostedZone.DomainName), nil } func (d *DNSProvider) newTxtRecord(zone, fqdn, value string) (*alidns.AddDomainRecordRequest, error) { - request := alidns.CreateAddDomainRecordRequest() - request.Type = "TXT" - request.DomainName = zone - - var err error - request.RR, err = extractRecordName(fqdn, zone) + rr, err := extractRecordName(fqdn, zone) if err != nil { return nil, err } - request.Value = value - request.TTL = requests.NewInteger(d.config.TTL) - - return request, nil + return new(alidns.AddDomainRecordRequest). + SetType("TXT"). + SetDomainName(zone). + SetRR(rr). + SetValue(value). + SetTTL(int64(d.config.TTL)), nil } -func (d *DNSProvider) findTxtRecords(fqdn string) ([]alidns.Record, error) { +func (d *DNSProvider) findTxtRecords(fqdn string) ([]*alidns.DescribeDomainRecordsResponseBodyDomainRecordsRecord, error) { zoneName, err := d.getHostedZone(fqdn) if err != nil { return nil, err } - request := alidns.CreateDescribeDomainRecordsRequest() - request.DomainName = zoneName - request.PageSize = requests.NewInteger(500) + request := new(alidns.DescribeDomainRecordsRequest). + SetDomainName(zoneName). + SetPageSize(500) - var records []alidns.Record + var records []*alidns.DescribeDomainRecordsResponseBodyDomainRecordsRecord result, err := d.client.DescribeDomainRecords(request) if err != nil { @@ -257,8 +275,8 @@ func (d *DNSProvider) findTxtRecords(fqdn string) ([]alidns.Record, error) { return nil, err } - for _, record := range result.DomainRecords.Record { - if record.RR == recordName && record.Type == "TXT" { + for _, record := range result.Body.DomainRecords.Record { + if ptr.Deref(record.RR) == recordName && ptr.Deref(record.Type) == "TXT" { records = append(records, record) } } diff --git a/providers/dns/alidns/alidns.toml b/providers/dns/alidns/alidns.toml index 7098213e3..49a9aeeab 100644 --- a/providers/dns/alidns/alidns.toml +++ b/providers/dns/alidns/alidns.toml @@ -18,7 +18,7 @@ lego --email you@example.com --dns alidns - -d '*.example.com' -d example.com ru [Configuration] [Configuration.Credentials] - ALICLOUD_RAM_ROLE = "Your instance RAM role (https://www.alibabacloud.com/help/doc-detail/54579.htm)" + ALICLOUD_RAM_ROLE = "Your instance RAM role (https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance)" ALICLOUD_ACCESS_KEY = "Access key ID" ALICLOUD_SECRET_KEY = "Access Key secret" ALICLOUD_SECURITY_TOKEN = "STS Security Token (optional)" @@ -30,4 +30,5 @@ lego --email you@example.com --dns alidns - -d '*.example.com' -d example.com ru [Links] API = "https://www.alibabacloud.com/help/en/alibaba-cloud-dns/latest/api-alidns-2015-01-09-dir-parsing-records" - GoClient = "https://github.com/aliyun/alibaba-cloud-sdk-go" + GoClient = "https://github.com/alibabacloud-go/alidns-20150109" + GoClient2 = "https://github.com/aliyun/alibabacloud-go-sdk/tree/HEAD/alidns-20150109" From 990f9ac60126cd941d8e7370374d584b92a3389e Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 5 Jul 2025 12:27:48 +0200 Subject: [PATCH 111/298] mijnhost: improve record filter (#2562) --- providers/dns/mijnhost/mijnhost.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/providers/dns/mijnhost/mijnhost.go b/providers/dns/mijnhost/mijnhost.go index 32aadfb2d..bbdd810b9 100644 --- a/providers/dns/mijnhost/mijnhost.go +++ b/providers/dns/mijnhost/mijnhost.go @@ -28,6 +28,8 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) +const txtType = "TXT" + var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. @@ -128,7 +130,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { } record := internal.Record{ - Type: "TXT", + Type: txtType, Name: subDomain, Value: info.Value, TTL: d.config.TTL, @@ -137,7 +139,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // mijn.host doesn't support multiple values for a domain, // so we removed existing record for the subdomain. cleanedRecords := filterRecords(records, func(record internal.Record) bool { - return record.Name == subDomain || record.Name == dns01.UnFqdn(info.EffectiveFQDN) + return record.Type == txtType && (record.Name == subDomain || record.Name == dns01.UnFqdn(info.EffectiveFQDN)) }) cleanedRecords = append(cleanedRecords, record) @@ -170,7 +172,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } cleanedRecords := filterRecords(records, func(record internal.Record) bool { - return record.Value == info.Value + return record.Type == txtType && record.Value == info.Value }) err = d.client.UpdateRecords(context.Background(), dom.Domain, cleanedRecords) From 17c65de6e77a04391c8b3024e8c1780b58443f4d Mon Sep 17 00:00:00 2001 From: Marcus Grando Date: Mon, 7 Jul 2025 14:48:57 -0300 Subject: [PATCH 112/298] azion: improve zone lookup (#2564) Co-authored-by: Fernandez Ludovic --- providers/dns/azion/azion.go | 25 ++-- providers/dns/azion/azion_test.go | 134 ++++++++++++++++++ providers/dns/azion/fixtures/zones.json | 19 +++ providers/dns/azion/fixtures/zones_empty.json | 10 ++ 4 files changed, 175 insertions(+), 13 deletions(-) create mode 100644 providers/dns/azion/fixtures/zones.json create mode 100644 providers/dns/azion/fixtures/zones_empty.json diff --git a/providers/dns/azion/azion.go b/providers/dns/azion/azion.go index 660c08073..bc25586d0 100644 --- a/providers/dns/azion/azion.go +++ b/providers/dns/azion/azion.go @@ -12,6 +12,7 @@ import ( "github.com/aziontech/azionapi-go-sdk/idns" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/miekg/dns" ) // Environment variables names. @@ -182,13 +183,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } defer func() { - // Remove the record ID from our map + // Cleans the record ID. d.recordIDsMu.Lock() delete(d.recordIDs, token) d.recordIDsMu.Unlock() }() - // Find the existing TXT record existingRecord, err := d.findExistingTXTRecord(ctxAuth, zone.GetId(), subDomain) if err != nil { return fmt.Errorf("azion: find existing record: %w", err) @@ -198,7 +198,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } - // Get current answers and remove the specific value currentAnswers := existingRecord.GetAnswersList() var updatedAnswers []string @@ -239,11 +238,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } func (d *DNSProvider) findZone(ctx context.Context, fqdn string) (*idns.Zone, error) { - authZone, err := dns01.FindZoneByFqdn(fqdn) - if err != nil { - return nil, fmt.Errorf("could not find a zone for domain %q: %w", fqdn, err) - } - resp, _, err := d.client.ZonesAPI.GetZones(ctx).Execute() if err != nil { return nil, fmt.Errorf("get zones: %w", err) @@ -253,14 +247,19 @@ func (d *DNSProvider) findZone(ctx context.Context, fqdn string) (*idns.Zone, er return nil, errors.New("get zones: no results") } - targetZone := dns01.UnFqdn(authZone) - for _, zone := range resp.GetResults() { - if zone.GetName() == targetZone { - return &zone, nil + labelIndexes := dns.Split(fqdn) + + for _, index := range labelIndexes { + domain := dns01.UnFqdn(fqdn[index:]) + + for _, zone := range resp.GetResults() { + if zone.GetDomain() == domain { + return &zone, nil + } } } - return nil, fmt.Errorf("zone %q not found (fqdn: %q)", authZone, fqdn) + return nil, fmt.Errorf("zone not found (fqdn: %q)", fqdn) } // findExistingTXTRecord searches for an existing TXT record with the given name in the specified zone. diff --git a/providers/dns/azion/azion_test.go b/providers/dns/azion/azion_test.go index e96efb56e..de25e7c69 100644 --- a/providers/dns/azion/azion_test.go +++ b/providers/dns/azion/azion_test.go @@ -1,9 +1,17 @@ package azion import ( + "context" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" "testing" + "github.com/aziontech/azionapi-go-sdk/idns" "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -113,3 +121,129 @@ func TestLiveCleanUp(t *testing.T) { err = provider.CleanUp(envTest.GetDomain(), "", "123d==") require.NoError(t, err) } + +func TestDNSProvider_findZone(t *testing.T) { + provider, mux := setupTest(t) + mux.HandleFunc("GET /intelligent_dns", writeFixtureHandler("zones.json")) + + testCases := []struct { + desc string + fqdn string + expected *idns.Zone + }{ + { + desc: "apex", + fqdn: "example.com.", + expected: &idns.Zone{ + Id: idns.PtrInt32(1), + Domain: idns.PtrString("example.com"), + }, + }, + { + desc: "sub domain", + fqdn: "sub.example.com.", + expected: &idns.Zone{ + Id: idns.PtrInt32(2), + Domain: idns.PtrString("sub.example.com"), + }, + }, + { + desc: "long sub domain", + fqdn: "_acme-challenge.api.sub.example.com.", + expected: &idns.Zone{ + Id: idns.PtrInt32(2), + Domain: idns.PtrString("sub.example.com"), + }, + }, + { + desc: "long sub domain, apex", + fqdn: "_acme-challenge.test.example.com.", + expected: &idns.Zone{ + Id: idns.PtrInt32(1), + Domain: idns.PtrString("example.com"), + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + zone, err := provider.findZone(context.Background(), test.fqdn) + require.NoError(t, err) + + assert.Equal(t, test.expected, zone) + }) + } +} + +func TestDNSProvider_findZone_error(t *testing.T) { + testCases := []struct { + desc string + fqdn string + response string + expected string + }{ + { + desc: "no parent zone found", + fqdn: "_acme-challenge.example.org.", + response: "zones.json", + expected: `zone not found (fqdn: "_acme-challenge.example.org.")`, + }, + { + desc: "empty zones list", + fqdn: "example.com.", + response: "zones_empty.json", + expected: `zone not found (fqdn: "example.com.")`, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + provider, mux := setupTest(t) + mux.HandleFunc("GET /intelligent_dns", writeFixtureHandler(test.response)) + + zone, err := provider.findZone(context.Background(), test.fqdn) + require.EqualError(t, err, test.expected) + + assert.Nil(t, zone) + }) + } +} + +func setupTest(t *testing.T) (*DNSProvider, *http.ServeMux) { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + + config := NewDefaultConfig() + config.PersonalToken = "secret" + + provider, err := NewDNSProviderConfig(config) + require.NoError(t, err) + + clientConfig := provider.client.GetConfig() + clientConfig.HTTPClient = server.Client() + clientConfig.Servers = idns.ServerConfigurations{ + { + URL: server.URL, + Description: "Production", + }, + } + + return provider, mux +} + +func writeFixtureHandler(filename string) http.HandlerFunc { + return func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("Content-Type", "application/json") + + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + defer func() { _ = file.Close() }() + + _, _ = io.Copy(rw, file) + } +} diff --git a/providers/dns/azion/fixtures/zones.json b/providers/dns/azion/fixtures/zones.json new file mode 100644 index 000000000..7dccedf1a --- /dev/null +++ b/providers/dns/azion/fixtures/zones.json @@ -0,0 +1,19 @@ +{ + "count": 2, + "links": { + "previous": null, + "next": null + }, + "total_pages": 1, + "results": [ + { + "id": 1, + "domain": "example.com" + }, + { + "id": 2, + "domain": "sub.example.com" + } + ], + "schema_version": 3 +} diff --git a/providers/dns/azion/fixtures/zones_empty.json b/providers/dns/azion/fixtures/zones_empty.json new file mode 100644 index 000000000..540063837 --- /dev/null +++ b/providers/dns/azion/fixtures/zones_empty.json @@ -0,0 +1,10 @@ +{ + "count": 0, + "links": { + "previous": null, + "next": null + }, + "total_pages": 0, + "results": null, + "schema_version": 3 +} From 14d66f0c2032617689d7c1e044e0424e9fdbe7ce Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 1 Jul 2025 17:36:51 +0200 Subject: [PATCH 113/298] Prepare release v4.24.0 --- CHANGELOG.md | 23 +++++++++++++++++++ acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4848675c1..efccdba6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## [v4.24.0](https://github.com/go-acme/lego/releases/tag/v4.24.0) (2025-07-07) + +### Added + +- **[dnsprovider]** Add DNS provider for Azion +- **[dnsprovider]** Add DNS provider for DynDnsFree.de +- **[dnsprovider]** Add DNS provider for ConoHa v3 +- **[dnsprovider]** Add DNS provider for RU Center +- **[dnsprovider]** gcloud: add service account impersonation + +### Changed + +- **[dnsprovider]** pdns: improve error messages +- **[dnsprovider]** cloudflare: add quotation marks to TXT record +- **[dnsprovider]** googledomains: provider deprecation +- **[dnsprovider]** mijnhost: improve record filter + +### Fixed + +- **[dnsprovider]** exoscale: fix find record +- **[dnsprovider]** nicmanager: fix mode env var name and value +- **[lib,cli]** Check order identifiers difference between client and server + ## [v4.23.1](https://github.com/go-acme/lego/releases/tag/v4.23.1) (2025-04-16) Due to an error related to Snapcraft, some artifacts of the v4.23.0 release have not been published. diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index a95836245..da7930e2d 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.23.1" + ourUserAgent = "xenolf-acme/4.24.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index e5a0919b8..435d90e89 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.23.1+dev-detach" +const defaultVersion = "v4.24.0+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 2151ea3e0..e01afb07a 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.23.1" + ourUserAgent = "goacme-lego/4.24.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From d1c79386e1811ffbdd059727438494d7b9e6f9e5 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 7 Jul 2025 19:51:25 +0200 Subject: [PATCH 114/298] Detach v4.24.0 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index da7930e2d..803bbf018 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 435d90e89..6e1ca64c8 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.24.0+dev-release" +const defaultVersion = "v4.24.0+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index e01afb07a..fed65ee88 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From d9bba80a19a6e957781d2fde7e93f4a1a789b5b9 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 8 Jul 2025 17:23:13 +0200 Subject: [PATCH 115/298] ionos: increase default propagation timeout (#2569) --- cmd/zz_gen_cmd_dnshelp.go | 2 +- docs/content/dns/zz_gen_ionos.md | 2 +- providers/dns/ionos/ionos.go | 2 +- providers/dns/ionos/ionos.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 1a057f3ae..8676edad5 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -1807,7 +1807,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "IONOS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "IONOS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "IONOS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "IONOS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 900)`) ew.writeln(` - "IONOS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() diff --git a/docs/content/dns/zz_gen_ionos.md b/docs/content/dns/zz_gen_ionos.md index c0cb859b7..60a2ede03 100644 --- a/docs/content/dns/zz_gen_ionos.md +++ b/docs/content/dns/zz_gen_ionos.md @@ -49,7 +49,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). |--------------------------------|-------------| | `IONOS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `IONOS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `IONOS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `IONOS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 900) | | `IONOS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/providers/dns/ionos/ionos.go b/providers/dns/ionos/ionos.go index d12fd7f09..394def027 100644 --- a/providers/dns/ionos/ionos.go +++ b/providers/dns/ionos/ionos.go @@ -45,7 +45,7 @@ type Config struct { func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, minTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 15*time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), diff --git a/providers/dns/ionos/ionos.toml b/providers/dns/ionos/ionos.toml index 3c5239594..0c905273f 100644 --- a/providers/dns/ionos/ionos.toml +++ b/providers/dns/ionos/ionos.toml @@ -14,7 +14,7 @@ lego --email you@example.com --dns ionos -d '*.example.com' -d example.com run IONOS_API_KEY = "API key `.` https://developer.hosting.ionos.com/docs/getstarted" [Configuration.Additional] IONOS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - IONOS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + IONOS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 900)" IONOS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" IONOS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" From 40baed291cb36bc1f13d64fbc56d618c90872fb3 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 8 Jul 2025 17:23:31 +0200 Subject: [PATCH 116/298] feat: add option to disable common name in CSR (#2570) --- certificate/certificates.go | 3 ++- cmd/flags.go | 6 ++++++ cmd/setup.go | 1 + lego/client.go | 10 +++++++++- lego/client_config.go | 1 + 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/certificate/certificates.go b/certificate/certificates.go index 44931dd50..e5830722d 100644 --- a/certificate/certificates.go +++ b/certificate/certificates.go @@ -125,6 +125,7 @@ type CertifierOptions struct { KeyType certcrypto.KeyType Timeout time.Duration OverallRequestLimit int + DisableCommonName bool } // Certifier A service to obtain/renew/revoke certificates. @@ -301,7 +302,7 @@ func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, requ } commonName := "" - if len(domains[0]) <= 64 { + if len(domains[0]) <= 64 && !c.options.DisableCommonName { commonName = domains[0] } diff --git a/cmd/flags.go b/cmd/flags.go index ebf051ae6..066517229 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -16,6 +16,7 @@ const ( flgServer = "server" flgAcceptTOS = "accept-tos" flgEmail = "email" + flgDisableCommonName = "disable-cn" flgCSR = "csr" flgEAB = "eab" flgKID = "kid" @@ -88,6 +89,11 @@ func CreateFlags(defaultPath string) []cli.Flag { EnvVars: []string{envEmail}, Usage: "Email used for registration and recovery contact.", }, + &cli.StringFlag{ + Name: flgDisableCommonName, + EnvVars: []string{flgDisableCommonName}, + Usage: "Disable the use of the common name in the CSR.", + }, &cli.StringFlag{ Name: flgCSR, Aliases: []string{"c"}, diff --git a/cmd/setup.go b/cmd/setup.go index 28c2c8eef..fd8038464 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -50,6 +50,7 @@ func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyTy KeyType: keyType, Timeout: time.Duration(ctx.Int(flgCertTimeout)) * time.Second, OverallRequestLimit: ctx.Int(flgOverallRequestLimit), + DisableCommonName: ctx.Bool(flgDisableCommonName), } config.UserAgent = getUserAgent(ctx) diff --git a/lego/client.go b/lego/client.go index 1109e1224..d06956203 100644 --- a/lego/client.go +++ b/lego/client.go @@ -53,7 +53,15 @@ func NewClient(config *Config) (*Client, error) { solversManager := resolver.NewSolversManager(core) prober := resolver.NewProber(solversManager) - certifier := certificate.NewCertifier(core, prober, certificate.CertifierOptions{KeyType: config.Certificate.KeyType, Timeout: config.Certificate.Timeout, OverallRequestLimit: config.Certificate.OverallRequestLimit}) + + options := certificate.CertifierOptions{ + KeyType: config.Certificate.KeyType, + Timeout: config.Certificate.Timeout, + OverallRequestLimit: config.Certificate.OverallRequestLimit, + DisableCommonName: config.Certificate.DisableCommonName, + } + + certifier := certificate.NewCertifier(core, prober, options) return &Client{ Certificate: certifier, diff --git a/lego/client_config.go b/lego/client_config.go index fdf1a55f8..969135a13 100644 --- a/lego/client_config.go +++ b/lego/client_config.go @@ -64,6 +64,7 @@ type CertificateConfig struct { KeyType certcrypto.KeyType Timeout time.Duration OverallRequestLimit int + DisableCommonName bool } // createDefaultHTTPClient Creates an HTTP client with a reasonable timeout value From 713acefd7f3233266910019fdab5567387075a42 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 8 Jul 2025 17:23:52 +0200 Subject: [PATCH 117/298] chore: update to go1.24 (#2566) --- .golangci.yml | 7 ++ acme/api/api.go | 8 +- acme/api/internal/secure/jws.go | 4 +- acme/api/internal/sender/sender.go | 6 +- certcrypto/crypto.go | 4 +- challenge/dns01/fqdn.go | 36 +++++++ challenge/dns01/fqdn_test.go | 101 ++++++++++++++++++ challenge/dns01/nameserver.go | 5 +- cmd/cmd_dnshelp.go | 4 +- e2e/loader/loader.go | 11 +- go.mod | 3 +- go.sum | 2 + internal/dns/docs/generator.go | 2 +- internal/dns/providers/generator.go | 2 +- internal/releaser/generator.go | 2 +- log/logger.go | 26 ++--- platform/config/env/env.go | 19 +++- platform/config/env/env_test.go | 74 +++++++++++++ platform/tester/api.go | 2 +- providers/dns/acmedns/acmedns_test.go | 5 +- .../dns/acmedns/internal/http_storage_test.go | 15 ++- providers/dns/allinkl/internal/client_test.go | 6 +- providers/dns/allinkl/internal/identity.go | 2 +- .../dns/allinkl/internal/identity_test.go | 10 +- .../dns/arvancloud/internal/client_test.go | 9 +- providers/dns/autodns/internal/client.go | 2 +- providers/dns/autodns/internal/client_test.go | 5 +- .../dns/axelname/internal/client_test.go | 13 ++- providers/dns/azion/azion.go | 7 +- providers/dns/bluecat/internal/client.go | 2 +- providers/dns/bluecat/internal/client_test.go | 3 +- .../dns/bluecat/internal/identity_test.go | 3 +- .../dns/bookmyname/internal/client_test.go | 9 +- providers/dns/brandit/internal/client_test.go | 17 ++- providers/dns/bunny/bunny.go | 11 +- .../dns/checkdomain/internal/client_test.go | 9 +- .../dns/clouddns/internal/client_test.go | 5 +- .../dns/clouddns/internal/identity_test.go | 3 +- providers/dns/cloudns/internal/client_test.go | 13 ++- providers/dns/cloudru/internal/client_test.go | 8 +- .../dns/cloudru/internal/identity_test.go | 10 +- providers/dns/conoha/internal/client.go | 2 +- providers/dns/conoha/internal/client_test.go | 9 +- .../dns/conoha/internal/identity_test.go | 3 +- providers/dns/conohav3/internal/client.go | 2 +- .../dns/conohav3/internal/client_test.go | 9 +- .../dns/conohav3/internal/identity_test.go | 3 +- .../dns/constellix/internal/domains_test.go | 5 +- .../constellix/internal/txtrecords_test.go | 13 ++- .../dns/corenetworks/internal/client_test.go | 15 ++- .../dns/cpanel/internal/cpanel/client.go | 2 +- .../dns/cpanel/internal/cpanel/client_test.go | 19 ++-- providers/dns/cpanel/internal/whm/client.go | 2 +- .../dns/cpanel/internal/whm/client_test.go | 19 ++-- providers/dns/derak/internal/client.go | 6 +- providers/dns/derak/internal/client_test.go | 25 +++-- .../dns/digitalocean/internal/client_test.go | 5 +- .../dns/directadmin/internal/client_test.go | 9 +- providers/dns/dnshomede/dnshomede.go | 21 +--- providers/dns/dnshomede/dnshomede_test.go | 4 +- .../dns/dnshomede/internal/client_test.go | 9 +- providers/dns/dode/internal/client_test.go | 5 +- providers/dns/domeneshop/internal/client.go | 6 +- .../dns/domeneshop/internal/client_test.go | 9 +- providers/dns/dyn/internal/client.go | 2 +- providers/dns/dyn/internal/client_test.go | 7 +- providers/dns/dyn/internal/session_test.go | 12 ++- .../dns/dyndnsfree/internal/client_test.go | 5 +- providers/dns/dynu/internal/client_test.go | 9 +- providers/dns/easydns/internal/client.go | 2 +- providers/dns/easydns/internal/client_test.go | 11 +- providers/dns/efficientip/internal/client.go | 2 +- .../dns/efficientip/internal/client_test.go | 11 +- providers/dns/epik/internal/client.go | 2 +- providers/dns/epik/internal/client_test.go | 13 ++- providers/dns/exec/log_mock_test.go | 12 +-- providers/dns/exoscale/exoscale.go | 2 +- providers/dns/f5xc/internal/client.go | 2 +- providers/dns/f5xc/internal/client_test.go | 19 ++-- providers/dns/gcloud/googlecloud_test.go | 2 +- providers/dns/gcore/internal/client.go | 2 +- providers/dns/gcore/internal/client_test.go | 17 ++- providers/dns/glesys/internal/client.go | 2 +- providers/dns/glesys/internal/client_test.go | 5 +- providers/dns/godaddy/internal/client.go | 2 +- providers/dns/godaddy/internal/client_test.go | 13 ++- providers/dns/hetzner/internal/client_test.go | 9 +- .../dns/hosttech/internal/client_test.go | 21 ++-- providers/dns/hurricane/hurricane.go | 21 +--- providers/dns/hurricane/hurricane_test.go | 4 +- providers/dns/hurricane/internal/client.go | 4 +- .../dns/hurricane/internal/client_test.go | 3 +- providers/dns/hyperone/internal/client.go | 4 +- .../dns/hyperone/internal/client_test.go | 19 ++-- providers/dns/iij/iij.go | 22 ++-- providers/dns/iij/iij_test.go | 54 +++++++--- .../dns/infomaniak/internal/client_test.go | 7 +- .../dns/internal/active24/client_test.go | 17 ++- .../dns/internal/hostingde/client_test.go | 9 +- .../dns/internal/rimuhosting/client_test.go | 5 +- .../dns/internal/selectel/client_test.go | 11 +- providers/dns/internetbs/internal/client.go | 4 +- .../dns/internetbs/internal/client_test.go | 21 ++-- providers/dns/ionos/internal/client_test.go | 17 ++- providers/dns/ipv64/internal/client_test.go | 13 ++- providers/dns/iwantmyname/internal/client.go | 2 +- .../dns/iwantmyname/internal/client_test.go | 3 +- providers/dns/joker/internal/dmapi/client.go | 6 +- .../dns/joker/internal/dmapi/client_test.go | 2 +- .../dns/joker/internal/dmapi/identity_test.go | 18 ++-- .../dns/joker/internal/svc/client_test.go | 5 +- providers/dns/joker/joker_test.go | 4 +- providers/dns/liara/internal/client_test.go | 15 ++- providers/dns/lightsail/lightsail.go | 5 +- .../lightsail/lightsail_integration_test.go | 3 +- providers/dns/lightsail/lightsail_test.go | 3 +- .../dns/limacity/internal/client_test.go | 23 ++-- providers/dns/limacity/limacity.go | 6 +- providers/dns/linode/linode_test.go | 2 +- providers/dns/loopia/internal/client.go | 6 +- providers/dns/loopia/internal/client_test.go | 13 ++- providers/dns/loopia/loopia.go | 6 +- providers/dns/loopia/loopia_mock_test.go | 6 +- providers/dns/luadns/internal/client_test.go | 7 +- providers/dns/manageengine/internal/client.go | 2 +- .../dns/manageengine/internal/client_test.go | 25 +++-- .../dns/metaregistrar/internal/client_test.go | 5 +- .../dns/mijnhost/internal/client_test.go | 9 +- providers/dns/mijnhost/mijnhost.go | 7 +- .../dns/mittwald/internal/client_test.go | 17 ++- providers/dns/mittwald/mittwald.go | 13 +-- providers/dns/myaddr/internal/client_test.go | 7 +- providers/dns/myaddr/myaddr.go | 18 +--- providers/dns/mydnsjp/internal/client.go | 2 +- providers/dns/mydnsjp/internal/client_test.go | 5 +- providers/dns/mythicbeasts/internal/client.go | 2 +- .../dns/mythicbeasts/internal/client_test.go | 4 +- .../mythicbeasts/internal/identity_test.go | 10 +- providers/dns/namecheap/internal/client.go | 2 +- .../dns/namecheap/internal/client_test.go | 9 +- .../dns/nearlyfreespeech/internal/client.go | 4 +- .../nearlyfreespeech/internal/client_test.go | 9 +- providers/dns/netcup/internal/client.go | 2 +- providers/dns/netcup/internal/client_test.go | 9 +- providers/dns/netcup/internal/session_test.go | 16 +-- providers/dns/netlify/internal/client.go | 2 +- providers/dns/netlify/internal/client_test.go | 7 +- .../dns/nicmanager/internal/client_test.go | 13 ++- providers/dns/nicru/internal/client.go | 2 +- providers/dns/nicru/internal/client_test.go | 29 +++-- .../dns/nifcloud/internal/client_test.go | 9 +- providers/dns/njalla/internal/client.go | 4 +- providers/dns/njalla/internal/client_test.go | 17 ++- providers/dns/otc/internal/client.go | 4 +- providers/dns/otc/internal/identity_test.go | 3 +- providers/dns/pdns/internal/client_test.go | 21 ++-- providers/dns/plesk/internal/client.go | 2 +- providers/dns/plesk/internal/client_test.go | 19 ++-- providers/dns/rackspace/internal/client.go | 6 +- .../dns/rackspace/internal/client_test.go | 9 +- .../dns/rackspace/internal/identity_test.go | 3 +- providers/dns/rainyun/internal/client_test.go | 17 ++- .../dns/rcodezero/internal/client_test.go | 5 +- providers/dns/regru/internal/client_test.go | 9 +- providers/dns/route53/route53.go | 5 +- .../dns/route53/route53_integration_test.go | 5 +- providers/dns/route53/route53_test.go | 9 +- providers/dns/safedns/internal/client_test.go | 5 +- providers/dns/sakuracloud/wrapper_test.go | 5 +- .../dns/selfhostde/internal/client_test.go | 5 +- .../dns/servercow/internal/client_test.go | 13 ++- providers/dns/shellrent/internal/client.go | 4 +- .../dns/shellrent/internal/client_test.go | 31 +++--- providers/dns/simply/internal/client.go | 4 +- providers/dns/simply/internal/client_test.go | 17 ++- providers/dns/sonic/internal/client.go | 2 +- providers/dns/sonic/internal/client_test.go | 3 +- .../dns/spaceship/internal/client_test.go | 13 ++- .../dns/stackpath/internal/client_test.go | 9 +- .../dns/technitium/internal/client_test.go | 11 +- .../dns/timewebcloud/internal/client_test.go | 9 +- .../dns/variomedia/internal/client_test.go | 9 +- providers/dns/variomedia/variomedia.go | 2 +- providers/dns/vercel/internal/client.go | 2 +- providers/dns/vercel/internal/client_test.go | 5 +- providers/dns/versio/internal/client.go | 2 +- providers/dns/versio/internal/client_test.go | 9 +- providers/dns/volcengine/volcengine.go | 5 +- providers/dns/vultr/vultr_test.go | 8 +- .../dns/webnames/internal/client_test.go | 5 +- providers/dns/wedos/internal/client.go | 4 +- providers/dns/wedos/internal/client_test.go | 13 ++- providers/dns/wedos/internal/token.go | 2 +- providers/dns/westcn/internal/client_test.go | 9 +- providers/dns/yandex/internal/client_test.go | 7 +- .../dns/yandex360/internal/client_test.go | 9 +- providers/dns/zoneee/internal/client.go | 2 +- providers/dns/zoneee/internal/client_test.go | 7 +- 198 files changed, 968 insertions(+), 881 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index c7530d3b5..42f10adc7 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -6,6 +6,13 @@ formatters: - gofmt - gofumpt - goimports + settings: + gofumpt: + extra-rules: true + gofmt: + rewrite-rules: + - pattern: 'interface{}' + replacement: 'any' linters: default: all diff --git a/acme/api/api.go b/acme/api/api.go index b8c9cf0c9..a4e6398ea 100644 --- a/acme/api/api.go +++ b/acme/api/api.go @@ -60,7 +60,7 @@ func New(httpClient *http.Client, userAgent, caDirURL, kid string, privateKey cr // post performs an HTTP POST request and parses the response body as JSON, // into the provided respBody object. -func (a *Core) post(uri string, reqBody, response interface{}) (*http.Response, error) { +func (a *Core) post(uri string, reqBody, response any) (*http.Response, error) { content, err := json.Marshal(reqBody) if err != nil { return nil, errors.New("failed to marshal message") @@ -71,11 +71,11 @@ func (a *Core) post(uri string, reqBody, response interface{}) (*http.Response, // postAsGet performs an HTTP POST ("POST-as-GET") request. // https://www.rfc-editor.org/rfc/rfc8555.html#section-6.3 -func (a *Core) postAsGet(uri string, response interface{}) (*http.Response, error) { +func (a *Core) postAsGet(uri string, response any) (*http.Response, error) { return a.retrievablePost(uri, []byte{}, response) } -func (a *Core) retrievablePost(uri string, content []byte, response interface{}) (*http.Response, error) { +func (a *Core) retrievablePost(uri string, content []byte, response any) (*http.Response, error) { // during tests, allow to support ~90% of bad nonce with a minimum of attempts. bo := backoff.NewExponentialBackOff() bo.InitialInterval = 200 * time.Millisecond @@ -111,7 +111,7 @@ func (a *Core) retrievablePost(uri string, content []byte, response interface{}) return resp, nil } -func (a *Core) signedPost(uri string, content []byte, response interface{}) (*http.Response, error) { +func (a *Core) signedPost(uri string, content []byte, response any) (*http.Response, error) { signedContent, err := a.jws.SignContent(uri, content) if err != nil { return nil, fmt.Errorf("failed to post JWS message: failed to sign content: %w", err) diff --git a/acme/api/internal/secure/jws.go b/acme/api/internal/secure/jws.go index 8afd44676..7aa6c4c46 100644 --- a/acme/api/internal/secure/jws.go +++ b/acme/api/internal/secure/jws.go @@ -54,7 +54,7 @@ func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, e options := jose.SignerOptions{ NonceSource: j.nonces, - ExtraHeaders: map[jose.HeaderKey]interface{}{ + ExtraHeaders: map[jose.HeaderKey]any{ "url": url, }, } @@ -87,7 +87,7 @@ func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignatu jose.SigningKey{Algorithm: jose.HS256, Key: hmac}, &jose.SignerOptions{ EmbedJWK: false, - ExtraHeaders: map[jose.HeaderKey]interface{}{ + ExtraHeaders: map[jose.HeaderKey]any{ "kid": kid, "url": url, }, diff --git a/acme/api/internal/sender/sender.go b/acme/api/internal/sender/sender.go index 042a1318a..2e1bbec8d 100644 --- a/acme/api/internal/sender/sender.go +++ b/acme/api/internal/sender/sender.go @@ -35,7 +35,7 @@ func NewDoer(client *http.Client, userAgent string) *Doer { // Get performs a GET request with a proper User-Agent string. // If "response" is not provided, callers should close resp.Body when done reading from it. -func (d *Doer) Get(url string, response interface{}) (*http.Response, error) { +func (d *Doer) Get(url string, response any) (*http.Response, error) { req, err := d.newRequest(http.MethodGet, url, nil) if err != nil { return nil, err @@ -57,7 +57,7 @@ func (d *Doer) Head(url string) (*http.Response, error) { // Post performs a POST request with a proper User-Agent string. // If "response" is not provided, callers should close resp.Body when done reading from it. -func (d *Doer) Post(url string, body io.Reader, bodyType string, response interface{}) (*http.Response, error) { +func (d *Doer) Post(url string, body io.Reader, bodyType string, response any) (*http.Response, error) { req, err := d.newRequest(http.MethodPost, url, body, contentType(bodyType)) if err != nil { return nil, err @@ -84,7 +84,7 @@ func (d *Doer) newRequest(method, uri string, body io.Reader, opts ...RequestOpt return req, nil } -func (d *Doer) do(req *http.Request, response interface{}) (*http.Response, error) { +func (d *Doer) do(req *http.Request, response any) (*http.Response, error) { resp, err := d.httpClient.Do(req) if err != nil { return nil, err diff --git a/certcrypto/crypto.go b/certcrypto/crypto.go index c6551071e..d6f53c3a1 100644 --- a/certcrypto/crypto.go +++ b/certcrypto/crypto.go @@ -179,11 +179,11 @@ func CreateCSR(privateKey crypto.PrivateKey, opts CSROptions) ([]byte, error) { return x509.CreateCertificateRequest(rand.Reader, &template, privateKey) } -func PEMEncode(data interface{}) []byte { +func PEMEncode(data any) []byte { return pem.EncodeToMemory(PEMBlock(data)) } -func PEMBlock(data interface{}) *pem.Block { +func PEMBlock(data any) *pem.Block { var pemBlock *pem.Block switch key := data.(type) { case *ecdsa.PrivateKey: diff --git a/challenge/dns01/fqdn.go b/challenge/dns01/fqdn.go index c238c8cf5..3b94a491a 100644 --- a/challenge/dns01/fqdn.go +++ b/challenge/dns01/fqdn.go @@ -1,5 +1,11 @@ package dns01 +import ( + "iter" + + "github.com/miekg/dns" +) + // ToFqdn converts the name into a fqdn appending a trailing dot. func ToFqdn(name string) string { n := len(name) @@ -17,3 +23,33 @@ func UnFqdn(name string) string { } return name } + +// UnFqdnDomainsSeq generates a sequence of "unFQDNed" domain names derived from a domain (FQDN or not) in descending order. +func UnFqdnDomainsSeq(fqdn string) iter.Seq[string] { + return func(yield func(string) bool) { + if fqdn == "" { + return + } + + for _, index := range dns.Split(fqdn) { + if !yield(UnFqdn(fqdn[index:])) { + return + } + } + } +} + +// DomainsSeq generates a sequence of domain names derived from a domain (FQDN or not) in descending order. +func DomainsSeq(fqdn string) iter.Seq[string] { + return func(yield func(string) bool) { + if fqdn == "" { + return + } + + for _, index := range dns.Split(fqdn) { + if !yield(fqdn[index:]) { + return + } + } + } +} diff --git a/challenge/dns01/fqdn_test.go b/challenge/dns01/fqdn_test.go index a902667a2..7a6506d4e 100644 --- a/challenge/dns01/fqdn_test.go +++ b/challenge/dns01/fqdn_test.go @@ -1,6 +1,7 @@ package dns01 import ( + "slices" "testing" "github.com/stretchr/testify/assert" @@ -62,3 +63,103 @@ func TestUnFqdn(t *testing.T) { }) } } + +func TestUnFqdnDomainsSeq(t *testing.T) { + testCases := []struct { + desc string + fqdn string + expected []string + }{ + { + desc: "empty", + fqdn: "", + expected: nil, + }, + { + desc: "TLD", + fqdn: "com", + expected: []string{"com"}, + }, + { + desc: "2 levels", + fqdn: "example.com", + expected: []string{"example.com", "com"}, + }, + { + desc: "3 levels", + fqdn: "foo.example.com", + expected: []string{"foo.example.com", "example.com", "com"}, + }, + } + + for _, test := range testCases { + for name, suffix := range map[string]string{"": "", " FQDN": "."} { //nolint:gocritic + t.Run(test.desc+name, func(t *testing.T) { + t.Parallel() + + actual := slices.Collect(UnFqdnDomainsSeq(test.fqdn + suffix)) + + assert.Equal(t, test.expected, actual) + }) + } + } +} + +func TestDomainsSeq(t *testing.T) { + testCases := []struct { + desc string + fqdn string + expected []string + }{ + { + desc: "empty", + fqdn: "", + expected: nil, + }, + { + desc: "empty FQDN", + fqdn: ".", + expected: nil, + }, + { + desc: "TLD FQDN", + fqdn: "com", + expected: []string{"com"}, + }, + { + desc: "TLD", + fqdn: "com.", + expected: []string{"com."}, + }, + { + desc: "2 levels", + fqdn: "example.com", + expected: []string{"example.com", "com"}, + }, + { + desc: "2 levels FQDN", + fqdn: "example.com.", + expected: []string{"example.com.", "com."}, + }, + { + desc: "3 levels", + fqdn: "foo.example.com", + expected: []string{"foo.example.com", "example.com", "com"}, + }, + { + desc: "3 levels FQDN", + fqdn: "foo.example.com.", + expected: []string{"foo.example.com.", "example.com.", "com."}, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := slices.Collect(DomainsSeq(test.fqdn)) + + assert.Equal(t, test.expected, actual) + }) + } +} diff --git a/challenge/dns01/nameserver.go b/challenge/dns01/nameserver.go index a8d678af2..bb6dc0841 100644 --- a/challenge/dns01/nameserver.go +++ b/challenge/dns01/nameserver.go @@ -175,10 +175,7 @@ func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) { var err error var r *dns.Msg - labelIndexes := dns.Split(fqdn) - for _, index := range labelIndexes { - domain := fqdn[index:] - + for domain := range DomainsSeq(fqdn) { r, err = dnsQuery(domain, dns.TypeSOA, nameservers, true) if err != nil { continue diff --git a/cmd/cmd_dnshelp.go b/cmd/cmd_dnshelp.go index 1a61cac80..41adf4c8d 100644 --- a/cmd/cmd_dnshelp.go +++ b/cmd/cmd_dnshelp.go @@ -58,7 +58,7 @@ type errWriter struct { err error } -func (ew *errWriter) writeln(a ...interface{}) { +func (ew *errWriter) writeln(a ...any) { if ew.err != nil { return } @@ -66,7 +66,7 @@ func (ew *errWriter) writeln(a ...interface{}) { _, ew.err = fmt.Fprintln(ew.w, a...) } -func (ew *errWriter) writef(format string, a ...interface{}) { +func (ew *errWriter) writef(format string, a ...any) { if ew.err != nil { return } diff --git a/e2e/loader/loader.go b/e2e/loader/loader.go index 5579bb523..b5ac9cef8 100644 --- a/e2e/loader/loader.go +++ b/e2e/loader/loader.go @@ -3,6 +3,7 @@ package loader import ( "bufio" "bytes" + "context" "crypto/tls" "errors" "fmt" @@ -16,6 +17,7 @@ import ( "time" "github.com/go-acme/lego/v4/platform/wait" + "github.com/ldez/grignotin/goenv" ) const ( @@ -311,8 +313,13 @@ func goTool() (string, error) { exeSuffix = ".exe" } - path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix) - if _, err := os.Stat(path); err == nil { + goRoot, err := goenv.GetOne(context.Background(), goenv.GOROOT) + if err != nil { + return "", fmt.Errorf("cannot find go root: %w", err) + } + + path := filepath.Join(goRoot, "bin", "go"+exeSuffix) + if _, err = os.Stat(path); err == nil { return path, nil } diff --git a/go.mod b/go.mod index 4d78443e9..52f4d2eb5 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/go-acme/lego/v4 -go 1.23.0 +go 1.24.0 require ( cloud.google.com/go/compute/metadata v0.6.0 @@ -44,6 +44,7 @@ require ( github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df github.com/infobloxopen/infoblox-go-client/v2 v2.9.0 github.com/labbsr0x/bindman-dns-webhook v1.0.2 + github.com/ldez/grignotin v0.9.0 github.com/linode/linodego v1.48.1 github.com/liquidweb/liquidweb-go v1.6.4 github.com/mattn/go-isatty v0.0.20 diff --git a/go.sum b/go.sum index e30416eb7..bb55586a4 100644 --- a/go.sum +++ b/go.sum @@ -597,6 +597,8 @@ github.com/labbsr0x/bindman-dns-webhook v1.0.2 h1:I7ITbmQPAVwrDdhd6dHKi+MYJTJqPC github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA= github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk= github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= +github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= +github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/linode/linodego v1.48.1 h1:Ojw1S+K5jJr1dggO8/H6r4FINxXnJbOU5GkbpaTfmhU= diff --git a/internal/dns/docs/generator.go b/internal/dns/docs/generator.go index a6b91b45d..d618ce568 100644 --- a/internal/dns/docs/generator.go +++ b/internal/dns/docs/generator.go @@ -96,7 +96,7 @@ func generateCLIHelp(models *descriptors.Providers) error { b := &bytes.Buffer{} err = template.Must( - template.New(filepath.Base(cliTemplate)).Funcs(map[string]interface{}{ + template.New(filepath.Base(cliTemplate)).Funcs(map[string]any{ "safe": func(src string) string { return strings.ReplaceAll(src, "`", "'") }, diff --git a/internal/dns/providers/generator.go b/internal/dns/providers/generator.go index bab31072d..8f133a765 100644 --- a/internal/dns/providers/generator.go +++ b/internal/dns/providers/generator.go @@ -47,7 +47,7 @@ func generate() error { b := &bytes.Buffer{} err = template.Must( - template.New("").Funcs(map[string]interface{}{ + template.New("").Funcs(map[string]any{ "cleanName": func(src string) string { return strings.ReplaceAll(src, "-", "") }, diff --git a/internal/releaser/generator.go b/internal/releaser/generator.go index d1b3e74e1..f24aea25f 100644 --- a/internal/releaser/generator.go +++ b/internal/releaser/generator.go @@ -33,7 +33,7 @@ type Generator struct { targetFile string } -func NewGenerator(templatePath string, targetFile string) *Generator { +func NewGenerator(templatePath, targetFile string) *Generator { return &Generator{templatePath: templatePath, targetFile: targetFile} } diff --git a/log/logger.go b/log/logger.go index 48a81fad0..2f700a359 100644 --- a/log/logger.go +++ b/log/logger.go @@ -10,50 +10,50 @@ var Logger StdLogger = log.New(os.Stderr, "", log.LstdFlags) // StdLogger interface for Standard Logger. type StdLogger interface { - Fatal(args ...interface{}) - Fatalln(args ...interface{}) - Fatalf(format string, args ...interface{}) - Print(args ...interface{}) - Println(args ...interface{}) - Printf(format string, args ...interface{}) + Fatal(args ...any) + Fatalln(args ...any) + Fatalf(format string, args ...any) + Print(args ...any) + Println(args ...any) + Printf(format string, args ...any) } // Fatal writes a log entry. // It uses Logger if not nil, otherwise it uses the default log.Logger. -func Fatal(args ...interface{}) { +func Fatal(args ...any) { Logger.Fatal(args...) } // Fatalf writes a log entry. // It uses Logger if not nil, otherwise it uses the default log.Logger. -func Fatalf(format string, args ...interface{}) { +func Fatalf(format string, args ...any) { Logger.Fatalf(format, args...) } // Print writes a log entry. // It uses Logger if not nil, otherwise it uses the default log.Logger. -func Print(args ...interface{}) { +func Print(args ...any) { Logger.Print(args...) } // Println writes a log entry. // It uses Logger if not nil, otherwise it uses the default log.Logger. -func Println(args ...interface{}) { +func Println(args ...any) { Logger.Println(args...) } // Printf writes a log entry. // It uses Logger if not nil, otherwise it uses the default log.Logger. -func Printf(format string, args ...interface{}) { +func Printf(format string, args ...any) { Logger.Printf(format, args...) } // Warnf writes a log entry. -func Warnf(format string, args ...interface{}) { +func Warnf(format string, args ...any) { Printf("[WARN] "+format, args...) } // Infof writes a log entry. -func Infof(format string, args ...interface{}) { +func Infof(format string, args ...any) { Printf("[INFO] "+format, args...) } diff --git a/platform/config/env/env.go b/platform/config/env/env.go index 3fd1e3a1a..b74a65cd9 100644 --- a/platform/config/env/env.go +++ b/platform/config/env/env.go @@ -107,7 +107,7 @@ func getOneWithFallback(main string, names ...string) (string, string) { // GetOrDefaultString returns the given environment variable value as a string. // Returns the default if the env var cannot be found. -func GetOrDefaultString(envVar string, defaultValue string) string { +func GetOrDefaultString(envVar, defaultValue string) string { return getOrDefault(envVar, defaultValue, ParseString) } @@ -184,3 +184,20 @@ func ParseString(s string) (string, error) { return s, nil } + +// ParsePairs parses a raw string of comma-separated key-value pairs into a map. +// Keys and values are separated by a colon and are trimmed of whitespace. +func ParsePairs(raw string) (map[string]string, error) { + result := make(map[string]string) + + for pair := range strings.SplitSeq(strings.TrimSuffix(raw, ","), ",") { + data := strings.Split(pair, ":") + if len(data) != 2 { + return nil, fmt.Errorf("incorrect pair: %s", pair) + } + + result[strings.TrimSpace(data[0])] = strings.TrimSpace(data[1]) + } + + return result, nil +} diff --git a/platform/config/env/env_test.go b/platform/config/env/env_test.go index d7c51a552..b131d4d91 100644 --- a/platform/config/env/env_test.go +++ b/platform/config/env/env_test.go @@ -408,3 +408,77 @@ func TestGetOrFile_PrefersEnvVars(t *testing.T) { assert.Equal(t, "lego_env", value) } + +func TestParsePairs(t *testing.T) { + testCases := []struct { + desc string + value string + expected map[string]string + }{ + { + desc: "one pair", + value: "foo:bar", + expected: map[string]string{"foo": "bar"}, + }, + { + desc: "multiple pairs", + value: "foo:bar,a:b,c:d", + expected: map[string]string{"a": "b", "c": "d", "foo": "bar"}, + }, + { + desc: "multiple pairs with spaces", + value: "foo:bar, a:b , c: d", + expected: map[string]string{"a": "b", "c": "d", "foo": "bar"}, + }, + { + desc: "empty value pair", + value: "foo:", + expected: map[string]string{"foo": ""}, + }, + { + desc: "empty key pair", + value: ":bar", + expected: map[string]string{"": "bar"}, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + pairs, err := ParsePairs(test.value) + require.NoError(t, err) + + assert.Equal(t, test.expected, pairs) + }) + } +} + +func TestParsePairs_error(t *testing.T) { + testCases := []struct { + desc string + value string + }{ + { + desc: "empty value", + value: "", + }, + { + desc: "multiple colons", + value: "foo:bar:bir", + }, + { + desc: "valid pair and multiple colons", + value: "a:b,foo:bar:bir", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + _, err := ParsePairs(test.value) + require.Error(t, err) + }) + } +} diff --git a/platform/tester/api.go b/platform/tester/api.go index 175530f96..2084cf1bb 100644 --- a/platform/tester/api.go +++ b/platform/tester/api.go @@ -52,7 +52,7 @@ func SetupFakeAPI(t *testing.T) (*http.ServeMux, string) { } // WriteJSONResponse marshals the body as JSON and writes it to the response. -func WriteJSONResponse(w http.ResponseWriter, body interface{}) error { +func WriteJSONResponse(w http.ResponseWriter, body any) error { bs, err := json.Marshal(body) if err != nil { return err diff --git a/providers/dns/acmedns/acmedns_test.go b/providers/dns/acmedns/acmedns_test.go index 081080d21..3bc847b6d 100644 --- a/providers/dns/acmedns/acmedns_test.go +++ b/providers/dns/acmedns/acmedns_test.go @@ -1,7 +1,6 @@ package acmedns import ( - "context" "net/http" "net/http/httptest" "testing" @@ -133,7 +132,7 @@ func TestRegister(t *testing.T) { p.storage = test.Storage } - acc, err := p.register(context.Background(), egDomain, egFQDN) + acc, err := p.register(t.Context(), egDomain, egFQDN) if test.ExpectedError != nil { assert.Equal(t, test.ExpectedError, err) } else { @@ -242,7 +241,7 @@ func TestRegister_httpStorage(t *testing.T) { w.WriteHeader(test.StatusCode) }) - acc, err := p.register(context.Background(), egDomain, egFQDN) + acc, err := p.register(t.Context(), egDomain, egFQDN) if test.ExpectedError != nil { assert.Equal(t, test.ExpectedError, err) } else { diff --git a/providers/dns/acmedns/internal/http_storage_test.go b/providers/dns/acmedns/internal/http_storage_test.go index 7f9367722..14a5fd97c 100644 --- a/providers/dns/acmedns/internal/http_storage_test.go +++ b/providers/dns/acmedns/internal/http_storage_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "io" "net/http" "net/http/httptest" @@ -54,7 +53,7 @@ func setupTest(t *testing.T, pattern, filename string, statusCode int) *HTTPStor func TestHTTPStorage_Fetch(t *testing.T) { storage := setupTest(t, "GET /example.com", "fetch.json", http.StatusOK) - account, err := storage.Fetch(context.Background(), "example.com") + account, err := storage.Fetch(t.Context(), "example.com") require.NoError(t, err) expected := goacmedns.Account{ @@ -71,14 +70,14 @@ func TestHTTPStorage_Fetch(t *testing.T) { func TestHTTPStorage_Fetch_error(t *testing.T) { storage := setupTest(t, "GET /example.com", "error.json", http.StatusInternalServerError) - _, err := storage.Fetch(context.Background(), "example.com") + _, err := storage.Fetch(t.Context(), "example.com") require.Error(t, err) } func TestHTTPStorage_FetchAll(t *testing.T) { storage := setupTest(t, "GET /", "fetch-all.json", http.StatusOK) - account, err := storage.FetchAll(context.Background()) + account, err := storage.FetchAll(t.Context()) require.NoError(t, err) expected := map[string]goacmedns.Account{ @@ -104,7 +103,7 @@ func TestHTTPStorage_FetchAll(t *testing.T) { func TestHTTPStorage_FetchAll_error(t *testing.T) { storage := setupTest(t, "GET /", "error.json", http.StatusInternalServerError) - _, err := storage.FetchAll(context.Background()) + _, err := storage.FetchAll(t.Context()) require.Error(t, err) } @@ -119,7 +118,7 @@ func TestHTTPStorage_Put(t *testing.T) { ServerURL: "https://example.com", } - err := storage.Put(context.Background(), "example.com", account) + err := storage.Put(t.Context(), "example.com", account) require.NoError(t, err) } @@ -134,7 +133,7 @@ func TestHTTPStorage_Put_error(t *testing.T) { ServerURL: "https://example.com", } - err := storage.Put(context.Background(), "example.com", account) + err := storage.Put(t.Context(), "example.com", account) require.Error(t, err) } @@ -149,6 +148,6 @@ func TestHTTPStorage_Put_CNAME_created(t *testing.T) { ServerURL: "https://example.com", } - err := storage.Put(context.Background(), "example.com", account) + err := storage.Put(t.Context(), "example.com", account) require.ErrorIs(t, err, ErrCNAMEAlreadyCreated) } diff --git a/providers/dns/allinkl/internal/client_test.go b/providers/dns/allinkl/internal/client_test.go index b8cc27851..6ccb1ebf6 100644 --- a/providers/dns/allinkl/internal/client_test.go +++ b/providers/dns/allinkl/internal/client_test.go @@ -23,7 +23,7 @@ func TestClient_GetDNSSettings(t *testing.T) { client := NewClient("user") client.baseURL = server.URL - records, err := client.GetDNSSettings(mockContext(), "example.com", "") + records, err := client.GetDNSSettings(mockContext(t), "example.com", "") require.NoError(t, err) expected := []ReturnInfo{ @@ -112,7 +112,7 @@ func TestClient_AddDNSSettings(t *testing.T) { RecordData: "abcdefgh", } - recordID, err := client.AddDNSSettings(mockContext(), record) + recordID, err := client.AddDNSSettings(mockContext(t), record) require.NoError(t, err) assert.Equal(t, "57347444", recordID) @@ -128,7 +128,7 @@ func TestClient_DeleteDNSSettings(t *testing.T) { client := NewClient("user") client.baseURL = server.URL - r, err := client.DeleteDNSSettings(mockContext(), "57347450") + r, err := client.DeleteDNSSettings(mockContext(t), "57347450") require.NoError(t, err) assert.Equal(t, "TRUE", r) diff --git a/providers/dns/allinkl/internal/identity.go b/providers/dns/allinkl/internal/identity.go index 4353ece31..ba8d4d90e 100644 --- a/providers/dns/allinkl/internal/identity.go +++ b/providers/dns/allinkl/internal/identity.go @@ -29,7 +29,7 @@ type Identifier struct { } // NewIdentifier creates a new Identifier. -func NewIdentifier(login string, password string) *Identifier { +func NewIdentifier(login, password string) *Identifier { return &Identifier{ login: login, password: password, diff --git a/providers/dns/allinkl/internal/identity_test.go b/providers/dns/allinkl/internal/identity_test.go index 0753f3862..2ef0a4ca4 100644 --- a/providers/dns/allinkl/internal/identity_test.go +++ b/providers/dns/allinkl/internal/identity_test.go @@ -10,8 +10,10 @@ import ( "github.com/stretchr/testify/require" ) -func mockContext() context.Context { - return context.WithValue(context.Background(), tokenKey, "593959ca04f0de9689b586c6a647d15d") +func mockContext(t *testing.T) context.Context { + t.Helper() + + return context.WithValue(t.Context(), tokenKey, "593959ca04f0de9689b586c6a647d15d") } func TestIdentifier_Authentication(t *testing.T) { @@ -24,7 +26,7 @@ func TestIdentifier_Authentication(t *testing.T) { client := NewIdentifier("user", "secret") client.authEndpoint = server.URL - credentialToken, err := client.Authentication(context.Background(), 60, false) + credentialToken, err := client.Authentication(t.Context(), 60, false) require.NoError(t, err) assert.Equal(t, "593959ca04f0de9689b586c6a647d15d", credentialToken) @@ -40,6 +42,6 @@ func TestIdentifier_Authentication_error(t *testing.T) { client := NewIdentifier("user", "secret") client.authEndpoint = server.URL - _, err := client.Authentication(context.Background(), 60, false) + _, err := client.Authentication(t.Context(), 60, false) require.Error(t, err) } diff --git a/providers/dns/arvancloud/internal/client_test.go b/providers/dns/arvancloud/internal/client_test.go index 5c9154c62..2930dcb3d 100644 --- a/providers/dns/arvancloud/internal/client_test.go +++ b/providers/dns/arvancloud/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -61,7 +60,7 @@ func TestClient_GetTxtRecord(t *testing.T) { } }) - _, err := client.GetTxtRecord(context.Background(), domain, "_acme-challenge", "txtxtxt") + _, err := client.GetTxtRecord(t.Context(), domain, "_acme-challenge", "txtxtxt") require.NoError(t, err) } @@ -106,13 +105,13 @@ func TestClient_CreateRecord(t *testing.T) { TTL: 600, } - newRecord, err := client.CreateRecord(context.Background(), domain, record) + newRecord, err := client.CreateRecord(t.Context(), domain, record) require.NoError(t, err) expected := &DNSRecord{ ID: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", Type: "txt", - Value: map[string]interface{}{"text": "txtxtxt"}, + Value: map[string]any{"text": "txtxtxt"}, Name: "_acme-challenge", TTL: 120, UpstreamHTTPS: "default", @@ -147,6 +146,6 @@ func TestClient_DeleteRecord(t *testing.T) { } }) - err := client.DeleteRecord(context.Background(), domain, recordID) + err := client.DeleteRecord(t.Context(), domain, recordID) require.NoError(t, err) } diff --git a/providers/dns/autodns/internal/client.go b/providers/dns/autodns/internal/client.go index 363250d0a..1fc9589ea 100644 --- a/providers/dns/autodns/internal/client.go +++ b/providers/dns/autodns/internal/client.go @@ -31,7 +31,7 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(username string, password string, clientContext int) *Client { +func NewClient(username, password string, clientContext int) *Client { baseURL, _ := url.Parse(DefaultEndpoint) return &Client{ diff --git a/providers/dns/autodns/internal/client_test.go b/providers/dns/autodns/internal/client_test.go index f8743b24b..d656e0ae9 100644 --- a/providers/dns/autodns/internal/client_test.go +++ b/providers/dns/autodns/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -67,7 +66,7 @@ func TestClient_AddTxtRecords(t *testing.T) { records := []*ResourceRecord{{}} - zone, err := client.AddTxtRecords(context.Background(), "example.com", records) + zone, err := client.AddTxtRecords(t.Context(), "example.com", records) require.NoError(t, err) expected := &Zone{ @@ -91,6 +90,6 @@ func TestClient_RemoveTXTRecords(t *testing.T) { records := []*ResourceRecord{{}} - err := client.RemoveTXTRecords(context.Background(), "example.com", records) + err := client.RemoveTXTRecords(t.Context(), "example.com", records) require.NoError(t, err) } diff --git a/providers/dns/axelname/internal/client_test.go b/providers/dns/axelname/internal/client_test.go index d68e2dc5c..0ead4b180 100644 --- a/providers/dns/axelname/internal/client_test.go +++ b/providers/dns/axelname/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "io" "net/http" "net/http/httptest" @@ -55,7 +54,7 @@ func setupTest(t *testing.T, pattern string, status int, filename string) *Clien func TestClient_ListRecords(t *testing.T) { client := setupTest(t, "GET /dns_list", http.StatusOK, "dns_list.json") - records, err := client.ListRecords(context.Background(), "example.com") + records, err := client.ListRecords(t.Context(), "example.com") require.NoError(t, err) expected := []Record{ @@ -71,7 +70,7 @@ func TestClient_ListRecords(t *testing.T) { func TestClient_ListRecords_error(t *testing.T) { client := setupTest(t, "GET /dns_list", http.StatusNotFound, "dns_list_error.json") - _, err := client.ListRecords(context.Background(), "example.com") + _, err := client.ListRecords(t.Context(), "example.com") require.EqualError(t, err, "error: Domain not found (1)") } @@ -80,7 +79,7 @@ func TestClient_DeleteRecord(t *testing.T) { record := Record{ID: "74749"} - err := client.DeleteRecord(context.Background(), "example.com", record) + err := client.DeleteRecord(t.Context(), "example.com", record) require.NoError(t, err) } @@ -89,7 +88,7 @@ func TestClient_DeleteRecord_error(t *testing.T) { record := Record{ID: "74749"} - err := client.DeleteRecord(context.Background(), "example.com", record) + err := client.DeleteRecord(t.Context(), "example.com", record) require.EqualError(t, err, "error: Domain not found (1)") } @@ -98,7 +97,7 @@ func TestClient_AddRecord(t *testing.T) { record := Record{ID: "74749"} - err := client.AddRecord(context.Background(), "example.com", record) + err := client.AddRecord(t.Context(), "example.com", record) require.NoError(t, err) } @@ -107,6 +106,6 @@ func TestClient_AddRecord_error(t *testing.T) { record := Record{ID: "74749"} - err := client.AddRecord(context.Background(), "example.com", record) + err := client.AddRecord(t.Context(), "example.com", record) require.EqualError(t, err, "error: Domain not found (1)") } diff --git a/providers/dns/azion/azion.go b/providers/dns/azion/azion.go index bc25586d0..b319e1779 100644 --- a/providers/dns/azion/azion.go +++ b/providers/dns/azion/azion.go @@ -12,7 +12,6 @@ import ( "github.com/aziontech/azionapi-go-sdk/idns" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/miekg/dns" ) // Environment variables names. @@ -247,11 +246,7 @@ func (d *DNSProvider) findZone(ctx context.Context, fqdn string) (*idns.Zone, er return nil, errors.New("get zones: no results") } - labelIndexes := dns.Split(fqdn) - - for _, index := range labelIndexes { - domain := dns01.UnFqdn(fqdn[index:]) - + for domain := range dns01.UnFqdnDomainsSeq(fqdn) { for _, zone := range resp.GetResults() { if zone.GetDomain() == domain { return &zone, nil diff --git a/providers/dns/bluecat/internal/client.go b/providers/dns/bluecat/internal/client.go index a2649a455..de31579ea 100644 --- a/providers/dns/bluecat/internal/client.go +++ b/providers/dns/bluecat/internal/client.go @@ -36,7 +36,7 @@ type Client struct { HTTPClient *http.Client } -func NewClient(baseURL string, username, password string) *Client { +func NewClient(baseURL, username, password string) *Client { bu, _ := url.Parse(baseURL) return &Client{ diff --git a/providers/dns/bluecat/internal/client_test.go b/providers/dns/bluecat/internal/client_test.go index 206d7d1a4..c06ae1b8b 100644 --- a/providers/dns/bluecat/internal/client_test.go +++ b/providers/dns/bluecat/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "encoding/json" "net/http" "net/http/httptest" @@ -35,7 +34,7 @@ func TestClient_LookupParentZoneID(t *testing.T) { http.Error(rw, "{}", http.StatusOK) }) - parentID, name, err := client.LookupParentZoneID(context.Background(), 2, "foo.example.com") + parentID, name, err := client.LookupParentZoneID(t.Context(), 2, "foo.example.com") require.NoError(t, err) assert.EqualValues(t, 2, parentID) diff --git a/providers/dns/bluecat/internal/identity_test.go b/providers/dns/bluecat/internal/identity_test.go index 378f6ab38..3d9e00c0e 100644 --- a/providers/dns/bluecat/internal/identity_test.go +++ b/providers/dns/bluecat/internal/identity_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -48,7 +47,7 @@ func TestClient_CreateAuthenticatedContext(t *testing.T) { } }) - ctx, err := client.CreateAuthenticatedContext(context.Background()) + ctx, err := client.CreateAuthenticatedContext(t.Context()) require.NoError(t, err) at := getToken(ctx) diff --git a/providers/dns/bookmyname/internal/client_test.go b/providers/dns/bookmyname/internal/client_test.go index dab559cee..26e5f7227 100644 --- a/providers/dns/bookmyname/internal/client_test.go +++ b/providers/dns/bookmyname/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -62,7 +61,7 @@ func TestClient_AddRecord(t *testing.T) { Value: "test", } - err := client.AddRecord(context.Background(), record) + err := client.AddRecord(t.Context(), record) require.NoError(t, err) } @@ -76,7 +75,7 @@ func TestClient_AddRecord_error(t *testing.T) { Value: "test", } - err := client.AddRecord(context.Background(), record) + err := client.AddRecord(t.Context(), record) require.Error(t, err) require.EqualError(t, err, "unexpected response: notfqdn: Host _acme-challenge.sub.example.com. malformed / vhn") @@ -92,7 +91,7 @@ func TestClient_RemoveRecord(t *testing.T) { Value: "test", } - err := client.RemoveRecord(context.Background(), record) + err := client.RemoveRecord(t.Context(), record) require.NoError(t, err) } @@ -106,7 +105,7 @@ func TestClient_RemoveRecord_error(t *testing.T) { Value: "test", } - err := client.RemoveRecord(context.Background(), record) + err := client.RemoveRecord(t.Context(), record) require.Error(t, err) require.EqualError(t, err, "unexpected response: notfqdn: Host _acme-challenge.sub.example.com. malformed / vhn") diff --git a/providers/dns/brandit/internal/client_test.go b/providers/dns/brandit/internal/client_test.go index a37e51a29..0e79e5799 100644 --- a/providers/dns/brandit/internal/client_test.go +++ b/providers/dns/brandit/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "io" "net/http" "net/http/httptest" @@ -46,7 +45,7 @@ func setupTest(t *testing.T, filename string) *Client { func TestClient_StatusDomain(t *testing.T) { client := setupTest(t, "status-domain.json") - domain, err := client.StatusDomain(context.Background(), "example.com") + domain, err := client.StatusDomain(t.Context(), "example.com") require.NoError(t, err) expected := &StatusResponse{ @@ -82,14 +81,14 @@ func TestClient_StatusDomain(t *testing.T) { func TestClient_StatusDomain_error(t *testing.T) { client := setupTest(t, "error.json") - _, err := client.StatusDomain(context.Background(), "example.com") + _, err := client.StatusDomain(t.Context(), "example.com") require.ErrorIs(t, err, APIError{Code: 402, Status: "error", Message: "Invalid user."}) } func TestClient_ListRecords(t *testing.T) { client := setupTest(t, "list-records.json") - resp, err := client.ListRecords(context.Background(), "example", "example.com") + resp, err := client.ListRecords(t.Context(), "example", "example.com") require.NoError(t, err) expected := &ListRecordsResponse{ @@ -108,7 +107,7 @@ func TestClient_ListRecords(t *testing.T) { func TestClient_ListRecords_error(t *testing.T) { client := setupTest(t, "error.json") - _, err := client.ListRecords(context.Background(), "example", "example.com") + _, err := client.ListRecords(t.Context(), "example", "example.com") require.ErrorIs(t, err, APIError{Code: 402, Status: "error", Message: "Invalid user."}) } @@ -122,7 +121,7 @@ func TestClient_AddRecord(t *testing.T) { Content: "txttxttxt", TTL: 600, } - resp, err := client.AddRecord(context.Background(), "example.com", "test", "2565", testRecord) + resp, err := client.AddRecord(t.Context(), "example.com", "test", "2565", testRecord) require.NoError(t, err) expected := &AddRecord{ @@ -150,20 +149,20 @@ func TestClient_AddRecord_error(t *testing.T) { TTL: 600, } - _, err := client.AddRecord(context.Background(), "example.com", "test", "2565", testRecord) + _, err := client.AddRecord(t.Context(), "example.com", "test", "2565", testRecord) require.ErrorIs(t, err, APIError{Code: 402, Status: "error", Message: "Invalid user."}) } func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, "delete-record.json") - err := client.DeleteRecord(context.Background(), "example.com", "test", "example.com 600 IN TXT txttxttxt", "2374") + err := client.DeleteRecord(t.Context(), "example.com", "test", "example.com 600 IN TXT txttxttxt", "2374") require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { client := setupTest(t, "error.json") - err := client.DeleteRecord(context.Background(), "example.com", "test", "example.com 600 IN TXT txttxttxt", "2374") + err := client.DeleteRecord(t.Context(), "example.com", "test", "example.com 600 IN TXT txttxttxt", "2374") require.ErrorIs(t, err, APIError{Code: 402, Status: "error", Message: "Invalid user."}) } diff --git a/providers/dns/bunny/bunny.go b/providers/dns/bunny/bunny.go index c5bfcb173..1489d1c5e 100644 --- a/providers/dns/bunny/bunny.go +++ b/providers/dns/bunny/bunny.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/internal/ptr" - "github.com/miekg/dns" "github.com/nrdcg/bunny-go" "golang.org/x/net/publicsuffix" ) @@ -200,16 +199,14 @@ func findZone(zones *bunny.DNSZones, domain string) *bunny.DNSZone { func possibleDomains(domain string) []string { var domains []string - labelIndexes := dns.Split(domain) - - for _, index := range labelIndexes { - tld, _ := publicsuffix.PublicSuffix(domain) - if tld == domain[index:] { + tld, _ := publicsuffix.PublicSuffix(domain) + for d := range dns01.DomainsSeq(domain) { + if tld == d { // skip the TLD break } - domains = append(domains, dns01.UnFqdn(domain[index:])) + domains = append(domains, dns01.UnFqdn(d)) } return domains diff --git a/providers/dns/checkdomain/internal/client_test.go b/providers/dns/checkdomain/internal/client_test.go index 3f6a7e7a7..60d55ee5e 100644 --- a/providers/dns/checkdomain/internal/client_test.go +++ b/providers/dns/checkdomain/internal/client_test.go @@ -2,7 +2,6 @@ package internal import ( "bytes" - "context" "encoding/json" "fmt" "io" @@ -67,7 +66,7 @@ func TestClient_GetDomainIDByName(t *testing.T) { } }) - id, err := client.GetDomainIDByName(context.Background(), "test.com") + id, err := client.GetDomainIDByName(t.Context(), "test.com") require.NoError(t, err) assert.Equal(t, 1, id) @@ -103,7 +102,7 @@ func TestClient_CheckNameservers(t *testing.T) { } }) - err := client.CheckNameservers(context.Background(), 1) + err := client.CheckNameservers(t.Context(), 1) require.NoError(t, err) } @@ -141,7 +140,7 @@ func TestClient_CreateRecord(t *testing.T) { Value: "value", } - err := client.CreateRecord(context.Background(), 1, record) + err := client.CreateRecord(t.Context(), 1, record) require.NoError(t, err) } @@ -256,6 +255,6 @@ func TestClient_DeleteTXTRecord(t *testing.T) { }) info := dns01.GetChallengeInfo(domainName, "abc") - err := client.DeleteTXTRecord(context.Background(), 1, info.EffectiveFQDN, recordValue) + err := client.DeleteTXTRecord(t.Context(), 1, info.EffectiveFQDN, recordValue) require.NoError(t, err) } diff --git a/providers/dns/clouddns/internal/client_test.go b/providers/dns/clouddns/internal/client_test.go index 2a4891cce..2dee0bd0f 100644 --- a/providers/dns/clouddns/internal/client_test.go +++ b/providers/dns/clouddns/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "encoding/json" "net/http" "net/http/httptest" @@ -62,7 +61,7 @@ func TestClient_AddRecord(t *testing.T) { } }) - err := client.AddRecord(context.Background(), "example.com", "_acme-challenge.example.com", "txt") + err := client.AddRecord(t.Context(), "example.com", "_acme-challenge.example.com", "txt") require.NoError(t, err) } @@ -124,7 +123,7 @@ func TestClient_DeleteRecord(t *testing.T) { } }) - ctx, err := client.CreateAuthenticatedContext(context.Background()) + ctx, err := client.CreateAuthenticatedContext(t.Context()) require.NoError(t, err) err = client.DeleteRecord(ctx, "example.com", "_acme-challenge.example.com") diff --git a/providers/dns/clouddns/internal/identity_test.go b/providers/dns/clouddns/internal/identity_test.go index 3c727448d..a3f3f55ea 100644 --- a/providers/dns/clouddns/internal/identity_test.go +++ b/providers/dns/clouddns/internal/identity_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "encoding/json" "net/http" "testing" @@ -35,7 +34,7 @@ func TestClient_CreateAuthenticatedContext(t *testing.T) { } }) - ctx, err := client.CreateAuthenticatedContext(context.Background()) + ctx, err := client.CreateAuthenticatedContext(t.Context()) require.NoError(t, err) at := getAccessToken(ctx) diff --git a/providers/dns/cloudns/internal/client_test.go b/providers/dns/cloudns/internal/client_test.go index 8c29bc6ec..e5d10b089 100644 --- a/providers/dns/cloudns/internal/client_test.go +++ b/providers/dns/cloudns/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -134,7 +133,7 @@ func TestClient_GetZone(t *testing.T) { t.Run(test.desc, func(t *testing.T) { client := setupTest(t, "", handlerMock(http.MethodGet, []byte(test.apiResponse))) - zone, err := client.GetZone(context.Background(), test.authFQDN) + zone, err := client.GetZone(t.Context(), test.authFQDN) if test.expected.errorMsg != "" { require.EqualError(t, err, test.expected.errorMsg) @@ -241,7 +240,7 @@ func TestClient_FindTxtRecord(t *testing.T) { t.Run(test.desc, func(t *testing.T) { client := setupTest(t, "", handlerMock(http.MethodGet, []byte(test.apiResponse))) - txtRecord, err := client.FindTxtRecord(context.Background(), test.zoneName, test.authFQDN) + txtRecord, err := client.FindTxtRecord(t.Context(), test.zoneName, test.authFQDN) if test.expected.errorMsg != "" { require.EqualError(t, err, test.expected.errorMsg) @@ -350,7 +349,7 @@ func TestClient_ListTxtRecord(t *testing.T) { t.Run(test.desc, func(t *testing.T) { client := setupTest(t, "", handlerMock(http.MethodGet, []byte(test.apiResponse))) - txtRecords, err := client.ListTxtRecords(context.Background(), test.zoneName, test.authFQDN) + txtRecords, err := client.ListTxtRecords(t.Context(), test.zoneName, test.authFQDN) if test.expected.errorMsg != "" { require.EqualError(t, err, test.expected.errorMsg) @@ -455,7 +454,7 @@ func TestClient_AddTxtRecord(t *testing.T) { handlerMock(http.MethodPost, []byte(test.apiResponse))(rw, req) }) - err := client.AddTxtRecord(context.Background(), test.zoneName, test.authFQDN, test.value, test.ttl) + err := client.AddTxtRecord(t.Context(), test.zoneName, test.authFQDN, test.value, test.ttl) if test.expected.errorMsg != "" { require.EqualError(t, err, test.expected.errorMsg) @@ -528,7 +527,7 @@ func TestClient_RemoveTxtRecord(t *testing.T) { client.BaseURL, _ = url.Parse(server.URL) - err = client.RemoveTxtRecord(context.Background(), test.id, test.zoneName) + err = client.RemoveTxtRecord(t.Context(), test.id, test.zoneName) if test.expected.errorMsg != "" { require.EqualError(t, err, test.expected.errorMsg) @@ -598,7 +597,7 @@ func TestClient_GetUpdateStatus(t *testing.T) { client.BaseURL, _ = url.Parse(server.URL) - syncProgress, err := client.GetUpdateStatus(context.Background(), test.zoneName) + syncProgress, err := client.GetUpdateStatus(t.Context(), test.zoneName) if test.expected.errorMsg != "" { require.EqualError(t, err, test.expected.errorMsg) diff --git a/providers/dns/cloudru/internal/client_test.go b/providers/dns/cloudru/internal/client_test.go index d96183d9f..21e227f76 100644 --- a/providers/dns/cloudru/internal/client_test.go +++ b/providers/dns/cloudru/internal/client_test.go @@ -58,7 +58,7 @@ func writeFixtureHandler(method, filename string) http.HandlerFunc { func TestClient_GetZones(t *testing.T) { client := setupTest(t, "/zones", writeFixtureHandler(http.MethodGet, "zones.json")) - ctx := mockContext() + ctx := mockContext(t) zones, err := client.GetZones(ctx, "xxx") require.NoError(t, err) @@ -80,7 +80,7 @@ func TestClient_GetZones(t *testing.T) { func TestClient_GetRecords(t *testing.T) { client := setupTest(t, "/zones/zzz/records", writeFixtureHandler(http.MethodGet, "records.json")) - ctx := mockContext() + ctx := mockContext(t) records, err := client.GetRecords(ctx, "zzz") require.NoError(t, err) @@ -124,7 +124,7 @@ func TestClient_GetRecords(t *testing.T) { func TestClient_CreateRecord(t *testing.T) { client := setupTest(t, "/zones/zzz/records", writeFixtureHandler(http.MethodPost, "record.json")) - ctx := mockContext() + ctx := mockContext(t) recordReq := Record{ Name: "www.example.com.", @@ -152,7 +152,7 @@ func TestClient_CreateRecord(t *testing.T) { func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, "/zones/zzz/records/example.com/TXT", writeFixtureHandler(http.MethodDelete, "record.json")) - ctx := mockContext() + ctx := mockContext(t) err := client.DeleteRecord(ctx, "zzz", "example.com", "TXT") require.NoError(t, err) diff --git a/providers/dns/cloudru/internal/identity_test.go b/providers/dns/cloudru/internal/identity_test.go index 7329e7f55..68dbd90cd 100644 --- a/providers/dns/cloudru/internal/identity_test.go +++ b/providers/dns/cloudru/internal/identity_test.go @@ -13,8 +13,10 @@ import ( "github.com/stretchr/testify/require" ) -func mockContext() context.Context { - return context.WithValue(context.Background(), tokenKey, &Token{AccessToken: "xxx"}) +func mockContext(t *testing.T) context.Context { + t.Helper() + + return context.WithValue(t.Context(), tokenKey, &Token{AccessToken: "xxx"}) } func tokenHandler(rw http.ResponseWriter, req *http.Request) { @@ -60,7 +62,7 @@ func TestClient_obtainToken(t *testing.T) { assert.Nil(t, client.token) - tok, err := client.obtainToken(context.Background()) + tok, err := client.obtainToken(t.Context()) require.NoError(t, err) assert.NotNil(t, tok) @@ -81,7 +83,7 @@ func TestClient_CreateAuthenticatedContext(t *testing.T) { assert.Nil(t, client.token) - ctx, err := client.CreateAuthenticatedContext(context.Background()) + ctx, err := client.CreateAuthenticatedContext(t.Context()) require.NoError(t, err) tok := getToken(ctx) diff --git a/providers/dns/conoha/internal/client.go b/providers/dns/conoha/internal/client.go index a1487c9d7..60d7fd6dc 100644 --- a/providers/dns/conoha/internal/client.go +++ b/providers/dns/conoha/internal/client.go @@ -25,7 +25,7 @@ type Client struct { } // NewClient returns a client instance logged into the ConoHa service. -func NewClient(region string, token string) (*Client, error) { +func NewClient(region, token string) (*Client, error) { baseURL, err := url.Parse(fmt.Sprintf(dnsServiceBaseURL, region)) if err != nil { return nil, err diff --git a/providers/dns/conoha/internal/client_test.go b/providers/dns/conoha/internal/client_test.go index bc27ec212..0cabb30dd 100644 --- a/providers/dns/conoha/internal/client_test.go +++ b/providers/dns/conoha/internal/client_test.go @@ -2,7 +2,6 @@ package internal import ( "bytes" - "context" "fmt" "io" "net/http" @@ -106,7 +105,7 @@ func TestClient_GetDomainID(t *testing.T) { mux.Handle("/v1/domains", test.handler) - domainID, err := client.GetDomainID(context.Background(), test.domainName) + domainID, err := client.GetDomainID(t.Context(), test.domainName) if test.expected.error { require.Error(t, err) @@ -177,7 +176,7 @@ func TestClient_CreateRecord(t *testing.T) { TTL: 300, } - err := client.CreateRecord(context.Background(), domainID, record) + err := client.CreateRecord(t.Context(), domainID, record) test.assert(t, err) }) } @@ -189,7 +188,7 @@ func TestClient_GetRecordID(t *testing.T) { mux.HandleFunc("/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records", writeFixtureHandler(http.MethodGet, "domains-records_GET.json")) - recordID, err := client.GetRecordID(context.Background(), "89acac79-38e7-497d-807c-a011e1310438", "www.example.com.", "A", "15.185.172.153") + recordID, err := client.GetRecordID(t.Context(), "89acac79-38e7-497d-807c-a011e1310438", "www.example.com.", "A", "15.185.172.153") require.NoError(t, err) assert.Equal(t, "2e32e609-3a4f-45ba-bdef-e50eacd345ad", recordID) @@ -207,6 +206,6 @@ func TestClient_DeleteRecord(t *testing.T) { rw.WriteHeader(http.StatusOK) }) - err := client.DeleteRecord(context.Background(), "89acac79-38e7-497d-807c-a011e1310438", "2e32e609-3a4f-45ba-bdef-e50eacd345ad") + err := client.DeleteRecord(t.Context(), "89acac79-38e7-497d-807c-a011e1310438", "2e32e609-3a4f-45ba-bdef-e50eacd345ad") require.NoError(t, err) } diff --git a/providers/dns/conoha/internal/identity_test.go b/providers/dns/conoha/internal/identity_test.go index 027c7f2c7..77db51f09 100644 --- a/providers/dns/conoha/internal/identity_test.go +++ b/providers/dns/conoha/internal/identity_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "net/http" "net/http/httptest" "net/url" @@ -32,7 +31,7 @@ func TestNewClient(t *testing.T) { }, } - token, err := identifier.GetToken(context.Background(), auth) + token, err := identifier.GetToken(t.Context(), auth) require.NoError(t, err) expected := &IdentityResponse{Access: Access{Token: Token{ID: "sample00d88246078f2bexample788f7"}}} diff --git a/providers/dns/conohav3/internal/client.go b/providers/dns/conohav3/internal/client.go index 9fac8f366..fcbd7f5ac 100644 --- a/providers/dns/conohav3/internal/client.go +++ b/providers/dns/conohav3/internal/client.go @@ -25,7 +25,7 @@ type Client struct { } // NewClient returns a client instance logged into the ConoHa service. -func NewClient(region string, token string) (*Client, error) { +func NewClient(region, token string) (*Client, error) { baseURL, err := url.Parse(fmt.Sprintf(dnsServiceBaseURL, region)) if err != nil { return nil, err diff --git a/providers/dns/conohav3/internal/client_test.go b/providers/dns/conohav3/internal/client_test.go index 171e7ba2f..9600b2f06 100644 --- a/providers/dns/conohav3/internal/client_test.go +++ b/providers/dns/conohav3/internal/client_test.go @@ -2,7 +2,6 @@ package internal import ( "bytes" - "context" "fmt" "io" "net/http" @@ -106,7 +105,7 @@ func TestClient_GetDomainID(t *testing.T) { mux.Handle("/v1/domains", test.handler) - domainID, err := client.GetDomainID(context.Background(), test.domainName) + domainID, err := client.GetDomainID(t.Context(), test.domainName) if test.expected.error { require.Error(t, err) @@ -177,7 +176,7 @@ func TestClient_CreateRecord(t *testing.T) { TTL: 300, } - err := client.CreateRecord(context.Background(), domainID, record) + err := client.CreateRecord(t.Context(), domainID, record) test.assert(t, err) }) } @@ -189,7 +188,7 @@ func TestClient_GetRecordID(t *testing.T) { mux.HandleFunc("/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records", writeFixtureHandler(http.MethodGet, "domains-records_GET.json")) - recordID, err := client.GetRecordID(context.Background(), "89acac79-38e7-497d-807c-a011e1310438", "www.example.com.", "A", "15.185.172.153") + recordID, err := client.GetRecordID(t.Context(), "89acac79-38e7-497d-807c-a011e1310438", "www.example.com.", "A", "15.185.172.153") require.NoError(t, err) assert.Equal(t, "2e32e609-3a4f-45ba-bdef-e50eacd345ad", recordID) @@ -207,6 +206,6 @@ func TestClient_DeleteRecord(t *testing.T) { rw.WriteHeader(http.StatusOK) }) - err := client.DeleteRecord(context.Background(), "89acac79-38e7-497d-807c-a011e1310438", "2e32e609-3a4f-45ba-bdef-e50eacd345ad") + err := client.DeleteRecord(t.Context(), "89acac79-38e7-497d-807c-a011e1310438", "2e32e609-3a4f-45ba-bdef-e50eacd345ad") require.NoError(t, err) } diff --git a/providers/dns/conohav3/internal/identity_test.go b/providers/dns/conohav3/internal/identity_test.go index 97a1e7e7e..d5222c05d 100644 --- a/providers/dns/conohav3/internal/identity_test.go +++ b/providers/dns/conohav3/internal/identity_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "net/http" "net/http/httptest" "net/url" @@ -45,7 +44,7 @@ func TestGetToken_HeaderToken(t *testing.T) { }, } - token, err := identifier.GetToken(context.Background(), auth) + token, err := identifier.GetToken(t.Context(), auth) require.NoError(t, err) assert.Equal(t, "sample-header-token-123", token) diff --git a/providers/dns/constellix/internal/domains_test.go b/providers/dns/constellix/internal/domains_test.go index 1b0779b3d..f6ade9d31 100644 --- a/providers/dns/constellix/internal/domains_test.go +++ b/providers/dns/constellix/internal/domains_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "io" "net/http" "net/http/httptest" @@ -48,7 +47,7 @@ func TestDomainService_GetAll(t *testing.T) { } }) - data, err := client.Domains.GetAll(context.Background(), nil) + data, err := client.Domains.GetAll(t.Context(), nil) require.NoError(t, err) expected := []Domain{ @@ -84,7 +83,7 @@ func TestDomainService_Search(t *testing.T) { } }) - data, err := client.Domains.Search(context.Background(), Exact, "lego.wtf") + data, err := client.Domains.Search(t.Context(), Exact, "lego.wtf") require.NoError(t, err) expected := []Domain{ diff --git a/providers/dns/constellix/internal/txtrecords_test.go b/providers/dns/constellix/internal/txtrecords_test.go index 7adc4af5c..ee4d20bf2 100644 --- a/providers/dns/constellix/internal/txtrecords_test.go +++ b/providers/dns/constellix/internal/txtrecords_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "encoding/json" "io" "net/http" @@ -35,7 +34,7 @@ func TestTxtRecordService_Create(t *testing.T) { } }) - records, err := client.TxtRecords.Create(context.Background(), 12345, RecordRequest{}) + records, err := client.TxtRecords.Create(t.Context(), 12345, RecordRequest{}) require.NoError(t, err) recordsJSON, err := json.Marshal(records) @@ -70,7 +69,7 @@ func TestTxtRecordService_GetAll(t *testing.T) { } }) - records, err := client.TxtRecords.GetAll(context.Background(), 12345) + records, err := client.TxtRecords.GetAll(t.Context(), 12345) require.NoError(t, err) recordsJSON, err := json.Marshal(records) @@ -105,7 +104,7 @@ func TestTxtRecordService_Get(t *testing.T) { } }) - record, err := client.TxtRecords.Get(context.Background(), 12345, 6789) + record, err := client.TxtRecords.Get(t.Context(), 12345, 6789) require.NoError(t, err) expected := &Record{ @@ -146,7 +145,7 @@ func TestTxtRecordService_Update(t *testing.T) { } }) - msg, err := client.TxtRecords.Update(context.Background(), 12345, 6789, RecordRequest{}) + msg, err := client.TxtRecords.Update(t.Context(), 12345, 6789, RecordRequest{}) require.NoError(t, err) expected := &SuccessMessage{Success: "Record updated successfully"} @@ -169,7 +168,7 @@ func TestTxtRecordService_Delete(t *testing.T) { } }) - msg, err := client.TxtRecords.Delete(context.Background(), 12345, 6789) + msg, err := client.TxtRecords.Delete(t.Context(), 12345, 6789) require.NoError(t, err) expected := &SuccessMessage{Success: "Record deleted successfully"} @@ -199,7 +198,7 @@ func TestTxtRecordService_Search(t *testing.T) { } }) - records, err := client.TxtRecords.Search(context.Background(), 12345, Exact, "test") + records, err := client.TxtRecords.Search(t.Context(), 12345, Exact, "test") require.NoError(t, err) recordsJSON, err := json.Marshal(records) diff --git a/providers/dns/corenetworks/internal/client_test.go b/providers/dns/corenetworks/internal/client_test.go index 0fff0d5ae..ec6de452e 100644 --- a/providers/dns/corenetworks/internal/client_test.go +++ b/providers/dns/corenetworks/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -92,7 +91,7 @@ func TestClient_CreateAuthenticationToken(t *testing.T) { mux.HandleFunc("/auth/token", testHandlerAuth(http.MethodPost, http.StatusOK, "auth.json")) - ctx := context.Background() + ctx := t.Context() token, err := client.CreateAuthenticationToken(ctx) require.NoError(t, err) @@ -109,7 +108,7 @@ func TestClient_ListZone(t *testing.T) { mux.HandleFunc("/dnszones/", testHandler(http.MethodGet, http.StatusOK, "ListZone.json")) - ctx := context.Background() + ctx := t.Context() zones, err := client.ListZone(ctx) require.NoError(t, err) @@ -127,7 +126,7 @@ func TestClient_GetZoneDetails(t *testing.T) { mux.HandleFunc("/dnszones/example.com", testHandler(http.MethodGet, http.StatusOK, "GetZoneDetails.json")) - ctx := context.Background() + ctx := t.Context() zone, err := client.GetZoneDetails(ctx, "example.com") require.NoError(t, err) @@ -147,7 +146,7 @@ func TestClient_ListRecords(t *testing.T) { mux.HandleFunc("/dnszones/example.com/records/", testHandler(http.MethodGet, http.StatusOK, "ListRecords.json")) - ctx := context.Background() + ctx := t.Context() records, err := client.ListRecords(ctx, "example.com") require.NoError(t, err) @@ -181,7 +180,7 @@ func TestClient_AddRecord(t *testing.T) { mux.HandleFunc("/dnszones/example.com/records/", testHandler(http.MethodPost, http.StatusNoContent, "")) - ctx := context.Background() + ctx := t.Context() record := Record{Name: "www", TTL: 3600, Type: "A", Data: "127.0.0.1"} @@ -194,7 +193,7 @@ func TestClient_DeleteRecords(t *testing.T) { mux.HandleFunc("/dnszones/example.com/records/delete", testHandler(http.MethodPost, http.StatusNoContent, "")) - ctx := context.Background() + ctx := t.Context() record := Record{Name: "www", Type: "A", Data: "127.0.0.1"} @@ -207,7 +206,7 @@ func TestClient_CommitRecords(t *testing.T) { mux.HandleFunc("/dnszones/example.com/records/commit", testHandler(http.MethodPost, http.StatusNoContent, "")) - ctx := context.Background() + ctx := t.Context() err := client.CommitRecords(ctx, "example.com") require.NoError(t, err) diff --git a/providers/dns/cpanel/internal/cpanel/client.go b/providers/dns/cpanel/internal/cpanel/client.go index 3bca6b521..f0ababd9f 100644 --- a/providers/dns/cpanel/internal/cpanel/client.go +++ b/providers/dns/cpanel/internal/cpanel/client.go @@ -24,7 +24,7 @@ type Client struct { HTTPClient *http.Client } -func NewClient(baseURL string, username string, token string) (*Client, error) { +func NewClient(baseURL, username, token string) (*Client, error) { apiEndpoint, err := url.Parse(baseURL) if err != nil { return nil, err diff --git a/providers/dns/cpanel/internal/cpanel/client_test.go b/providers/dns/cpanel/internal/cpanel/client_test.go index 8516259d6..78c45e82d 100644 --- a/providers/dns/cpanel/internal/cpanel/client_test.go +++ b/providers/dns/cpanel/internal/cpanel/client_test.go @@ -1,7 +1,6 @@ package cpanel import ( - "context" "fmt" "io" "net/http" @@ -15,7 +14,7 @@ import ( "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, filename string) *Client { +func setupTest(t *testing.T, pattern, filename string) *Client { t.Helper() mux := http.NewServeMux() @@ -55,7 +54,7 @@ func setupTest(t *testing.T, pattern string, filename string) *Client { func TestClient_FetchZoneInformation(t *testing.T) { client := setupTest(t, "/execute/DNS/parse_zone", "zone-info.json") - zoneInfo, err := client.FetchZoneInformation(context.Background(), "example.com") + zoneInfo, err := client.FetchZoneInformation(t.Context(), "example.com") require.NoError(t, err) expected := []shared.ZoneRecord{{ @@ -73,7 +72,7 @@ func TestClient_FetchZoneInformation(t *testing.T) { func TestClient_FetchZoneInformation_error(t *testing.T) { client := setupTest(t, "/execute/DNS/parse_zone", "zone-info_error.json") - zoneInfo, err := client.FetchZoneInformation(context.Background(), "example.com") + zoneInfo, err := client.FetchZoneInformation(t.Context(), "example.com") require.Error(t, err) assert.Nil(t, zoneInfo) @@ -89,7 +88,7 @@ func TestClient_AddRecord(t *testing.T) { Data: []string{"string1", "string2"}, } - zoneSerial, err := client.AddRecord(context.Background(), 123456, "example.com", record) + zoneSerial, err := client.AddRecord(t.Context(), 123456, "example.com", record) require.NoError(t, err) expected := &shared.ZoneSerial{NewSerial: "2021031903"} @@ -107,7 +106,7 @@ func TestClient_AddRecord_error(t *testing.T) { Data: []string{"string1", "string2"}, } - zoneSerial, err := client.AddRecord(context.Background(), 123456, "example.com", record) + zoneSerial, err := client.AddRecord(t.Context(), 123456, "example.com", record) require.Error(t, err) assert.Nil(t, zoneSerial) @@ -124,7 +123,7 @@ func TestClient_EditRecord(t *testing.T) { Data: []string{"string1", "string2"}, } - zoneSerial, err := client.EditRecord(context.Background(), 123456, "example.com", record) + zoneSerial, err := client.EditRecord(t.Context(), 123456, "example.com", record) require.NoError(t, err) expected := &shared.ZoneSerial{NewSerial: "2021031903"} @@ -143,7 +142,7 @@ func TestClient_EditRecord_error(t *testing.T) { Data: []string{"string1", "string2"}, } - zoneSerial, err := client.EditRecord(context.Background(), 123456, "example.com", record) + zoneSerial, err := client.EditRecord(t.Context(), 123456, "example.com", record) require.Error(t, err) assert.Nil(t, zoneSerial) @@ -152,7 +151,7 @@ func TestClient_EditRecord_error(t *testing.T) { func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, "/execute/DNS/mass_edit_zone", "update-zone.json") - zoneSerial, err := client.DeleteRecord(context.Background(), 123456, "example.com", 0) + zoneSerial, err := client.DeleteRecord(t.Context(), 123456, "example.com", 0) require.NoError(t, err) expected := &shared.ZoneSerial{NewSerial: "2021031903"} @@ -163,7 +162,7 @@ func TestClient_DeleteRecord(t *testing.T) { func TestClient_DeleteRecord_error(t *testing.T) { client := setupTest(t, "/execute/DNS/mass_edit_zone", "update-zone_error.json") - zoneSerial, err := client.DeleteRecord(context.Background(), 123456, "example.com", 0) + zoneSerial, err := client.DeleteRecord(t.Context(), 123456, "example.com", 0) require.Error(t, err) assert.Nil(t, zoneSerial) diff --git a/providers/dns/cpanel/internal/whm/client.go b/providers/dns/cpanel/internal/whm/client.go index d375b83e3..41b29388c 100644 --- a/providers/dns/cpanel/internal/whm/client.go +++ b/providers/dns/cpanel/internal/whm/client.go @@ -24,7 +24,7 @@ type Client struct { HTTPClient *http.Client } -func NewClient(baseURL string, username string, token string) (*Client, error) { +func NewClient(baseURL, username, token string) (*Client, error) { apiEndpoint, err := url.Parse(baseURL) if err != nil { return nil, err diff --git a/providers/dns/cpanel/internal/whm/client_test.go b/providers/dns/cpanel/internal/whm/client_test.go index f4f6d7b19..536417666 100644 --- a/providers/dns/cpanel/internal/whm/client_test.go +++ b/providers/dns/cpanel/internal/whm/client_test.go @@ -1,7 +1,6 @@ package whm import ( - "context" "fmt" "io" "net/http" @@ -15,7 +14,7 @@ import ( "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, filename string) *Client { +func setupTest(t *testing.T, pattern, filename string) *Client { t.Helper() mux := http.NewServeMux() @@ -55,7 +54,7 @@ func setupTest(t *testing.T, pattern string, filename string) *Client { func TestClient_FetchZoneInformation(t *testing.T) { client := setupTest(t, "/json-api/parse_dns_zone", "zone-info.json") - zoneInfo, err := client.FetchZoneInformation(context.Background(), "example.com") + zoneInfo, err := client.FetchZoneInformation(t.Context(), "example.com") require.NoError(t, err) expected := []shared.ZoneRecord{{ @@ -73,7 +72,7 @@ func TestClient_FetchZoneInformation(t *testing.T) { func TestClient_FetchZoneInformation_error(t *testing.T) { client := setupTest(t, "/json-api/parse_dns_zone", "zone-info_error.json") - zoneInfo, err := client.FetchZoneInformation(context.Background(), "example.com") + zoneInfo, err := client.FetchZoneInformation(t.Context(), "example.com") require.Error(t, err) assert.Nil(t, zoneInfo) @@ -89,7 +88,7 @@ func TestClient_AddRecord(t *testing.T) { Data: []string{"string1", "string2"}, } - zoneSerial, err := client.AddRecord(context.Background(), 123456, "example.com", record) + zoneSerial, err := client.AddRecord(t.Context(), 123456, "example.com", record) require.NoError(t, err) expected := &shared.ZoneSerial{NewSerial: "2021031903"} @@ -107,7 +106,7 @@ func TestClient_AddRecord_error(t *testing.T) { Data: []string{"string1", "string2"}, } - zoneSerial, err := client.AddRecord(context.Background(), 123456, "example.com", record) + zoneSerial, err := client.AddRecord(t.Context(), 123456, "example.com", record) require.Error(t, err) assert.Nil(t, zoneSerial) @@ -124,7 +123,7 @@ func TestClient_EditRecord(t *testing.T) { Data: []string{"string1", "string2"}, } - zoneSerial, err := client.EditRecord(context.Background(), 123456, "example.com", record) + zoneSerial, err := client.EditRecord(t.Context(), 123456, "example.com", record) require.NoError(t, err) expected := &shared.ZoneSerial{NewSerial: "2021031903"} @@ -143,7 +142,7 @@ func TestClient_EditRecord_error(t *testing.T) { Data: []string{"string1", "string2"}, } - zoneSerial, err := client.EditRecord(context.Background(), 123456, "example.com", record) + zoneSerial, err := client.EditRecord(t.Context(), 123456, "example.com", record) require.Error(t, err) assert.Nil(t, zoneSerial) @@ -152,7 +151,7 @@ func TestClient_EditRecord_error(t *testing.T) { func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, "/json-api/mass_edit_dns_zone", "update-zone.json") - zoneSerial, err := client.DeleteRecord(context.Background(), 123456, "example.com", 0) + zoneSerial, err := client.DeleteRecord(t.Context(), 123456, "example.com", 0) require.NoError(t, err) expected := &shared.ZoneSerial{NewSerial: "2021031903"} @@ -163,7 +162,7 @@ func TestClient_DeleteRecord(t *testing.T) { func TestClient_DeleteRecord_error(t *testing.T) { client := setupTest(t, "/json-api/mass_edit_dns_zone", "update-zone_error.json") - zoneSerial, err := client.DeleteRecord(context.Background(), 123456, "example.com", 0) + zoneSerial, err := client.DeleteRecord(t.Context(), 123456, "example.com", 0) require.Error(t, err) assert.Nil(t, zoneSerial) diff --git a/providers/dns/derak/internal/client.go b/providers/dns/derak/internal/client.go index 3e7c76fdb..ea24388b2 100644 --- a/providers/dns/derak/internal/client.go +++ b/providers/dns/derak/internal/client.go @@ -61,7 +61,7 @@ func (c Client) GetRecords(ctx context.Context, zoneID string, params *GetRecord } // GetRecord gets a record by ID. -func (c Client) GetRecord(ctx context.Context, zoneID string, recordID string) (*Record, error) { +func (c Client) GetRecord(ctx context.Context, zoneID, recordID string) (*Record, error) { endpoint := c.baseURL.JoinPath("zones", zoneID, "dnsrecords", recordID) req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -97,7 +97,7 @@ func (c Client) CreateRecord(ctx context.Context, zoneID string, record Record) } // EditRecord edits an existing record. -func (c Client) EditRecord(ctx context.Context, zoneID string, recordID string, record Record) (*Record, error) { +func (c Client) EditRecord(ctx context.Context, zoneID, recordID string, record Record) (*Record, error) { endpoint := c.baseURL.JoinPath("zones", zoneID, "dnsrecords", recordID) req, err := newJSONRequest(ctx, http.MethodPatch, endpoint, record) @@ -115,7 +115,7 @@ func (c Client) EditRecord(ctx context.Context, zoneID string, recordID string, } // DeleteRecord deletes an existing record. -func (c Client) DeleteRecord(ctx context.Context, zoneID string, recordID string) error { +func (c Client) DeleteRecord(ctx context.Context, zoneID, recordID string) error { endpoint := c.baseURL.JoinPath("zones", zoneID, "dnsrecords", recordID) req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) diff --git a/providers/dns/derak/internal/client_test.go b/providers/dns/derak/internal/client_test.go index 3d542e4a7..20dea0015 100644 --- a/providers/dns/derak/internal/client_test.go +++ b/providers/dns/derak/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -77,7 +76,7 @@ func TestGetRecords(t *testing.T) { mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords", testHandler(http.MethodGet, http.StatusOK, "records-GET.json")) - records, err := client.GetRecords(context.Background(), "47c0ecf6c91243308c649ad1d2d618dd", &GetRecordsParameters{DNSType: "TXT", Content: `"test"'`}) + records, err := client.GetRecords(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", &GetRecordsParameters{DNSType: "TXT", Content: `"test"'`}) require.NoError(t, err) excepted := &GetRecordsResponse{Data: []Record{ @@ -140,7 +139,7 @@ func TestGetRecords_error(t *testing.T) { mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords", testHandler(http.MethodGet, http.StatusUnauthorized, "error.json")) - _, err := client.GetRecords(context.Background(), "47c0ecf6c91243308c649ad1d2d618dd", &GetRecordsParameters{DNSType: "TXT", Content: `"test"'`}) + _, err := client.GetRecords(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", &GetRecordsParameters{DNSType: "TXT", Content: `"test"'`}) require.Error(t, err) } @@ -150,7 +149,7 @@ func TestGetRecord(t *testing.T) { mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/812bee17a0b440b0bd5ee099a78b839c", testHandler(http.MethodGet, http.StatusOK, "record-GET.json")) - record, err := client.GetRecord(context.Background(), "47c0ecf6c91243308c649ad1d2d618dd", "812bee17a0b440b0bd5ee099a78b839c") + record, err := client.GetRecord(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", "812bee17a0b440b0bd5ee099a78b839c") require.NoError(t, err) excepted := &Record{ @@ -169,7 +168,7 @@ func TestGetRecord_error(t *testing.T) { mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/812bee17a0b440b0bd5ee099a78b839c", testHandler(http.MethodGet, http.StatusUnauthorized, "error.json")) - _, err := client.GetRecord(context.Background(), "47c0ecf6c91243308c649ad1d2d618dd", "812bee17a0b440b0bd5ee099a78b839c") + _, err := client.GetRecord(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", "812bee17a0b440b0bd5ee099a78b839c") require.Error(t, err) } @@ -186,7 +185,7 @@ func TestCreateRecord(t *testing.T) { TTL: 120, } - record, err := client.CreateRecord(context.Background(), "47c0ecf6c91243308c649ad1d2d618dd", r) + record, err := client.CreateRecord(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", r) require.NoError(t, err) excepted := &Record{ @@ -212,7 +211,7 @@ func TestCreateRecord_error(t *testing.T) { TTL: 120, } - _, err := client.CreateRecord(context.Background(), "47c0ecf6c91243308c649ad1d2d618dd", r) + _, err := client.CreateRecord(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", r) require.Error(t, err) } @@ -222,7 +221,7 @@ func TestEditRecord(t *testing.T) { mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/eebc813de2f94d67b09d91e10e2d65c2", testHandler(http.MethodPatch, http.StatusOK, "record-PATCH.json")) - record, err := client.EditRecord(context.Background(), "47c0ecf6c91243308c649ad1d2d618dd", "eebc813de2f94d67b09d91e10e2d65c2", Record{ + record, err := client.EditRecord(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", "eebc813de2f94d67b09d91e10e2d65c2", Record{ Content: "foo", }) require.NoError(t, err) @@ -243,7 +242,7 @@ func TestEditRecord_error(t *testing.T) { mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/eebc813de2f94d67b09d91e10e2d65c2", testHandler(http.MethodPatch, http.StatusUnauthorized, "error.json")) - _, err := client.EditRecord(context.Background(), "47c0ecf6c91243308c649ad1d2d618dd", "eebc813de2f94d67b09d91e10e2d65c2", Record{ + _, err := client.EditRecord(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", "eebc813de2f94d67b09d91e10e2d65c2", Record{ Content: "foo", }) require.Error(t, err) @@ -255,7 +254,7 @@ func TestDeleteRecord(t *testing.T) { mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/653464211b7447a1bee6b8fcb9fb86df", testHandler(http.MethodDelete, http.StatusOK, "record-DELETE.json")) - err := client.DeleteRecord(context.Background(), "47c0ecf6c91243308c649ad1d2d618dd", "653464211b7447a1bee6b8fcb9fb86df") + err := client.DeleteRecord(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", "653464211b7447a1bee6b8fcb9fb86df") require.NoError(t, err) } @@ -265,7 +264,7 @@ func TestDeleteRecord_error(t *testing.T) { mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/653464211b7447a1bee6b8fcb9fb86df", testHandler(http.MethodDelete, http.StatusUnauthorized, "error.json")) - err := client.DeleteRecord(context.Background(), "47c0ecf6c91243308c649ad1d2d618dd", "653464211b7447a1bee6b8fcb9fb86df") + err := client.DeleteRecord(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", "653464211b7447a1bee6b8fcb9fb86df") require.Error(t, err) } @@ -274,7 +273,7 @@ func TestGetZones(t *testing.T) { mux.HandleFunc("/", testHandler(http.MethodGet, http.StatusOK, "service-cdn-zones.json")) - zones, err := client.GetZones(context.Background()) + zones, err := client.GetZones(t.Context()) require.NoError(t, err) excepted := []Zone{{ @@ -307,6 +306,6 @@ func TestGetZones_error(t *testing.T) { mux.HandleFunc("/", testHandler(http.MethodGet, http.StatusUnauthorized, "error.json")) - _, err := client.GetZones(context.Background()) + _, err := client.GetZones(t.Context()) require.Error(t, err) } diff --git a/providers/dns/digitalocean/internal/client_test.go b/providers/dns/digitalocean/internal/client_test.go index 081e1a109..171601438 100644 --- a/providers/dns/digitalocean/internal/client_test.go +++ b/providers/dns/digitalocean/internal/client_test.go @@ -2,7 +2,6 @@ package internal import ( "bytes" - "context" "fmt" "io" "net/http" @@ -98,7 +97,7 @@ func TestClient_AddTxtRecord(t *testing.T) { TTL: 30, } - newRecord, err := client.AddTxtRecord(context.Background(), "example.com", record) + newRecord, err := client.AddTxtRecord(t.Context(), "example.com", record) require.NoError(t, err) expected := &TxtRecordResponse{DomainRecord: Record{ @@ -134,6 +133,6 @@ func TestClient_RemoveTxtRecord(t *testing.T) { rw.WriteHeader(http.StatusNoContent) }) - err := client.RemoveTxtRecord(context.Background(), "example.com", 1234567) + err := client.RemoveTxtRecord(t.Context(), "example.com", 1234567) require.NoError(t, err) } diff --git a/providers/dns/directadmin/internal/client_test.go b/providers/dns/directadmin/internal/client_test.go index ded4769e3..6da73da65 100644 --- a/providers/dns/directadmin/internal/client_test.go +++ b/providers/dns/directadmin/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "encoding/json" "fmt" "io" @@ -92,7 +91,7 @@ func TestClient_SetRecord(t *testing.T) { TTL: 123, } - err := client.SetRecord(context.Background(), "example.com", record) + err := client.SetRecord(t.Context(), "example.com", record) require.NoError(t, err) } @@ -110,7 +109,7 @@ func TestClient_SetRecord_error(t *testing.T) { TTL: 123, } - err := client.SetRecord(context.Background(), "example.com", record) + err := client.SetRecord(t.Context(), "example.com", record) require.EqualError(t, err, "[status code 500] Cannot View Dns Record: OOPS") } @@ -133,7 +132,7 @@ func TestClient_DeleteRecord(t *testing.T) { Value: "txtTXTtxt", } - err := client.DeleteRecord(context.Background(), "example.com", record) + err := client.DeleteRecord(t.Context(), "example.com", record) require.NoError(t, err) } @@ -150,6 +149,6 @@ func TestClient_DeleteRecord_error(t *testing.T) { Value: "txtTXTtxt", } - err := client.DeleteRecord(context.Background(), "example.com", record) + err := client.DeleteRecord(t.Context(), "example.com", record) require.EqualError(t, err, "[status code 500] Cannot View Dns Record: OOPS") } diff --git a/providers/dns/dnshomede/dnshomede.go b/providers/dns/dnshomede/dnshomede.go index 1b81be744..91b0b11e3 100644 --- a/providers/dns/dnshomede/dnshomede.go +++ b/providers/dns/dnshomede/dnshomede.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "net/http" - "strings" "time" "github.com/go-acme/lego/v4/challenge/dns01" @@ -62,9 +61,9 @@ func NewDNSProvider() (*DNSProvider, error) { return nil, fmt.Errorf("dnshomede: %w", err) } - credentials, err := parseCredentials(values[EnvCredentials]) + credentials, err := env.ParsePairs(values[EnvCredentials]) if err != nil { - return nil, fmt.Errorf("dnshomede: %w", err) + return nil, fmt.Errorf("dnshomede: credentials: %w", err) } config.Credentials = credentials @@ -131,19 +130,3 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { func (d *DNSProvider) Sequential() time.Duration { return d.config.SequenceInterval } - -func parseCredentials(raw string) (map[string]string, error) { - credentials := make(map[string]string) - - credStrings := strings.Split(strings.TrimSuffix(raw, ","), ",") - for _, credPair := range credStrings { - data := strings.Split(credPair, ":") - if len(data) != 2 { - return nil, fmt.Errorf("invalid credential pair: %q", credPair) - } - - credentials[strings.TrimSpace(data[0])] = strings.TrimSpace(data[1]) - } - - return credentials, nil -} diff --git a/providers/dns/dnshomede/dnshomede_test.go b/providers/dns/dnshomede/dnshomede_test.go index 6b79912e8..bdb42f172 100644 --- a/providers/dns/dnshomede/dnshomede_test.go +++ b/providers/dns/dnshomede/dnshomede_test.go @@ -34,7 +34,7 @@ func TestNewDNSProvider(t *testing.T) { envVars: map[string]string{ EnvCredentials: ",", }, - expected: `dnshomede: invalid credential pair: ""`, + expected: `dnshomede: credentials: incorrect pair: `, }, { desc: "missing password", @@ -55,7 +55,7 @@ func TestNewDNSProvider(t *testing.T) { envVars: map[string]string{ EnvCredentials: "example.org:123,example.net", }, - expected: `dnshomede: invalid credential pair: "example.net"`, + expected: "dnshomede: credentials: incorrect pair: example.net", }, { desc: "missing credentials", diff --git a/providers/dns/dnshomede/internal/client_test.go b/providers/dns/dnshomede/internal/client_test.go index e6f2c1b7d..710e2c72e 100644 --- a/providers/dns/dnshomede/internal/client_test.go +++ b/providers/dns/dnshomede/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -31,7 +30,7 @@ func TestClient_Add(t *testing.T) { client := setupTest(t, map[string]string{"example.org": "secret"}, handlerMock(addAction, txtValue)) - err := client.Add(context.Background(), "example.org", txtValue) + err := client.Add(t.Context(), "example.org", txtValue) require.NoError(t, err) } @@ -40,7 +39,7 @@ func TestClient_Add_error(t *testing.T) { client := setupTest(t, map[string]string{"example.com": "secret"}, handlerMock(addAction, txtValue)) - err := client.Add(context.Background(), "example.org", txtValue) + err := client.Add(t.Context(), "example.org", txtValue) require.Error(t, err) } @@ -49,7 +48,7 @@ func TestClient_Remove(t *testing.T) { client := setupTest(t, map[string]string{"example.org": "secret"}, handlerMock(removeAction, txtValue)) - err := client.Remove(context.Background(), "example.org", txtValue) + err := client.Remove(t.Context(), "example.org", txtValue) require.NoError(t, err) } @@ -58,7 +57,7 @@ func TestClient_Remove_error(t *testing.T) { client := setupTest(t, map[string]string{"example.com": "secret"}, handlerMock(removeAction, txtValue)) - err := client.Remove(context.Background(), "example.org", txtValue) + err := client.Remove(t.Context(), "example.org", txtValue) require.Error(t, err) } diff --git a/providers/dns/dode/internal/client_test.go b/providers/dns/dode/internal/client_test.go index 116ca8c4c..139a0939a 100644 --- a/providers/dns/dode/internal/client_test.go +++ b/providers/dns/dode/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -81,13 +80,13 @@ func setupTest(t *testing.T, method, pattern string, status int, file string) *C func TestClient_UpdateTxtRecord(t *testing.T) { client := setupTest(t, http.MethodGet, "/letsencrypt", http.StatusOK, "success.json") - err := client.UpdateTxtRecord(context.Background(), "example.com.", "value", false) + err := client.UpdateTxtRecord(t.Context(), "example.com.", "value", false) require.NoError(t, err) } func TestClient_UpdateTxtRecord_clear(t *testing.T) { client := setupTest(t, http.MethodGet, "/letsencrypt", http.StatusOK, "success.json") - err := client.UpdateTxtRecord(context.Background(), "example.com.", "value", true) + err := client.UpdateTxtRecord(t.Context(), "example.com.", "value", true) require.NoError(t, err) } diff --git a/providers/dns/domeneshop/internal/client.go b/providers/dns/domeneshop/internal/client.go index b7ebb9940..9ab964222 100644 --- a/providers/dns/domeneshop/internal/client.go +++ b/providers/dns/domeneshop/internal/client.go @@ -72,7 +72,7 @@ func (c *Client) GetDomainByName(ctx context.Context, domain string) (*Domain, e // CreateTXTRecord creates a TXT record with the provided host (subdomain) and data. // https://api.domeneshop.no/docs/#tag/dns/paths/~1domains~1{domainId}~1dns/post -func (c *Client) CreateTXTRecord(ctx context.Context, domain *Domain, host string, data string) error { +func (c *Client) CreateTXTRecord(ctx context.Context, domain *Domain, host, data string) error { endpoint := c.baseURL.JoinPath("domains", strconv.Itoa(domain.ID), "dns") record := DNSRecord{ @@ -92,7 +92,7 @@ func (c *Client) CreateTXTRecord(ctx context.Context, domain *Domain, host strin // DeleteTXTRecord deletes the DNS record matching the provided host and data. // https://api.domeneshop.no/docs/#tag/dns/paths/~1domains~1{domainId}~1dns~1{recordId}/delete -func (c *Client) DeleteTXTRecord(ctx context.Context, domain *Domain, host string, data string) error { +func (c *Client) DeleteTXTRecord(ctx context.Context, domain *Domain, host, data string) error { record, err := c.getDNSRecordByHostData(ctx, *domain, host, data) if err != nil { return err @@ -110,7 +110,7 @@ func (c *Client) DeleteTXTRecord(ctx context.Context, domain *Domain, host strin // getDNSRecordByHostData finds the first matching DNS record with the provided host and data. // https://api.domeneshop.no/docs/#operation/getDnsRecords -func (c *Client) getDNSRecordByHostData(ctx context.Context, domain Domain, host string, data string) (*DNSRecord, error) { +func (c *Client) getDNSRecordByHostData(ctx context.Context, domain Domain, host, data string) (*DNSRecord, error) { endpoint := c.baseURL.JoinPath("domains", strconv.Itoa(domain.ID), "dns") req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) diff --git a/providers/dns/domeneshop/internal/client_test.go b/providers/dns/domeneshop/internal/client_test.go index 71205cac4..1f4265d03 100644 --- a/providers/dns/domeneshop/internal/client_test.go +++ b/providers/dns/domeneshop/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "net/http" "net/http/httptest" "net/url" @@ -45,7 +44,7 @@ func TestClient_CreateTXTRecord(t *testing.T) { _, _ = rw.Write([]byte(`{"id": 1}`)) }) - err := client.CreateTXTRecord(context.Background(), &Domain{ID: 1}, "example", "txtTXTtxt") + err := client.CreateTXTRecord(t.Context(), &Domain{ID: 1}, "example", "txtTXTtxt") require.NoError(t, err) } @@ -88,7 +87,7 @@ func TestClient_DeleteTXTRecord(t *testing.T) { } }) - err := client.DeleteTXTRecord(context.Background(), &Domain{ID: 1}, "example.com", "txtTXTtxt") + err := client.DeleteTXTRecord(t.Context(), &Domain{ID: 1}, "example.com", "txtTXTtxt") require.NoError(t, err) } @@ -118,7 +117,7 @@ func TestClient_getDNSRecordByHostData(t *testing.T) { ]`)) }) - record, err := client.getDNSRecordByHostData(context.Background(), Domain{ID: 1}, "example.com", "txtTXTtxt") + record, err := client.getDNSRecordByHostData(t.Context(), Domain{ID: 1}, "example.com", "txtTXTtxt") require.NoError(t, err) expected := &DNSRecord{ @@ -171,7 +170,7 @@ func TestClient_GetDomainByName(t *testing.T) { ]`)) }) - domain, err := client.GetDomainByName(context.Background(), "example.com") + domain, err := client.GetDomainByName(t.Context(), "example.com") require.NoError(t, err) expected := &Domain{ diff --git a/providers/dns/dyn/internal/client.go b/providers/dns/dyn/internal/client.go index 43981cc44..83a1bfc0f 100644 --- a/providers/dns/dyn/internal/client.go +++ b/providers/dns/dyn/internal/client.go @@ -28,7 +28,7 @@ type Client struct { } // NewClient Creates a new Client. -func NewClient(customerName string, username string, password string) *Client { +func NewClient(customerName, username, password string) *Client { baseURL, _ := url.Parse(defaultBaseURL) return &Client{ diff --git a/providers/dns/dyn/internal/client_test.go b/providers/dns/dyn/internal/client_test.go index 87bee1cd3..c6cdff9d5 100644 --- a/providers/dns/dyn/internal/client_test.go +++ b/providers/dns/dyn/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -103,20 +102,20 @@ func unauthenticatedHandler(method string, status int, file string) http.Handler func TestClient_Publish(t *testing.T) { client := setupTest(t, "/Zone/example.com", unauthenticatedHandler(http.MethodPut, http.StatusOK, "publish.json")) - err := client.Publish(context.Background(), "example.com", "my message") + err := client.Publish(t.Context(), "example.com", "my message") require.NoError(t, err) } func TestClient_AddTXTRecord(t *testing.T) { client := setupTest(t, "/TXTRecord/example.com/example.com.", unauthenticatedHandler(http.MethodPost, http.StatusCreated, "create-txt-record.json")) - err := client.AddTXTRecord(context.Background(), "example.com", "example.com.", "txt", 120) + err := client.AddTXTRecord(t.Context(), "example.com", "example.com.", "txt", 120) require.NoError(t, err) } func TestClient_RemoveTXTRecord(t *testing.T) { client := setupTest(t, "/TXTRecord/example.com/example.com.", unauthenticatedHandler(http.MethodDelete, http.StatusOK, "")) - err := client.RemoveTXTRecord(context.Background(), "example.com", "example.com.") + err := client.RemoveTXTRecord(t.Context(), "example.com", "example.com.") require.NoError(t, err) } diff --git a/providers/dns/dyn/internal/session_test.go b/providers/dns/dyn/internal/session_test.go index 76d5bef4e..5a939f40c 100644 --- a/providers/dns/dyn/internal/session_test.go +++ b/providers/dns/dyn/internal/session_test.go @@ -9,14 +9,16 @@ import ( "github.com/stretchr/testify/require" ) -func mockContext() context.Context { - return context.WithValue(context.Background(), tokenKey, "tok") +func mockContext(t *testing.T) context.Context { + t.Helper() + + return context.WithValue(t.Context(), tokenKey, "tok") } func TestClient_login(t *testing.T) { client := setupTest(t, "/Session", unauthenticatedHandler(http.MethodPost, http.StatusOK, "login.json")) - sess, err := client.login(context.Background()) + sess, err := client.login(t.Context()) require.NoError(t, err) expected := session{Token: "tok", Version: "456"} @@ -27,14 +29,14 @@ func TestClient_login(t *testing.T) { func TestClient_Logout(t *testing.T) { client := setupTest(t, "/Session", authenticatedHandler(http.MethodDelete, http.StatusOK, "")) - err := client.Logout(mockContext()) + err := client.Logout(mockContext(t)) require.NoError(t, err) } func TestClient_CreateAuthenticatedContext(t *testing.T) { client := setupTest(t, "/Session", unauthenticatedHandler(http.MethodPost, http.StatusOK, "login.json")) - ctx, err := client.CreateAuthenticatedContext(context.Background()) + ctx, err := client.CreateAuthenticatedContext(t.Context()) require.NoError(t, err) at := getToken(ctx) diff --git a/providers/dns/dyndnsfree/internal/client_test.go b/providers/dns/dyndnsfree/internal/client_test.go index 17ac89b0f..206022d5c 100644 --- a/providers/dns/dyndnsfree/internal/client_test.go +++ b/providers/dns/dyndnsfree/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "net/http" "net/http/httptest" "testing" @@ -46,13 +45,13 @@ func setupTest(t *testing.T, message string) *Client { func TestAddTXTRecord(t *testing.T) { client := setupTest(t, "success") - err := client.AddTXTRecord(context.Background(), "example.com", "sub.example.com", "value") + err := client.AddTXTRecord(t.Context(), "example.com", "sub.example.com", "value") require.NoError(t, err) } func TestAddTXTRecord_error(t *testing.T) { client := setupTest(t, "error: authentification failed") - err := client.AddTXTRecord(context.Background(), "example.com", "sub.example.com", "value") + err := client.AddTXTRecord(t.Context(), "example.com", "sub.example.com", "value") require.EqualError(t, err, "error: authentification failed") } diff --git a/providers/dns/dynu/internal/client_test.go b/providers/dns/dynu/internal/client_test.go index 7f33bc2c0..4f3a16be9 100644 --- a/providers/dns/dynu/internal/client_test.go +++ b/providers/dns/dynu/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -97,7 +96,7 @@ func TestGetRootDomain(t *testing.T) { client := setupTest(t, http.MethodGet, test.pattern, test.status, test.file) - domain, err := client.GetRootDomain(context.Background(), "test.lego.freeddns.org") + domain, err := client.GetRootDomain(t.Context(), "test.lego.freeddns.org") if test.expected.error != "" { assert.EqualError(t, err, test.expected.error) @@ -185,7 +184,7 @@ func TestGetRecords(t *testing.T) { client := setupTest(t, http.MethodGet, test.pattern, test.status, test.file) - records, err := client.GetRecords(context.Background(), "_acme-challenge.lego.freeddns.org", "TXT") + records, err := client.GetRecords(t.Context(), "_acme-challenge.lego.freeddns.org", "TXT") if test.expected.error != "" { assert.EqualError(t, err, test.expected.error) @@ -245,7 +244,7 @@ func TestAddNewRecord(t *testing.T) { TTL: 300, } - err := client.AddNewRecord(context.Background(), 9007481, record) + err := client.AddNewRecord(t.Context(), 9007481, record) if test.expected.error != "" { assert.EqualError(t, err, test.expected.error) @@ -292,7 +291,7 @@ func TestDeleteRecord(t *testing.T) { client := setupTest(t, http.MethodDelete, test.pattern, test.status, test.file) - err := client.DeleteRecord(context.Background(), 9007481, 6041418) + err := client.DeleteRecord(t.Context(), 9007481, 6041418) if test.expected.error != "" { assert.EqualError(t, err, test.expected.error) diff --git a/providers/dns/easydns/internal/client.go b/providers/dns/easydns/internal/client.go index 3568eeea5..c044d7e7f 100644 --- a/providers/dns/easydns/internal/client.go +++ b/providers/dns/easydns/internal/client.go @@ -26,7 +26,7 @@ type Client struct { } // NewClient Creates a new Client. -func NewClient(token string, key string) *Client { +func NewClient(token, key string) *Client { baseURL, _ := url.Parse(DefaultBaseURL) return &Client{ diff --git a/providers/dns/easydns/internal/client_test.go b/providers/dns/easydns/internal/client_test.go index 030b28f34..02d46a5a7 100644 --- a/providers/dns/easydns/internal/client_test.go +++ b/providers/dns/easydns/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -70,7 +69,7 @@ func setupTest(t *testing.T, method, pattern string, status int, file string) *C func TestClient_ListZones(t *testing.T) { client := setupTest(t, http.MethodGet, "/zones/records/all/example.com", http.StatusOK, "list-zone.json") - zones, err := client.ListZones(context.Background(), "example.com") + zones, err := client.ListZones(t.Context(), "example.com") require.NoError(t, err) expected := []ZoneRecord{{ @@ -90,7 +89,7 @@ func TestClient_ListZones(t *testing.T) { func TestClient_ListZones_error(t *testing.T) { client := setupTest(t, http.MethodGet, "/zones/records/all/example.com", http.StatusOK, "error1.json") - _, err := client.ListZones(context.Background(), "example.com") + _, err := client.ListZones(t.Context(), "example.com") require.EqualError(t, err, "code 420: Enhance Your Calm. Rate limit exceeded (too many requests) OR you did NOT provide any credentials with your request!") } @@ -106,7 +105,7 @@ func TestClient_AddRecord(t *testing.T) { Priority: "0", } - recordID, err := client.AddRecord(context.Background(), "example.com", record) + recordID, err := client.AddRecord(t.Context(), "example.com", record) require.NoError(t, err) assert.Equal(t, "xxx", recordID) @@ -124,13 +123,13 @@ func TestClient_AddRecord_error(t *testing.T) { Priority: "0", } - _, err := client.AddRecord(context.Background(), "example.com", record) + _, err := client.AddRecord(t.Context(), "example.com", record) require.EqualError(t, err, "code 420: Enhance Your Calm. Rate limit exceeded (too many requests) OR you did NOT provide any credentials with your request!") } func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, http.MethodDelete, "/zones/records/example.com/xxx", http.StatusOK, "") - err := client.DeleteRecord(context.Background(), "example.com", "xxx") + err := client.DeleteRecord(t.Context(), "example.com", "xxx") require.NoError(t, err) } diff --git a/providers/dns/efficientip/internal/client.go b/providers/dns/efficientip/internal/client.go index 2fea76a13..fb6ee185b 100644 --- a/providers/dns/efficientip/internal/client.go +++ b/providers/dns/efficientip/internal/client.go @@ -22,7 +22,7 @@ type Client struct { password string } -func NewClient(hostname string, username string, password string) *Client { +func NewClient(hostname, username, password string) *Client { baseURL, _ := url.Parse(fmt.Sprintf("https://%s/rest/", hostname)) return &Client{ diff --git a/providers/dns/efficientip/internal/client_test.go b/providers/dns/efficientip/internal/client_test.go index a766c9085..137f2628c 100644 --- a/providers/dns/efficientip/internal/client_test.go +++ b/providers/dns/efficientip/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -72,7 +71,7 @@ func setupTest(t *testing.T, method, pattern string, status int, file string) *C func TestListRecords(t *testing.T) { client := setupTest(t, http.MethodGet, "/dns_rr_list", http.StatusOK, "dns_rr_list.json") - ctx := context.Background() + ctx := t.Context() records, err := client.ListRecords(ctx) require.NoError(t, err) @@ -339,7 +338,7 @@ func TestListRecords(t *testing.T) { func TestGetRecord(t *testing.T) { client := setupTest(t, http.MethodGet, "/dns_rr_info", http.StatusOK, "dns_rr_info.json") - ctx := context.Background() + ctx := t.Context() record, err := client.GetRecord(ctx, "239") require.NoError(t, err) @@ -386,7 +385,7 @@ func TestGetRecord(t *testing.T) { func TestAddRecord(t *testing.T) { client := setupTest(t, http.MethodPost, "/dns_rr_add", http.StatusCreated, "dns_rr_add.json") - ctx := context.Background() + ctx := t.Context() r := ResourceRecord{ RRName: "test.example.com", @@ -407,7 +406,7 @@ func TestAddRecord(t *testing.T) { func TestDeleteRecord(t *testing.T) { client := setupTest(t, http.MethodDelete, "/dns_rr_delete", http.StatusOK, "dns_rr_delete.json") - ctx := context.Background() + ctx := t.Context() resp, err := client.DeleteRecord(ctx, DeleteInputParameters{RRID: "251"}) require.NoError(t, err) @@ -420,7 +419,7 @@ func TestDeleteRecord(t *testing.T) { func TestDeleteRecord_error(t *testing.T) { client := setupTest(t, http.MethodDelete, "/dns_rr_delete", http.StatusBadRequest, "dns_rr_delete-error.json") - ctx := context.Background() + ctx := t.Context() _, err := client.DeleteRecord(ctx, DeleteInputParameters{RRID: "251"}) require.ErrorAs(t, err, &APIError{}) diff --git a/providers/dns/epik/internal/client.go b/providers/dns/epik/internal/client.go index 9a5385453..640085065 100644 --- a/providers/dns/epik/internal/client.go +++ b/providers/dns/epik/internal/client.go @@ -77,7 +77,7 @@ func (c Client) CreateHostRecord(ctx context.Context, domain string, record Reco // RemoveHostRecord removes a record for a domain. // https://docs.userapi.epik.com/v2/#/DNS%20Host%20Records/removeHostRecord -func (c Client) RemoveHostRecord(ctx context.Context, domain string, recordID string) (*Data, error) { +func (c Client) RemoveHostRecord(ctx context.Context, domain, recordID string) (*Data, error) { params := url.Values{} params.Set("ID", recordID) diff --git a/providers/dns/epik/internal/client_test.go b/providers/dns/epik/internal/client_test.go index 78c4452f0..b23862207 100644 --- a/providers/dns/epik/internal/client_test.go +++ b/providers/dns/epik/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -34,7 +33,7 @@ func TestClient_GetDNSRecords(t *testing.T) { mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodGet, http.StatusOK, "getDnsRecord.json")) - records, err := client.GetDNSRecords(context.Background(), "example.com") + records, err := client.GetDNSRecords(t.Context(), "example.com") require.NoError(t, err) expected := []Record{ @@ -93,7 +92,7 @@ func TestClient_GetDNSRecords_error(t *testing.T) { mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodGet, http.StatusUnauthorized, "error.json")) - _, err := client.GetDNSRecords(context.Background(), "example.com") + _, err := client.GetDNSRecords(t.Context(), "example.com") require.Error(t, err) } @@ -110,7 +109,7 @@ func TestClient_CreateHostRecord(t *testing.T) { TTL: 300, } - data, err := client.CreateHostRecord(context.Background(), "example.com", record) + data, err := client.CreateHostRecord(t.Context(), "example.com", record) require.NoError(t, err) expected := &Data{ @@ -134,7 +133,7 @@ func TestClient_CreateHostRecord_error(t *testing.T) { TTL: 300, } - _, err := client.CreateHostRecord(context.Background(), "example.com", record) + _, err := client.CreateHostRecord(t.Context(), "example.com", record) require.Error(t, err) } @@ -143,7 +142,7 @@ func TestClient_RemoveHostRecord(t *testing.T) { mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodDelete, http.StatusOK, "removeHostRecord.json")) - data, err := client.RemoveHostRecord(context.Background(), "example.com", "abc123") + data, err := client.RemoveHostRecord(t.Context(), "example.com", "abc123") require.NoError(t, err) expected := &Data{ @@ -159,7 +158,7 @@ func TestClient_RemoveHostRecord_error(t *testing.T) { mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodDelete, http.StatusUnauthorized, "error.json")) - _, err := client.RemoveHostRecord(context.Background(), "example.com", "abc123") + _, err := client.RemoveHostRecord(t.Context(), "example.com", "abc123") require.Error(t, err) } diff --git a/providers/dns/exec/log_mock_test.go b/providers/dns/exec/log_mock_test.go index 47935cc55..65753dcf8 100644 --- a/providers/dns/exec/log_mock_test.go +++ b/providers/dns/exec/log_mock_test.go @@ -6,26 +6,26 @@ type LogRecorder struct { mock.Mock } -func (*LogRecorder) Fatal(args ...interface{}) { +func (*LogRecorder) Fatal(args ...any) { panic("implement me") } -func (*LogRecorder) Fatalln(args ...interface{}) { +func (*LogRecorder) Fatalln(args ...any) { panic("implement me") } -func (*LogRecorder) Fatalf(format string, args ...interface{}) { +func (*LogRecorder) Fatalf(format string, args ...any) { panic("implement me") } -func (*LogRecorder) Print(args ...interface{}) { +func (*LogRecorder) Print(args ...any) { panic("implement me") } -func (l *LogRecorder) Println(args ...interface{}) { +func (l *LogRecorder) Println(args ...any) { l.Called(args...) } -func (*LogRecorder) Printf(format string, args ...interface{}) { +func (*LogRecorder) Printf(format string, args ...any) { panic("implement me") } diff --git a/providers/dns/exoscale/exoscale.go b/providers/dns/exoscale/exoscale.go index 8fd39eaaf..1a5f358f5 100644 --- a/providers/dns/exoscale/exoscale.go +++ b/providers/dns/exoscale/exoscale.go @@ -207,7 +207,7 @@ func (d *DNSProvider) findExistingZone(zoneName string) (*egoscale.DNSDomain, er // findExistingRecordID Query Exoscale to find an existing record for this name. // Returns empty result if no record could be found. -func (d *DNSProvider) findExistingRecordID(zoneID egoscale.UUID, recordName string, value string) (egoscale.UUID, error) { +func (d *DNSProvider) findExistingRecordID(zoneID egoscale.UUID, recordName, value string) (egoscale.UUID, error) { ctx := context.Background() records, err := d.client.ListDNSDomainRecords(ctx, zoneID) diff --git a/providers/dns/f5xc/internal/client.go b/providers/dns/f5xc/internal/client.go index 620336c88..26eb03db5 100644 --- a/providers/dns/f5xc/internal/client.go +++ b/providers/dns/f5xc/internal/client.go @@ -27,7 +27,7 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(apiToken string, tenantName string) (*Client, error) { +func NewClient(apiToken, tenantName string) (*Client, error) { if apiToken == "" { return nil, errors.New("credentials missing") } diff --git a/providers/dns/f5xc/internal/client_test.go b/providers/dns/f5xc/internal/client_test.go index ca9ac2950..7b53a3bce 100644 --- a/providers/dns/f5xc/internal/client_test.go +++ b/providers/dns/f5xc/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "io" "net/http" "net/http/httptest" @@ -64,7 +63,7 @@ func TestClient_Create(t *testing.T) { }, } - result, err := client.CreateRRSet(context.Background(), "example.com", "groupA", rrSet) + result, err := client.CreateRRSet(t.Context(), "example.com", "groupA", rrSet) require.NoError(t, err) expected := &APIRRSet{ @@ -94,14 +93,14 @@ func TestClient_Create_error(t *testing.T) { }, } - _, err := client.CreateRRSet(context.Background(), "example.com", "groupA", rrSet) + _, err := client.CreateRRSet(t.Context(), "example.com", "groupA", rrSet) require.Error(t, err) } func TestClient_Get(t *testing.T) { client := setupTest(t, "GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusOK, "get.json") - result, err := client.GetRRSet(context.Background(), "example.com", "groupA", "www", "TXT") + result, err := client.GetRRSet(t.Context(), "example.com", "groupA", "www", "TXT") require.NoError(t, err) expected := &APIRRSet{ @@ -125,7 +124,7 @@ func TestClient_Get(t *testing.T) { func TestClient_Get_not_found(t *testing.T) { client := setupTest(t, "GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusNotFound, "error_404.json") - result, err := client.GetRRSet(context.Background(), "example.com", "groupA", "www", "TXT") + result, err := client.GetRRSet(t.Context(), "example.com", "groupA", "www", "TXT") require.NoError(t, err) assert.Nil(t, result) @@ -134,14 +133,14 @@ func TestClient_Get_not_found(t *testing.T) { func TestClient_Get_error(t *testing.T) { client := setupTest(t, "GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusBadRequest, "") - _, err := client.GetRRSet(context.Background(), "example.com", "groupA", "www", "TXT") + _, err := client.GetRRSet(t.Context(), "example.com", "groupA", "www", "TXT") require.Error(t, err) } func TestClient_Delete(t *testing.T) { client := setupTest(t, "DELETE /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusOK, "get.json") - result, err := client.DeleteRRSet(context.Background(), "example.com", "groupA", "www", "TXT") + result, err := client.DeleteRRSet(t.Context(), "example.com", "groupA", "www", "TXT") require.NoError(t, err) expected := &APIRRSet{ @@ -165,7 +164,7 @@ func TestClient_Delete(t *testing.T) { func TestClient_Delete_error(t *testing.T) { client := setupTest(t, "DELETE /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusBadRequest, "") - _, err := client.DeleteRRSet(context.Background(), "example.com", "groupA", "www", "TXT") + _, err := client.DeleteRRSet(t.Context(), "example.com", "groupA", "www", "TXT") require.Error(t, err) } @@ -181,7 +180,7 @@ func TestClient_Replace(t *testing.T) { }, } - result, err := client.ReplaceRRSet(context.Background(), "example.com", "groupA", "www", "TXT", rrSet) + result, err := client.ReplaceRRSet(t.Context(), "example.com", "groupA", "www", "TXT", rrSet) require.NoError(t, err) expected := &APIRRSet{ @@ -214,6 +213,6 @@ func TestClient_Replace_error(t *testing.T) { }, } - _, err := client.ReplaceRRSet(context.Background(), "example.com", "groupA", "www", "TXT", rrSet) + _, err := client.ReplaceRRSet(t.Context(), "example.com", "groupA", "www", "TXT", rrSet) require.Error(t, err) } diff --git a/providers/dns/gcloud/googlecloud_test.go b/providers/dns/gcloud/googlecloud_test.go index b42eab8c2..15c61556c 100644 --- a/providers/dns/gcloud/googlecloud_test.go +++ b/providers/dns/gcloud/googlecloud_test.go @@ -1,6 +1,7 @@ package gcloud import ( + "context" "encoding/json" "fmt" "net/http" @@ -11,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/platform/tester" "github.com/stretchr/testify/require" - "golang.org/x/net/context" "golang.org/x/oauth2/google" "google.golang.org/api/dns/v1" ) diff --git a/providers/dns/gcore/internal/client.go b/providers/dns/gcore/internal/client.go index 085b4d6cb..b76da4388 100644 --- a/providers/dns/gcore/internal/client.go +++ b/providers/dns/gcore/internal/client.go @@ -116,7 +116,7 @@ func (c *Client) updateRRSet(ctx context.Context, zone, name string, record RRSe return c.doRequest(ctx, http.MethodPut, endpoint, record, nil) } -func (c *Client) doRequest(ctx context.Context, method string, endpoint *url.URL, bodyParams any, result any) error { +func (c *Client) doRequest(ctx context.Context, method string, endpoint *url.URL, bodyParams, result any) error { req, err := newJSONRequest(ctx, method, endpoint, bodyParams) if err != nil { return fmt.Errorf("new request: %w", err) diff --git a/providers/dns/gcore/internal/client_test.go b/providers/dns/gcore/internal/client_test.go index f414b33e1..21bdb8e05 100644 --- a/providers/dns/gcore/internal/client_test.go +++ b/providers/dns/gcore/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "encoding/json" "fmt" "net/http" @@ -44,7 +43,7 @@ func TestClient_GetZone(t *testing.T) { next: handleJSONResponse(expected), }) - zone, err := client.GetZone(context.Background(), "example.com") + zone, err := client.GetZone(t.Context(), "example.com") require.NoError(t, err) assert.Equal(t, expected, zone) @@ -58,7 +57,7 @@ func TestClient_GetZone_error(t *testing.T) { next: handleAPIError(), }) - _, err := client.GetZone(context.Background(), "example.com") + _, err := client.GetZone(t.Context(), "example.com") require.Error(t, err) } @@ -77,7 +76,7 @@ func TestClient_GetRRSet(t *testing.T) { next: handleJSONResponse(expected), }) - rrSet, err := client.GetRRSet(context.Background(), "example.com", "foo.example.com") + rrSet, err := client.GetRRSet(t.Context(), "example.com", "foo.example.com") require.NoError(t, err) assert.Equal(t, expected, rrSet) @@ -91,7 +90,7 @@ func TestClient_GetRRSet_error(t *testing.T) { next: handleAPIError(), }) - _, err := client.GetRRSet(context.Background(), "example.com", "foo.example.com") + _, err := client.GetRRSet(t.Context(), "example.com", "foo.example.com") require.Error(t, err) } @@ -101,7 +100,7 @@ func TestClient_DeleteRRSet(t *testing.T) { mux.Handle("/v2/zones/test.example.com/my.test.example.com/"+txtRecordType, validationHandler{method: http.MethodDelete}) - err := client.DeleteRRSet(context.Background(), "test.example.com", "my.test.example.com.") + err := client.DeleteRRSet(t.Context(), "test.example.com", "my.test.example.com.") require.NoError(t, err) } @@ -113,7 +112,7 @@ func TestClient_DeleteRRSet_error(t *testing.T) { next: handleAPIError(), }) - err := client.DeleteRRSet(context.Background(), "test.example.com", "my.test.example.com.") + err := client.DeleteRRSet(t.Context(), "test.example.com", "my.test.example.com.") require.NoError(t, err) } @@ -183,7 +182,7 @@ func TestClient_AddRRSet(t *testing.T) { mux.Handle(pattern, handler) } - err := cl.AddRRSet(context.Background(), test.zone, test.recordName, test.value, testTTL) + err := cl.AddRRSet(t.Context(), test.zone, test.recordName, test.value, testTTL) if test.wantErr { require.Error(t, err) return @@ -223,7 +222,7 @@ func handleAPIError() http.HandlerFunc { } } -func handleJSONResponse(data interface{}) http.HandlerFunc { +func handleJSONResponse(data any) http.HandlerFunc { return func(rw http.ResponseWriter, req *http.Request) { err := json.NewEncoder(rw).Encode(data) if err != nil { diff --git a/providers/dns/glesys/internal/client.go b/providers/dns/glesys/internal/client.go index 038c6f0d5..20bc363ba 100644 --- a/providers/dns/glesys/internal/client.go +++ b/providers/dns/glesys/internal/client.go @@ -24,7 +24,7 @@ type Client struct { HTTPClient *http.Client } -func NewClient(apiUser string, apiKey string) *Client { +func NewClient(apiUser, apiKey string) *Client { baseURL, _ := url.Parse(defaultBaseURL) return &Client{ diff --git a/providers/dns/glesys/internal/client_test.go b/providers/dns/glesys/internal/client_test.go index 7e8ca9724..ab30f9516 100644 --- a/providers/dns/glesys/internal/client_test.go +++ b/providers/dns/glesys/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -65,7 +64,7 @@ func setupTest(t *testing.T, method, pattern string, status int, file string) *C func TestClient_AddTXTRecord(t *testing.T) { client := setupTest(t, http.MethodPost, "/domain/addrecord", http.StatusOK, "add-record.json") - recordID, err := client.AddTXTRecord(context.Background(), "example.com", "foo", "txt", 120) + recordID, err := client.AddTXTRecord(t.Context(), "example.com", "foo", "txt", 120) require.NoError(t, err) assert.Equal(t, 123, recordID) @@ -74,6 +73,6 @@ func TestClient_AddTXTRecord(t *testing.T) { func TestClient_DeleteTXTRecord(t *testing.T) { client := setupTest(t, http.MethodPost, "/domain/deleterecord", http.StatusOK, "delete-record.json") - err := client.DeleteTXTRecord(context.Background(), 123) + err := client.DeleteTXTRecord(t.Context(), 123) require.NoError(t, err) } diff --git a/providers/dns/godaddy/internal/client.go b/providers/dns/godaddy/internal/client.go index 1902fc1fd..bf30d437f 100644 --- a/providers/dns/godaddy/internal/client.go +++ b/providers/dns/godaddy/internal/client.go @@ -26,7 +26,7 @@ type Client struct { HTTPClient *http.Client } -func NewClient(apiKey string, apiSecret string) *Client { +func NewClient(apiKey, apiSecret string) *Client { baseURL, _ := url.Parse(DefaultBaseURL) return &Client{ diff --git a/providers/dns/godaddy/internal/client_test.go b/providers/dns/godaddy/internal/client_test.go index 50d193bdb..64bf2b388 100644 --- a/providers/dns/godaddy/internal/client_test.go +++ b/providers/dns/godaddy/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -34,7 +33,7 @@ func TestClient_GetRecords(t *testing.T) { mux.HandleFunc("/v1/domains/example.com/records/TXT/", testHandler(http.MethodGet, http.StatusOK, "getrecords.json")) - records, err := client.GetRecords(context.Background(), "example.com", "TXT", "") + records, err := client.GetRecords(t.Context(), "example.com", "TXT", "") require.NoError(t, err) expected := []DNSRecord{ @@ -54,7 +53,7 @@ func TestClient_GetRecords_errors(t *testing.T) { mux.HandleFunc("/v1/domains/example.com/records/TXT/", testHandler(http.MethodGet, http.StatusUnprocessableEntity, "errors.json")) - records, err := client.GetRecords(context.Background(), "example.com", "TXT", "") + records, err := client.GetRecords(t.Context(), "example.com", "TXT", "") require.EqualError(t, err, "[status code: 422] INVALID_BODY: Request body doesn't fulfill schema, see details in `fields`") assert.Nil(t, records) } @@ -84,7 +83,7 @@ func TestClient_UpdateTxtRecords(t *testing.T) { {Name: "_acme-challenge.lego", Type: "TXT", Data: "acme", TTL: 600}, } - err := client.UpdateTxtRecords(context.Background(), records, "example.com", "lego") + err := client.UpdateTxtRecords(t.Context(), records, "example.com", "lego") require.NoError(t, err) } @@ -103,7 +102,7 @@ func TestClient_UpdateTxtRecords_errors(t *testing.T) { {Name: "_acme-challenge.lego", Type: "TXT", Data: "acme", TTL: 600}, } - err := client.UpdateTxtRecords(context.Background(), records, "example.com", "lego") + err := client.UpdateTxtRecords(t.Context(), records, "example.com", "lego") require.EqualError(t, err, "[status code: 422] INVALID_BODY: Request body doesn't fulfill schema, see details in `fields`") } @@ -112,7 +111,7 @@ func TestClient_DeleteTxtRecords(t *testing.T) { mux.HandleFunc("/v1/domains/example.com/records/TXT/foo", testHandler(http.MethodDelete, http.StatusNoContent, "")) - err := client.DeleteTxtRecords(context.Background(), "example.com", "foo") + err := client.DeleteTxtRecords(t.Context(), "example.com", "foo") require.NoError(t, err) } @@ -121,7 +120,7 @@ func TestClient_DeleteTxtRecords_errors(t *testing.T) { mux.HandleFunc("/v1/domains/example.com/records/TXT/foo", testHandler(http.MethodDelete, http.StatusConflict, "error-extended.json")) - err := client.DeleteTxtRecords(context.Background(), "example.com", "foo") + err := client.DeleteTxtRecords(t.Context(), "example.com", "foo") require.EqualError(t, err, "[status code: 409] ACCESS_DENIED: Authenticated user is not allowed access [test: content (path=/foo) (pathRelated=/bar)]") } diff --git a/providers/dns/hetzner/internal/client_test.go b/providers/dns/hetzner/internal/client_test.go index aa2175409..fe9f992fb 100644 --- a/providers/dns/hetzner/internal/client_test.go +++ b/providers/dns/hetzner/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -66,7 +65,7 @@ func TestClient_GetTxtRecord(t *testing.T) { } }) - record, err := client.GetTxtRecord(context.Background(), "test1", "txttxttxt", zoneID) + record, err := client.GetTxtRecord(t.Context(), "test1", "txttxttxt", zoneID) require.NoError(t, err) fmt.Println(record) @@ -112,7 +111,7 @@ func TestClient_CreateRecord(t *testing.T) { ZoneID: zoneID, } - err := client.CreateRecord(context.Background(), record) + err := client.CreateRecord(t.Context(), record) require.NoError(t, err) } @@ -134,7 +133,7 @@ func TestClient_DeleteRecord(t *testing.T) { } }) - err := client.DeleteRecord(context.Background(), "recordID") + err := client.DeleteRecord(t.Context(), "recordID") require.NoError(t, err) } @@ -169,7 +168,7 @@ func TestClient_GetZoneID(t *testing.T) { } }) - zoneID, err := client.GetZoneID(context.Background(), "example.com") + zoneID, err := client.GetZoneID(t.Context(), "example.com") require.NoError(t, err) assert.Equal(t, "zoneA", zoneID) diff --git a/providers/dns/hosttech/internal/client_test.go b/providers/dns/hosttech/internal/client_test.go index bf90acc9f..3acbaafc5 100644 --- a/providers/dns/hosttech/internal/client_test.go +++ b/providers/dns/hosttech/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -20,7 +19,7 @@ const testAPIKey = "secret" func TestClient_GetZones(t *testing.T) { client := setupTest(t, "/user/v1/zones", testHandler(http.MethodGet, http.StatusOK, "zones.json")) - zones, err := client.GetZones(context.Background(), "", 100, 0) + zones, err := client.GetZones(t.Context(), "", 100, 0) require.NoError(t, err) expected := []Zone{ @@ -41,14 +40,14 @@ func TestClient_GetZones(t *testing.T) { func TestClient_GetZones_error(t *testing.T) { client := setupTest(t, "/user/v1/zones", testHandler(http.MethodGet, http.StatusUnauthorized, "error.json")) - _, err := client.GetZones(context.Background(), "", 100, 0) + _, err := client.GetZones(t.Context(), "", 100, 0) require.Error(t, err) } func TestClient_GetZone(t *testing.T) { client := setupTest(t, "/user/v1/zones/123", testHandler(http.MethodGet, http.StatusOK, "zone.json")) - zone, err := client.GetZone(context.Background(), "123") + zone, err := client.GetZone(t.Context(), "123") require.NoError(t, err) expected := &Zone{ @@ -67,14 +66,14 @@ func TestClient_GetZone(t *testing.T) { func TestClient_GetZone_error(t *testing.T) { client := setupTest(t, "/user/v1/zones/123", testHandler(http.MethodGet, http.StatusUnauthorized, "error.json")) - _, err := client.GetZone(context.Background(), "123") + _, err := client.GetZone(t.Context(), "123") require.Error(t, err) } func TestClient_GetRecords(t *testing.T) { client := setupTest(t, "/user/v1/zones/123/records", testHandler(http.MethodGet, http.StatusOK, "records.json")) - records, err := client.GetRecords(context.Background(), "123", "TXT") + records, err := client.GetRecords(t.Context(), "123", "TXT") require.NoError(t, err) expected := []Record{ @@ -154,7 +153,7 @@ func TestClient_GetRecords(t *testing.T) { func TestClient_GetRecords_error(t *testing.T) { client := setupTest(t, "/user/v1/zones/123/records", testHandler(http.MethodGet, http.StatusUnauthorized, "error.json")) - _, err := client.GetRecords(context.Background(), "123", "TXT") + _, err := client.GetRecords(t.Context(), "123", "TXT") require.Error(t, err) } @@ -169,7 +168,7 @@ func TestClient_AddRecord(t *testing.T) { Comment: "example", } - newRecord, err := client.AddRecord(context.Background(), "123", record) + newRecord, err := client.AddRecord(t.Context(), "123", record) require.NoError(t, err) expected := &Record{ @@ -195,21 +194,21 @@ func TestClient_AddRecord_error(t *testing.T) { Comment: "example", } - _, err := client.AddRecord(context.Background(), "123", record) + _, err := client.AddRecord(t.Context(), "123", record) require.Error(t, err) } func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, "/user/v1/zones/123/records/6", testHandler(http.MethodDelete, http.StatusUnauthorized, "error.json")) - err := client.DeleteRecord(context.Background(), "123", "6") + err := client.DeleteRecord(t.Context(), "123", "6") require.Error(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { client := setupTest(t, "/user/v1/zones/123/records/6", testHandler(http.MethodDelete, http.StatusNoContent, "")) - err := client.DeleteRecord(context.Background(), "123", "6") + err := client.DeleteRecord(t.Context(), "123", "6") require.NoError(t, err) } diff --git a/providers/dns/hurricane/hurricane.go b/providers/dns/hurricane/hurricane.go index e2054d38d..7ce646bc9 100644 --- a/providers/dns/hurricane/hurricane.go +++ b/providers/dns/hurricane/hurricane.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/http" - "strings" "time" "github.com/go-acme/lego/v4/challenge" @@ -63,9 +62,9 @@ func NewDNSProvider() (*DNSProvider, error) { return nil, fmt.Errorf("hurricane: %w", err) } - credentials, err := parseCredentials(values[EnvTokens]) + credentials, err := env.ParsePairs(values[EnvTokens]) if err != nil { - return nil, fmt.Errorf("hurricane: %w", err) + return nil, fmt.Errorf("hurricane: credentials: %w", err) } config.Credentials = credentials @@ -122,19 +121,3 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { func (d *DNSProvider) Sequential() time.Duration { return d.config.SequenceInterval } - -func parseCredentials(raw string) (map[string]string, error) { - credentials := make(map[string]string) - - credStrings := strings.Split(strings.TrimSuffix(raw, ","), ",") - for _, credPair := range credStrings { - data := strings.Split(credPair, ":") - if len(data) != 2 { - return nil, fmt.Errorf("incorrect credential pair: %s", credPair) - } - - credentials[strings.TrimSpace(data[0])] = strings.TrimSpace(data[1]) - } - - return credentials, nil -} diff --git a/providers/dns/hurricane/hurricane_test.go b/providers/dns/hurricane/hurricane_test.go index 12217c790..f8a1f185c 100644 --- a/providers/dns/hurricane/hurricane_test.go +++ b/providers/dns/hurricane/hurricane_test.go @@ -34,14 +34,14 @@ func TestNewDNSProvider(t *testing.T) { envVars: map[string]string{ EnvTokens: ",", }, - expected: "hurricane: incorrect credential pair: ", + expected: "hurricane: credentials: incorrect pair: ", }, { desc: "invalid credentials, partial", envVars: map[string]string{ EnvTokens: "example.org:123,example.net", }, - expected: "hurricane: incorrect credential pair: example.net", + expected: "hurricane: credentials: incorrect pair: example.net", }, { desc: "missing credentials", diff --git a/providers/dns/hurricane/internal/client.go b/providers/dns/hurricane/internal/client.go index 62ca76159..b758ec166 100644 --- a/providers/dns/hurricane/internal/client.go +++ b/providers/dns/hurricane/internal/client.go @@ -52,7 +52,7 @@ func NewClient(credentials map[string]string) *Client { } // UpdateTxtRecord updates a TXT record. -func (c *Client) UpdateTxtRecord(ctx context.Context, hostname string, txt string) error { +func (c *Client) UpdateTxtRecord(ctx context.Context, hostname, txt string) error { domain := strings.TrimPrefix(hostname, "_acme-challenge.") c.credMu.Lock() @@ -101,7 +101,7 @@ func (c *Client) UpdateTxtRecord(ctx context.Context, hostname string, txt strin return evaluateBody(string(bytes.TrimSpace(raw)), hostname) } -func evaluateBody(body string, hostname string) error { +func evaluateBody(body, hostname string) error { code, _, _ := strings.Cut(body, " ") switch code { diff --git a/providers/dns/hurricane/internal/client_test.go b/providers/dns/hurricane/internal/client_test.go index 2862c2481..2e55c2057 100644 --- a/providers/dns/hurricane/internal/client_test.go +++ b/providers/dns/hurricane/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -75,7 +74,7 @@ func TestClient_UpdateTxtRecord(t *testing.T) { client.baseURL = server.URL client.HTTPClient = server.Client() - err := client.UpdateTxtRecord(context.Background(), "_acme-challenge.example.com", "foo") + err := client.UpdateTxtRecord(t.Context(), "_acme-challenge.example.com", "foo") test.expected(t, err) }) } diff --git a/providers/dns/hyperone/internal/client.go b/providers/dns/hyperone/internal/client.go index 09fa68768..cf9ab2a37 100644 --- a/providers/dns/hyperone/internal/client.go +++ b/providers/dns/hyperone/internal/client.go @@ -132,7 +132,7 @@ func (c *Client) CreateRecordset(ctx context.Context, zoneID, recordType, name, // DeleteRecordset deletes a recordset. // https://api.hyperone.com/v2/docs#operation/dns_project_zone_recordset_delete -func (c *Client) DeleteRecordset(ctx context.Context, zoneID string, recordsetID string) error { +func (c *Client) DeleteRecordset(ctx context.Context, zoneID, recordsetID string) error { // https://api.hyperone.com/v2/dns/{locationId}/project/{projectId}/zone/{zoneId}/recordset/{recordsetId} endpoint := c.baseURL.JoinPath("zone", zoneID, "recordset", recordsetID) @@ -146,7 +146,7 @@ func (c *Client) DeleteRecordset(ctx context.Context, zoneID string, recordsetID // GetRecords gets all records within specified recordset. // https://api.hyperone.com/v2/docs#operation/dns_project_zone_recordset_record_list -func (c *Client) GetRecords(ctx context.Context, zoneID string, recordsetID string) ([]Record, error) { +func (c *Client) GetRecords(ctx context.Context, zoneID, recordsetID string) ([]Record, error) { // https://api.hyperone.com/v2/dns/{locationId}/project/{projectId}/zone/{zoneId}/recordset/{recordsetId}/record endpoint := c.baseURL.JoinPath("zone", zoneID, "recordset", recordsetID, "record") diff --git a/providers/dns/hyperone/internal/client_test.go b/providers/dns/hyperone/internal/client_test.go index e3a1073e0..55c43700c 100644 --- a/providers/dns/hyperone/internal/client_test.go +++ b/providers/dns/hyperone/internal/client_test.go @@ -2,7 +2,6 @@ package internal import ( "bytes" - "context" "encoding/json" "fmt" "io" @@ -25,7 +24,7 @@ func (s signerMock) GetJWT() (string, error) { func TestClient_FindRecordset(t *testing.T) { client := setupTest(t, http.MethodGet, "/dns/loc123/project/proj123/zone/zone321/recordset", respFromFile("recordset.json")) - recordset, err := client.FindRecordset(context.Background(), "zone321", "SOA", "example.com.") + recordset, err := client.FindRecordset(t.Context(), "zone321", "SOA", "example.com.") require.NoError(t, err) expected := &Recordset{ @@ -49,7 +48,7 @@ func TestClient_CreateRecordset(t *testing.T) { client := setupTest(t, http.MethodPost, "/dns/loc123/project/proj123/zone/zone123/recordset", hasReqBody(expectedReqBody), respFromFile("createRecordset.json")) - rs, err := client.CreateRecordset(context.Background(), "zone123", "TXT", "test.example.com.", "value", 3600) + rs, err := client.CreateRecordset(t.Context(), "zone123", "TXT", "test.example.com.", "value", 3600) require.NoError(t, err) expected := &Recordset{RecordType: "TXT", Name: "test.example.com.", TTL: 3600, ID: "1234567890qwertyuiop"} @@ -59,14 +58,14 @@ func TestClient_CreateRecordset(t *testing.T) { func TestClient_DeleteRecordset(t *testing.T) { client := setupTest(t, http.MethodDelete, "/dns/loc123/project/proj123/zone/zone321/recordset/rs322") - err := client.DeleteRecordset(context.Background(), "zone321", "rs322") + err := client.DeleteRecordset(t.Context(), "zone321", "rs322") require.NoError(t, err) } func TestClient_GetRecords(t *testing.T) { client := setupTest(t, http.MethodGet, "/dns/loc123/project/proj123/zone/321/recordset/322/record", respFromFile("record.json")) - records, err := client.GetRecords(context.Background(), "321", "322") + records, err := client.GetRecords(t.Context(), "321", "322") require.NoError(t, err) expected := []Record{ @@ -88,7 +87,7 @@ func TestClient_CreateRecord(t *testing.T) { client := setupTest(t, http.MethodPost, "/dns/loc123/project/proj123/zone/z123/recordset/rs325/record", hasReqBody(expectedReqBody), respFromFile("createRecord.json")) - rs, err := client.CreateRecord(context.Background(), "z123", "rs325", "value") + rs, err := client.CreateRecord(t.Context(), "z123", "rs325", "value") require.NoError(t, err) expected := &Record{ID: "123321qwerqwewqerq", Content: "value", Enabled: true} @@ -98,14 +97,14 @@ func TestClient_CreateRecord(t *testing.T) { func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, http.MethodDelete, "/dns/loc123/project/proj123/zone/321/recordset/322/record/323") - err := client.DeleteRecord(context.Background(), "321", "322", "323") + err := client.DeleteRecord(t.Context(), "321", "322", "323") require.NoError(t, err) } func TestClient_FindZone(t *testing.T) { client := setupTest(t, http.MethodGet, "/dns/loc123/project/proj123/zone", respFromFile("zones.json")) - zone, err := client.FindZone(context.Background(), "example.com") + zone, err := client.FindZone(t.Context(), "example.com") require.NoError(t, err) expected := &Zone{ @@ -122,7 +121,7 @@ func TestClient_FindZone(t *testing.T) { func TestClient_GetZones(t *testing.T) { client := setupTest(t, http.MethodGet, "/dns/loc123/project/proj123/zone", respFromFile("zones.json")) - zones, err := client.GetZones(context.Background()) + zones, err := client.GetZones(t.Context()) require.NoError(t, err) expected := []Zone{ @@ -183,7 +182,7 @@ func setupTest(t *testing.T, method, path string, handlers ...assertHandler) *Cl type assertHandler func(http.ResponseWriter, *http.Request) (int, error) -func hasReqBody(v interface{}) assertHandler { +func hasReqBody(v any) assertHandler { return func(rw http.ResponseWriter, req *http.Request) (int, error) { reqBody, err := io.ReadAll(req.Body) if err != nil { diff --git a/providers/dns/iij/iij.go b/providers/dns/iij/iij.go index 9beb411ed..6bc7db21a 100644 --- a/providers/dns/iij/iij.go +++ b/providers/dns/iij/iij.go @@ -6,7 +6,6 @@ import ( "fmt" "slices" "strconv" - "strings" "time" "github.com/go-acme/lego/v4/challenge" @@ -14,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/platform/config/env" "github.com/iij/doapi" "github.com/iij/doapi/protocol" + "github.com/miekg/dns" ) // Environment variables names. @@ -226,26 +226,20 @@ func (d *DNSProvider) listZones() ([]string, error) { } func splitDomain(domain string, zones []string) (string, string, error) { - parts := strings.Split(strings.Trim(domain, "."), ".") + base := dns01.UnFqdn(domain) - var owner string - var zone string + for _, index := range dns.Split(base) { + zone := base[index:] - for i := range len(parts) - 1 { - zone = strings.Join(parts[i:], ".") if slices.Contains(zones, zone) { - baseOwner := strings.Join(parts[0:i], ".") + baseOwner := base[:index] if baseOwner != "" { baseOwner = "." + baseOwner } - owner = "_acme-challenge" + baseOwner - break + + return "_acme-challenge" + dns01.UnFqdn(baseOwner), zone, nil } } - if owner == "" { - return "", "", fmt.Errorf("%s not found", domain) - } - - return owner, zone, nil + return "", "", fmt.Errorf("%s not found", domain) } diff --git a/providers/dns/iij/iij_test.go b/providers/dns/iij/iij_test.go index 936dd9b8d..2c7ec4217 100644 --- a/providers/dns/iij/iij_test.go +++ b/providers/dns/iij/iij_test.go @@ -161,31 +161,31 @@ func TestSplitDomain(t *testing.T) { }{ { desc: "domain equals zone", - domain: "domain.com", - zones: []string{"domain.com"}, + domain: "example.com", + zones: []string{"example.com"}, expectedOwner: "_acme-challenge", - expectedZone: "domain.com", + expectedZone: "example.com", }, { desc: "with a subdomain", - domain: "my.domain.com", - zones: []string{"domain.com"}, + domain: "my.example.com", + zones: []string{"example.com"}, expectedOwner: "_acme-challenge.my", - expectedZone: "domain.com", + expectedZone: "example.com", }, { desc: "with a subdomain in a zone", - domain: "my.sub.domain.com", - zones: []string{"sub.domain.com", "domain.com"}, + domain: "my.sub.example.com", + zones: []string{"sub.example.com", "example.com"}, expectedOwner: "_acme-challenge.my", - expectedZone: "sub.domain.com", + expectedZone: "sub.example.com", }, { desc: "with a sub-subdomain", - domain: "my.sub.domain.com", - zones: []string{"domain1.com", "domain.com"}, + domain: "my.sub.example.com", + zones: []string{"domain1.com", "example.com"}, expectedOwner: "_acme-challenge.my.sub", - expectedZone: "domain.com", + expectedZone: "example.com", }, } @@ -202,6 +202,36 @@ func TestSplitDomain(t *testing.T) { } } +func TestSplitDomain_error(t *testing.T) { + testCases := []struct { + desc string + domain string + zones []string + expectedOwner string + expectedZone string + }{ + { + desc: "no zone", + domain: "example.com", + zones: nil, + }, + { + desc: "domain does not contain zone", + domain: "example.com", + zones: []string{"example.org"}, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + _, _, err := splitDomain(test.domain, test.zones) + require.Error(t, err) + }) + } +} + func TestLivePresent(t *testing.T) { if !envTest.IsLiveTest() { t.Skip("skipping live test") diff --git a/providers/dns/infomaniak/internal/client_test.go b/providers/dns/infomaniak/internal/client_test.go index 4fadaf0f5..5c2d93202 100644 --- a/providers/dns/infomaniak/internal/client_test.go +++ b/providers/dns/infomaniak/internal/client_test.go @@ -2,7 +2,6 @@ package internal import ( "bytes" - "context" "fmt" "io" "net/http" @@ -73,7 +72,7 @@ func TestClient_CreateDNSRecord(t *testing.T) { TTL: 60, } - recordID, err := client.CreateDNSRecord(context.Background(), domain, record) + recordID, err := client.CreateDNSRecord(t.Context(), domain, record) require.NoError(t, err) assert.Equal(t, "123", recordID) @@ -128,7 +127,7 @@ func TestClient_GetDomainByName(t *testing.T) { } }) - domain, err := client.GetDomainByName(context.Background(), "one.two.three.example.com.") + domain, err := client.GetDomainByName(t.Context(), "one.two.three.example.com.") require.NoError(t, err) expected := &DNSDomain{ID: 123, CustomerName: "two.three.example.com"} @@ -156,6 +155,6 @@ func TestClient_DeleteDNSRecord(t *testing.T) { } }) - err := client.DeleteDNSRecord(context.Background(), 123, "456") + err := client.DeleteDNSRecord(t.Context(), 123, "456") require.NoError(t, err) } diff --git a/providers/dns/internal/active24/client_test.go b/providers/dns/internal/active24/client_test.go index 9bbdebc8b..d92ec574d 100644 --- a/providers/dns/internal/active24/client_test.go +++ b/providers/dns/internal/active24/client_test.go @@ -1,7 +1,6 @@ package active24 import ( - "context" "io" "net/http" "net/http/httptest" @@ -56,7 +55,7 @@ func setupTest(t *testing.T, pattern string, status int, filename string) *Clien func TestClient_GetServices(t *testing.T) { client := setupTest(t, "GET /v1/user/self/service", http.StatusOK, "services.json") - services, err := client.GetServices(context.Background()) + services, err := client.GetServices(t.Context()) require.NoError(t, err) expected := []Service{ @@ -86,7 +85,7 @@ func TestClient_GetServices(t *testing.T) { func TestClient_GetServices_errors(t *testing.T) { client := setupTest(t, "GET /v1/user/self/service", http.StatusUnauthorized, "error_v1.json") - _, err := client.GetServices(context.Background()) + _, err := client.GetServices(t.Context()) require.EqualError(t, err, "401: No username or password.") } @@ -99,7 +98,7 @@ func TestClient_GetRecords(t *testing.T) { Content: "txt", } - records, err := client.GetRecords(context.Background(), "aaa", filter) + records, err := client.GetRecords(t.Context(), "aaa", filter) require.NoError(t, err) expected := []Record{{ @@ -124,35 +123,35 @@ func TestClient_GetRecords_errors(t *testing.T) { Content: "txt", } - _, err := client.GetRecords(context.Background(), "aaa", filter) + _, err := client.GetRecords(t.Context(), "aaa", filter) require.EqualError(t, err, "403: /errors/httpException: This action is unauthorized.") } func TestClient_CreateRecord(t *testing.T) { client := setupTest(t, "POST /v2/service/aaa/dns/record", http.StatusNoContent, "") - err := client.CreateRecord(context.Background(), "aaa", Record{}) + err := client.CreateRecord(t.Context(), "aaa", Record{}) require.NoError(t, err) } func TestClient_CreateRecord_errors(t *testing.T) { client := setupTest(t, "POST /v2/service/aaa/dns/record", http.StatusForbidden, "error_403.json") - err := client.CreateRecord(context.Background(), "aaa", Record{}) + err := client.CreateRecord(t.Context(), "aaa", Record{}) require.EqualError(t, err, "403: /errors/httpException: This action is unauthorized.") } func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, "DELETE /v2/service/aaa/dns/record/123", http.StatusNoContent, "") - err := client.DeleteRecord(context.Background(), "aaa", "123") + err := client.DeleteRecord(t.Context(), "aaa", "123") require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { client := setupTest(t, "DELETE /v2/service/aaa/dns/record/123", http.StatusForbidden, "error_403.json") - err := client.DeleteRecord(context.Background(), "aaa", "123") + err := client.DeleteRecord(t.Context(), "aaa", "123") require.EqualError(t, err, "403: /errors/httpException: This action is unauthorized.") } diff --git a/providers/dns/internal/hostingde/client_test.go b/providers/dns/internal/hostingde/client_test.go index d538c8bc0..c4090ec5c 100644 --- a/providers/dns/internal/hostingde/client_test.go +++ b/providers/dns/internal/hostingde/client_test.go @@ -2,7 +2,6 @@ package hostingde import ( "bytes" - "context" "encoding/json" "fmt" "io" @@ -72,7 +71,7 @@ func TestClient_ListZoneConfigs(t *testing.T) { Page: 1, } - zoneResponse, err := client.ListZoneConfigs(context.Background(), zonesFind) + zoneResponse, err := client.ListZoneConfigs(t.Context(), zonesFind) require.NoError(t, err) expected := &ZoneResponse{ @@ -124,7 +123,7 @@ func TestClient_ListZoneConfigs_error(t *testing.T) { Page: 1, } - _, err := client.ListZoneConfigs(context.Background(), zonesFind) + _, err := client.ListZoneConfigs(t.Context(), zonesFind) require.Error(t, err) } @@ -179,7 +178,7 @@ func TestClient_UpdateZone(t *testing.T) { }}, } - response, err := client.UpdateZone(context.Background(), request) + response, err := client.UpdateZone(t.Context(), request) require.NoError(t, err) expected := &Zone{ @@ -259,6 +258,6 @@ func TestClient_UpdateZone_error(t *testing.T) { }}, } - _, err := client.UpdateZone(context.Background(), request) + _, err := client.UpdateZone(t.Context(), request) require.Error(t, err) } diff --git a/providers/dns/internal/rimuhosting/client_test.go b/providers/dns/internal/rimuhosting/client_test.go index ecd55b0b5..90a574b3a 100644 --- a/providers/dns/internal/rimuhosting/client_test.go +++ b/providers/dns/internal/rimuhosting/client_test.go @@ -1,7 +1,6 @@ package rimuhosting import ( - "context" "encoding/xml" "fmt" "io" @@ -99,7 +98,7 @@ func TestClient_FindTXTRecords(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - records, err := client.FindTXTRecords(context.Background(), test.domain) + records, err := client.FindTXTRecords(t.Context(), test.domain) require.NoError(t, err) assert.Equal(t, test.expected, records) @@ -291,7 +290,7 @@ func TestClient_DoActions(t *testing.T) { } }) - resp, err := client.DoActions(context.Background(), test.actions...) + resp, err := client.DoActions(t.Context(), test.actions...) if test.expected.Error != "" { require.EqualError(t, err, test.expected.Error) return diff --git a/providers/dns/internal/selectel/client_test.go b/providers/dns/internal/selectel/client_test.go index 703fd7b98..d0a2f8cf0 100644 --- a/providers/dns/internal/selectel/client_test.go +++ b/providers/dns/internal/selectel/client_test.go @@ -1,7 +1,6 @@ package selectel import ( - "context" "encoding/json" "fmt" "io" @@ -47,7 +46,7 @@ func TestClient_ListRecords(t *testing.T) { } }) - records, err := client.ListRecords(context.Background(), 123) + records, err := client.ListRecords(t.Context(), 123) require.NoError(t, err) expected := []Record{ @@ -76,7 +75,7 @@ func TestClient_ListRecords_error(t *testing.T) { } }) - records, err := client.ListRecords(context.Background(), 123) + records, err := client.ListRecords(t.Context(), 123) require.EqualError(t, err, "request failed with status code 401: API error: 400 - error description - field that the error occurred in") assert.Nil(t, records) @@ -118,7 +117,7 @@ func TestClient_GetDomainByName(t *testing.T) { } }) - domain, err := client.GetDomainByName(context.Background(), "sub.sub.example.org") + domain, err := client.GetDomainByName(t.Context(), "sub.sub.example.org") require.NoError(t, err) expected := &Domain{ @@ -155,7 +154,7 @@ func TestClient_AddRecord(t *testing.T) { } }) - record, err := client.AddRecord(context.Background(), 123, Record{ + record, err := client.AddRecord(t.Context(), 123, Record{ Name: "example.org", Type: "TXT", TTL: 60, @@ -187,7 +186,7 @@ func TestClient_DeleteRecord(t *testing.T) { } }) - err := client.DeleteRecord(context.Background(), 123, 456) + err := client.DeleteRecord(t.Context(), 123, 456) require.NoError(t, err) } diff --git a/providers/dns/internetbs/internal/client.go b/providers/dns/internetbs/internal/client.go index 771408c5d..23cc7d1b3 100644 --- a/providers/dns/internetbs/internal/client.go +++ b/providers/dns/internetbs/internal/client.go @@ -34,7 +34,7 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(apiKey string, password string) *Client { +func NewClient(apiKey, password string) *Client { baseURL, _ := url.Parse(baseURL) return &Client{ @@ -90,7 +90,7 @@ func (c Client) ListRecords(ctx context.Context, query ListRecordQuery) ([]Recor return l.Records, nil } -func (c Client) doRequest(ctx context.Context, action string, params any, result any) error { +func (c Client) doRequest(ctx context.Context, action string, params, result any) error { endpoint := c.baseURL.JoinPath("Domain", "DnsRecord", action) values, err := querystring.Values(params) diff --git a/providers/dns/internetbs/internal/client_test.go b/providers/dns/internetbs/internal/client_test.go index a22f1b121..d0b94ad4e 100644 --- a/providers/dns/internetbs/internal/client_test.go +++ b/providers/dns/internetbs/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -32,7 +31,7 @@ func TestClient_AddRecord(t *testing.T) { TTL: 36000, } - err := client.AddRecord(context.Background(), query) + err := client.AddRecord(t.Context(), query) require.NoError(t, err) } @@ -46,7 +45,7 @@ func TestClient_AddRecord_error(t *testing.T) { TTL: 36000, } - err := client.AddRecord(context.Background(), query) + err := client.AddRecord(t.Context(), query) require.Error(t, err) } @@ -67,7 +66,7 @@ func TestClient_AddRecord_integration(t *testing.T) { TTL: 36000, } - err := client.AddRecord(context.Background(), query) + err := client.AddRecord(t.Context(), query) require.NoError(t, err) query = RecordQuery{ @@ -77,7 +76,7 @@ func TestClient_AddRecord_integration(t *testing.T) { TTL: 36000, } - err = client.AddRecord(context.Background(), query) + err = client.AddRecord(t.Context(), query) require.NoError(t, err) } @@ -89,7 +88,7 @@ func TestClient_RemoveRecord(t *testing.T) { Type: "TXT", Value: "", } - err := client.RemoveRecord(context.Background(), query) + err := client.RemoveRecord(t.Context(), query) require.NoError(t, err) } @@ -101,7 +100,7 @@ func TestClient_RemoveRecord_error(t *testing.T) { Type: "TXT", Value: "", } - err := client.RemoveRecord(context.Background(), query) + err := client.RemoveRecord(t.Context(), query) require.Error(t, err) } @@ -121,7 +120,7 @@ func TestClient_RemoveRecord_integration(t *testing.T) { Value: "", } - err := client.RemoveRecord(context.Background(), query) + err := client.RemoveRecord(t.Context(), query) require.NoError(t, err) } @@ -132,7 +131,7 @@ func TestClient_ListRecords(t *testing.T) { Domain: "example.com", } - records, err := client.ListRecords(context.Background(), query) + records, err := client.ListRecords(t.Context(), query) require.NoError(t, err) expected := []Record{ @@ -184,7 +183,7 @@ func TestClient_ListRecords_error(t *testing.T) { Domain: "www.example.com", } - _, err := client.ListRecords(context.Background(), query) + _, err := client.ListRecords(t.Context(), query) require.Error(t, err) } @@ -202,7 +201,7 @@ func TestClient_ListRecords_integration(t *testing.T) { Domain: "example.com", } - records, err := client.ListRecords(context.Background(), query) + records, err := client.ListRecords(t.Context(), query) require.NoError(t, err) for _, record := range records { diff --git a/providers/dns/ionos/internal/client_test.go b/providers/dns/ionos/internal/client_test.go index 21a7a2675..6a36dfde7 100644 --- a/providers/dns/ionos/internal/client_test.go +++ b/providers/dns/ionos/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -21,7 +20,7 @@ func TestClient_ListZones(t *testing.T) { mux.HandleFunc("/v1/zones", mockHandler(http.MethodGet, http.StatusOK, "list_zones.json")) - zones, err := client.ListZones(context.Background()) + zones, err := client.ListZones(t.Context()) require.NoError(t, err) expected := []Zone{{ @@ -38,7 +37,7 @@ func TestClient_ListZones_error(t *testing.T) { mux.HandleFunc("/v1/zones", mockHandler(http.MethodGet, http.StatusUnauthorized, "list_zones_error.json")) - zones, err := client.ListZones(context.Background()) + zones, err := client.ListZones(t.Context()) require.Error(t, err) assert.Nil(t, zones) @@ -53,7 +52,7 @@ func TestClient_GetRecords(t *testing.T) { mux.HandleFunc("/v1/zones/azone01", mockHandler(http.MethodGet, http.StatusOK, "get_records.json")) - records, err := client.GetRecords(context.Background(), "azone01", nil) + records, err := client.GetRecords(t.Context(), "azone01", nil) require.NoError(t, err) expected := []Record{{ @@ -71,7 +70,7 @@ func TestClient_GetRecords_error(t *testing.T) { mux.HandleFunc("/v1/zones/azone01", mockHandler(http.MethodGet, http.StatusUnauthorized, "get_records_error.json")) - records, err := client.GetRecords(context.Background(), "azone01", nil) + records, err := client.GetRecords(t.Context(), "azone01", nil) require.Error(t, err) assert.Nil(t, records) @@ -86,7 +85,7 @@ func TestClient_RemoveRecord(t *testing.T) { mux.HandleFunc("/v1/zones/azone01/records/arecord01", mockHandler(http.MethodDelete, http.StatusOK, "")) - err := client.RemoveRecord(context.Background(), "azone01", "arecord01") + err := client.RemoveRecord(t.Context(), "azone01", "arecord01") require.NoError(t, err) } @@ -95,7 +94,7 @@ func TestClient_RemoveRecord_error(t *testing.T) { mux.HandleFunc("/v1/zones/azone01/records/arecord01", mockHandler(http.MethodDelete, http.StatusInternalServerError, "remove_record_error.json")) - err := client.RemoveRecord(context.Background(), "azone01", "arecord01") + err := client.RemoveRecord(t.Context(), "azone01", "arecord01") require.Error(t, err) var cErr *ClientError @@ -115,7 +114,7 @@ func TestClient_ReplaceRecords(t *testing.T) { Type: "A", }} - err := client.ReplaceRecords(context.Background(), "azone01", records) + err := client.ReplaceRecords(t.Context(), "azone01", records) require.NoError(t, err) } @@ -131,7 +130,7 @@ func TestClient_ReplaceRecords_error(t *testing.T) { Type: "A", }} - err := client.ReplaceRecords(context.Background(), "azone01", records) + err := client.ReplaceRecords(t.Context(), "azone01", records) require.Error(t, err) var cErr *ClientError diff --git a/providers/dns/ipv64/internal/client_test.go b/providers/dns/ipv64/internal/client_test.go index 1966f9f68..8f97d8ff2 100644 --- a/providers/dns/ipv64/internal/client_test.go +++ b/providers/dns/ipv64/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -63,7 +62,7 @@ func testHandler(method, filename string, statusCode int) http.HandlerFunc { func TestClient_GetDomains(t *testing.T) { client := setupTest(t, testHandler(http.MethodGet, "get_domains.json", http.StatusOK)) - domains, err := client.GetDomains(context.Background()) + domains, err := client.GetDomains(t.Context()) require.NoError(t, err) expected := &Domains{ @@ -114,7 +113,7 @@ func TestClient_GetDomains(t *testing.T) { func TestClient_GetDomains_error(t *testing.T) { client := setupTest(t, testHandler(http.MethodGet, "error.json", http.StatusUnauthorized)) - domains, err := client.GetDomains(context.Background()) + domains, err := client.GetDomains(t.Context()) require.Error(t, err) require.Nil(t, domains) @@ -123,27 +122,27 @@ func TestClient_GetDomains_error(t *testing.T) { func TestClient_AddRecord(t *testing.T) { client := setupTest(t, testHandler(http.MethodPost, "add_record.json", http.StatusCreated)) - err := client.AddRecord(context.Background(), "lego.ipv64.net", "_acme-challenge", "TXT", "value") + err := client.AddRecord(t.Context(), "lego.ipv64.net", "_acme-challenge", "TXT", "value") require.NoError(t, err) } func TestClient_AddRecord_error(t *testing.T) { client := setupTest(t, testHandler(http.MethodPost, "add_record-error.json", http.StatusBadRequest)) - err := client.AddRecord(context.Background(), "lego.ipv64.net", "_acme-challenge", "TXT", "value") + err := client.AddRecord(t.Context(), "lego.ipv64.net", "_acme-challenge", "TXT", "value") require.Error(t, err) } func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, testHandler(http.MethodDelete, "del_record.json", http.StatusAccepted)) - err := client.DeleteRecord(context.Background(), "lego.ipv64.net", "_acme-challenge", "TXT", "value") + err := client.DeleteRecord(t.Context(), "lego.ipv64.net", "_acme-challenge", "TXT", "value") require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { client := setupTest(t, testHandler(http.MethodDelete, "del_record-error.json", http.StatusBadRequest)) - err := client.DeleteRecord(context.Background(), "lego.ipv64.net", "_acme-challenge", "TXT", "value") + err := client.DeleteRecord(t.Context(), "lego.ipv64.net", "_acme-challenge", "TXT", "value") require.Error(t, err) } diff --git a/providers/dns/iwantmyname/internal/client.go b/providers/dns/iwantmyname/internal/client.go index 7a7c50e20..1edc7531f 100644 --- a/providers/dns/iwantmyname/internal/client.go +++ b/providers/dns/iwantmyname/internal/client.go @@ -23,7 +23,7 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(username string, password string) *Client { +func NewClient(username, password string) *Client { baseURL, _ := url.Parse(defaultBaseURL) return &Client{ diff --git a/providers/dns/iwantmyname/internal/client_test.go b/providers/dns/iwantmyname/internal/client_test.go index b26f7c0f0..39dca6dca 100644 --- a/providers/dns/iwantmyname/internal/client_test.go +++ b/providers/dns/iwantmyname/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -82,6 +81,6 @@ func TestClient_Do(t *testing.T) { TTL: 120, } - err := client.SendRequest(context.Background(), record) + err := client.SendRequest(t.Context(), record) require.NoError(t, err) } diff --git a/providers/dns/joker/internal/dmapi/client.go b/providers/dns/joker/internal/dmapi/client.go index 04f4350a9..6496abe2e 100644 --- a/providers/dns/joker/internal/dmapi/client.go +++ b/providers/dns/joker/internal/dmapi/client.go @@ -126,7 +126,7 @@ func parseResponse(message string) *Response { lines, body, _ := strings.Cut(message, "\n\n") - for _, line := range strings.Split(lines, "\n") { + for line := range strings.Lines(lines) { if strings.TrimSpace(line) == "" { continue } @@ -177,7 +177,7 @@ func RemoveTxtEntryFromZone(zone, relative string) (string, bool) { modified := false var zoneEntries []string - for _, line := range strings.Split(zone, "\n") { + for line := range strings.Lines(zone) { if strings.HasPrefix(line, prefix) { modified = true continue @@ -192,7 +192,7 @@ func RemoveTxtEntryFromZone(zone, relative string) (string, bool) { func AddTxtEntryToZone(zone, relative, value string, ttl int) string { var zoneEntries []string - for _, line := range strings.Split(zone, "\n") { + for line := range strings.Lines(zone) { zoneEntries = append(zoneEntries, fixTxtLines(line)) } diff --git a/providers/dns/joker/internal/dmapi/client_test.go b/providers/dns/joker/internal/dmapi/client_test.go index dc6653bf0..b7a294e09 100644 --- a/providers/dns/joker/internal/dmapi/client_test.go +++ b/providers/dns/joker/internal/dmapi/client_test.go @@ -93,7 +93,7 @@ func TestClient_GetZone(t *testing.T) { client := NewClient(AuthInfo{APIKey: "12345"}) client.BaseURL = serverURL - response, err := client.GetZone(mockContext(test.authSid), test.domain) + response, err := client.GetZone(mockContext(t, test.authSid), test.domain) if test.expectedError { require.Error(t, err) } else { diff --git a/providers/dns/joker/internal/dmapi/identity_test.go b/providers/dns/joker/internal/dmapi/identity_test.go index 418deaf4f..b84321096 100644 --- a/providers/dns/joker/internal/dmapi/identity_test.go +++ b/providers/dns/joker/internal/dmapi/identity_test.go @@ -14,12 +14,14 @@ import ( "github.com/stretchr/testify/require" ) -func mockContext(sessionID string) context.Context { +func mockContext(t *testing.T, sessionID string) context.Context { + t.Helper() + if sessionID == "" { sessionID = "xxx" } - return context.WithValue(context.Background(), sessionIDKey, sessionID) + return context.WithValue(t.Context(), sessionIDKey, sessionID) } func TestClient_login_apikey(t *testing.T) { @@ -78,7 +80,7 @@ func TestClient_login_apikey(t *testing.T) { client := NewClient(AuthInfo{APIKey: test.apiKey}) client.BaseURL = serverURL - response, err := client.login(context.Background()) + response, err := client.login(t.Context()) if test.expectedError { require.Error(t, err) } else { @@ -153,7 +155,7 @@ func TestClient_login_username(t *testing.T) { client := NewClient(AuthInfo{Username: test.username, Password: test.password}) client.BaseURL = serverURL - response, err := client.login(context.Background()) + response, err := client.login(t.Context()) if test.expectedError { require.Error(t, err) } else { @@ -216,7 +218,7 @@ func TestClient_logout(t *testing.T) { client.BaseURL = serverURL client.token = &Token{SessionID: test.authSid} - response, err := client.Logout(mockContext(test.authSid)) + response, err := client.Logout(mockContext(t, test.authSid)) if test.expectedError { require.Error(t, err) } else { @@ -253,7 +255,7 @@ func TestClient_CreateAuthenticatedContext(t *testing.T) { client.HTTPClient = server.Client() client.BaseURL = server.URL - ctx, err := client.CreateAuthenticatedContext(context.Background()) + ctx, err := client.CreateAuthenticatedContext(t.Context()) require.NoError(t, err) assert.Equal(t, "100", getSessionID(ctx)) @@ -263,7 +265,7 @@ func TestClient_CreateAuthenticatedContext(t *testing.T) { client.token.SessionID = "cache" client.muToken.Unlock() - ctx, err = client.CreateAuthenticatedContext(context.Background()) + ctx, err = client.CreateAuthenticatedContext(t.Context()) require.NoError(t, err) assert.Equal(t, "cache", getSessionID(ctx)) @@ -273,7 +275,7 @@ func TestClient_CreateAuthenticatedContext(t *testing.T) { client.token.ExpireAt = time.Now().UTC().Add(-1 * time.Hour) client.muToken.Unlock() - ctx, err = client.CreateAuthenticatedContext(context.Background()) + ctx, err = client.CreateAuthenticatedContext(t.Context()) require.NoError(t, err) assert.Equal(t, "200", getSessionID(ctx)) diff --git a/providers/dns/joker/internal/svc/client_test.go b/providers/dns/joker/internal/svc/client_test.go index 6803ae844..a396f67e5 100644 --- a/providers/dns/joker/internal/svc/client_test.go +++ b/providers/dns/joker/internal/svc/client_test.go @@ -1,7 +1,6 @@ package svc import ( - "context" "fmt" "io" "net/http" @@ -52,7 +51,7 @@ func TestClient_Send(t *testing.T) { label := "_acme-challenge" value := "123" - err := client.SendRequest(context.Background(), zone, label, value) + err := client.SendRequest(t.Context(), zone, label, value) require.NoError(t, err) } @@ -83,6 +82,6 @@ func TestClient_Send_empty(t *testing.T) { label := "_acme-challenge" value := "" - err := client.SendRequest(context.Background(), zone, label, value) + err := client.SendRequest(t.Context(), zone, label, value) require.NoError(t, err) } diff --git a/providers/dns/joker/joker_test.go b/providers/dns/joker/joker_test.go index a71e4d9fe..20e3fc7a5 100644 --- a/providers/dns/joker/joker_test.go +++ b/providers/dns/joker/joker_test.go @@ -20,7 +20,7 @@ func TestNewDNSProvider(t *testing.T) { testCases := []struct { desc string envVars map[string]string - expected interface{} + expected any }{ { desc: "mode DMAPI (default)", @@ -72,7 +72,7 @@ func TestNewDNSProviderConfig(t *testing.T) { testCases := []struct { desc string mode string - expected interface{} + expected any }{ { desc: "mode DMAPI (default)", diff --git a/providers/dns/liara/internal/client_test.go b/providers/dns/liara/internal/client_test.go index ed6672ab6..233a4bc2b 100644 --- a/providers/dns/liara/internal/client_test.go +++ b/providers/dns/liara/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -22,7 +21,7 @@ func TestClient_GetRecords(t *testing.T) { mux.HandleFunc("/api/v1/zones/example.com/dns-records", testHandler("./RecordsResponse.json", http.MethodGet, http.StatusOK)) - records, err := client.GetRecords(context.Background(), "example.com") + records, err := client.GetRecords(t.Context(), "example.com") require.NoError(t, err) expected := []Record{ @@ -46,7 +45,7 @@ func TestClient_GetRecord(t *testing.T) { mux.HandleFunc("/api/v1/zones/example.com/dns-records/123", testHandler("./RecordResponse.json", http.MethodGet, http.StatusOK)) - record, err := client.GetRecord(context.Background(), "example.com", "123") + record, err := client.GetRecord(t.Context(), "example.com", "123") require.NoError(t, err) expected := &Record{ @@ -79,7 +78,7 @@ func TestClient_CreateRecord(t *testing.T) { TTL: 3600, } - record, err := client.CreateRecord(context.Background(), "example.com", data) + record, err := client.CreateRecord(t.Context(), "example.com", data) require.NoError(t, err) expected := &Record{ @@ -104,7 +103,7 @@ func TestClient_DeleteRecord(t *testing.T) { rw.WriteHeader(http.StatusNoContent) }) - err := client.DeleteRecord(context.Background(), "example.com", "123") + err := client.DeleteRecord(t.Context(), "example.com", "123") require.NoError(t, err) } @@ -115,7 +114,7 @@ func TestClient_DeleteRecord_NotFound_Response(t *testing.T) { rw.WriteHeader(http.StatusNotFound) }) - err := client.DeleteRecord(context.Background(), "example.com", "123") + err := client.DeleteRecord(t.Context(), "example.com", "123") require.NoError(t, err) } @@ -124,11 +123,11 @@ func TestClient_DeleteRecord_error(t *testing.T) { mux.HandleFunc("/api/v1/zones/example.com/dns-records/123", testHandler("./error.json", http.MethodDelete, http.StatusUnauthorized)) - err := client.DeleteRecord(context.Background(), "example.com", "123") + err := client.DeleteRecord(t.Context(), "example.com", "123") require.Error(t, err) } -func testHandler(filename string, method string, statusCode int) http.HandlerFunc { +func testHandler(filename, method string, statusCode int) http.HandlerFunc { return func(rw http.ResponseWriter, req *http.Request) { if req.Method != method { http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) diff --git a/providers/dns/lightsail/lightsail.go b/providers/dns/lightsail/lightsail.go index d07b5505a..ddaf7baca 100644 --- a/providers/dns/lightsail/lightsail.go +++ b/providers/dns/lightsail/lightsail.go @@ -96,10 +96,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // causing a high number of consecutive throttling errors. // For reference: Route 53 enforces an account-wide(!) 5req/s query limit. options.Backoff = retry.BackoffDelayerFunc(func(attempt int, err error) (time.Duration, error) { - retryCount := attempt - if retryCount > 7 { - retryCount = 7 - } + retryCount := min(attempt, 7) delay := (1 << uint(retryCount)) * (rand.Intn(50) + 200) return time.Duration(delay) * time.Millisecond, nil diff --git a/providers/dns/lightsail/lightsail_integration_test.go b/providers/dns/lightsail/lightsail_integration_test.go index 1b96e87f0..718e57460 100644 --- a/providers/dns/lightsail/lightsail_integration_test.go +++ b/providers/dns/lightsail/lightsail_integration_test.go @@ -1,7 +1,6 @@ package lightsail import ( - "context" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -29,7 +28,7 @@ func TestLiveTTL(t *testing.T) { // we need a separate Lightsail client here as the one in the DNS provider is unexported. fqdn := "_acme-challenge." + domain - ctx := context.Background() + ctx := t.Context() cfg, err := awsconfig.LoadDefaultConfig(ctx) require.NoError(t, err) diff --git a/providers/dns/lightsail/lightsail_test.go b/providers/dns/lightsail/lightsail_test.go index 14370ffd9..4a11f6eb4 100644 --- a/providers/dns/lightsail/lightsail_test.go +++ b/providers/dns/lightsail/lightsail_test.go @@ -1,7 +1,6 @@ package lightsail import ( - "context" "os" "testing" @@ -53,7 +52,7 @@ func TestCredentialsFromEnv(t *testing.T) { _ = os.Setenv(envAwsSecretAccessKey, "123") _ = os.Setenv(envAwsRegion, "us-east-1") - ctx := context.Background() + ctx := t.Context() cfg, err := awsconfig.LoadDefaultConfig(ctx) require.NoError(t, err) diff --git a/providers/dns/limacity/internal/client_test.go b/providers/dns/limacity/internal/client_test.go index b9a13bdab..307783953 100644 --- a/providers/dns/limacity/internal/client_test.go +++ b/providers/dns/limacity/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -30,7 +29,7 @@ func setupTest(t *testing.T) (*Client, *http.ServeMux) { return client, mux } -func testHandler(filename string, method string, statusCode int) http.HandlerFunc { +func testHandler(filename, method string, statusCode int) http.HandlerFunc { return func(rw http.ResponseWriter, req *http.Request) { if req.Method != method { http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) @@ -66,7 +65,7 @@ func TestClient_GetDomains(t *testing.T) { mux.HandleFunc("/domains.json", testHandler("get-domains.json", http.MethodGet, http.StatusOK)) - domains, err := client.GetDomains(context.Background()) + domains, err := client.GetDomains(t.Context()) require.NoError(t, err) expected := []Domain{{ @@ -84,7 +83,7 @@ func TestClient_GetDomains_error(t *testing.T) { mux.HandleFunc("/domains.json", testHandler("error.json", http.MethodGet, http.StatusBadRequest)) - _, err := client.GetDomains(context.Background()) + _, err := client.GetDomains(t.Context()) require.EqualError(t, err, "[status code: 400] status: invalid_resource, details: name: [muss ausgefüllt werden]") } @@ -93,7 +92,7 @@ func TestClient_GetRecords(t *testing.T) { mux.HandleFunc("/domains/123/records.json", testHandler("get-records.json", http.MethodGet, http.StatusOK)) - records, err := client.GetRecords(context.Background(), 123) + records, err := client.GetRecords(t.Context(), 123) require.NoError(t, err) expected := []Record{ @@ -120,7 +119,7 @@ func TestClient_GetRecords_error(t *testing.T) { mux.HandleFunc("/domains/123/records.json", testHandler("error.json", http.MethodGet, http.StatusBadRequest)) - _, err := client.GetRecords(context.Background(), 123) + _, err := client.GetRecords(t.Context(), 123) require.EqualError(t, err, "[status code: 400] status: invalid_resource, details: name: [muss ausgefüllt werden]") } @@ -136,7 +135,7 @@ func TestClient_AddRecord(t *testing.T) { Type: "TXT", } - err := client.AddRecord(context.Background(), 123, record) + err := client.AddRecord(t.Context(), 123, record) require.NoError(t, err) } @@ -152,7 +151,7 @@ func TestClient_AddRecord_error(t *testing.T) { Type: "TXT", } - err := client.AddRecord(context.Background(), 123, record) + err := client.AddRecord(t.Context(), 123, record) require.EqualError(t, err, "[status code: 400] status: invalid_resource, details: name: [muss ausgefüllt werden]") } @@ -161,7 +160,7 @@ func TestClient_UpdateRecord(t *testing.T) { mux.HandleFunc("/domains/123/records/456", testHandler("ok.json", http.MethodPut, http.StatusOK)) - err := client.UpdateRecord(context.Background(), 123, 456, Record{}) + err := client.UpdateRecord(t.Context(), 123, 456, Record{}) require.NoError(t, err) } @@ -170,7 +169,7 @@ func TestClient_UpdateRecord_error(t *testing.T) { mux.HandleFunc("/domains/123/records/456", testHandler("error.json", http.MethodPut, http.StatusBadRequest)) - err := client.UpdateRecord(context.Background(), 123, 456, Record{}) + err := client.UpdateRecord(t.Context(), 123, 456, Record{}) require.EqualError(t, err, "[status code: 400] status: invalid_resource, details: name: [muss ausgefüllt werden]") } @@ -179,7 +178,7 @@ func TestClient_DeleteRecord(t *testing.T) { mux.HandleFunc("/domains/123/records/456", testHandler("ok.json", http.MethodDelete, http.StatusOK)) - err := client.DeleteRecord(context.Background(), 123, 456) + err := client.DeleteRecord(t.Context(), 123, 456) require.NoError(t, err) } @@ -188,6 +187,6 @@ func TestClient_DeleteRecord_error(t *testing.T) { mux.HandleFunc("/domains/123/records/456", testHandler("error.json", http.MethodDelete, http.StatusBadRequest)) - err := client.DeleteRecord(context.Background(), 123, 456) + err := client.DeleteRecord(t.Context(), 123, 456) require.EqualError(t, err, "[status code: 400] status: invalid_resource, details: name: [muss ausgefüllt werden]") } diff --git a/providers/dns/limacity/limacity.go b/providers/dns/limacity/limacity.go index ef2c6950d..58755aabe 100644 --- a/providers/dns/limacity/limacity.go +++ b/providers/dns/limacity/limacity.go @@ -14,7 +14,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/limacity/internal" - "github.com/miekg/dns" ) // Environment variables names. @@ -185,10 +184,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } func findDomain(domains []internal.Domain, fqdn string) (internal.Domain, error) { - labelIndexes := dns.Split(fqdn) - - for _, index := range labelIndexes { - f := fqdn[index:] + for f := range dns01.DomainsSeq(fqdn) { domain := dns01.UnFqdn(f) for _, dom := range domains { diff --git a/providers/dns/linode/linode_test.go b/providers/dns/linode/linode_test.go index 70b33eda4..a6b8041f8 100644 --- a/providers/dns/linode/linode_test.go +++ b/providers/dns/linode/linode_test.go @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/require" ) -type MockResponseMap map[string]interface{} +type MockResponseMap map[string]any var envTest = tester.NewEnvTest(EnvToken) diff --git a/providers/dns/loopia/internal/client.go b/providers/dns/loopia/internal/client.go index d521ffeec..0e9513024 100644 --- a/providers/dns/loopia/internal/client.go +++ b/providers/dns/loopia/internal/client.go @@ -37,7 +37,7 @@ func NewClient(apiUser, apiPassword string) *Client { } // AddTXTRecord adds a TXT record. -func (c *Client) AddTXTRecord(ctx context.Context, domain string, subdomain string, ttl int, value string) error { +func (c *Client) AddTXTRecord(ctx context.Context, domain, subdomain string, ttl int, value string) error { call := &methodCall{ MethodName: "addZoneRecord", Params: []param{ @@ -67,7 +67,7 @@ func (c *Client) AddTXTRecord(ctx context.Context, domain string, subdomain stri } // RemoveTXTRecord removes a TXT record. -func (c *Client) RemoveTXTRecord(ctx context.Context, domain string, subdomain string, recordID int) error { +func (c *Client) RemoveTXTRecord(ctx context.Context, domain, subdomain string, recordID int) error { call := &methodCall{ MethodName: "removeZoneRecord", Params: []param{ @@ -89,7 +89,7 @@ func (c *Client) RemoveTXTRecord(ctx context.Context, domain string, subdomain s } // GetTXTRecords gets TXT records. -func (c *Client) GetTXTRecords(ctx context.Context, domain string, subdomain string) ([]RecordObj, error) { +func (c *Client) GetTXTRecords(ctx context.Context, domain, subdomain string) ([]RecordObj, error) { call := &methodCall{ MethodName: "getZoneRecords", Params: []param{ diff --git a/providers/dns/loopia/internal/client_test.go b/providers/dns/loopia/internal/client_test.go index b99e9b202..a84b7c9ad 100644 --- a/providers/dns/loopia/internal/client_test.go +++ b/providers/dns/loopia/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "encoding/xml" "fmt" "io" @@ -59,7 +58,7 @@ func TestClient_AddZoneRecord(t *testing.T) { client := NewClient("apiuser", test.password) client.BaseURL = serverURL + "/" - err := client.AddTXTRecord(context.Background(), test.domain, exampleSubDomain, 123, "TXTrecord") + err := client.AddTXTRecord(t.Context(), test.domain, exampleSubDomain, 123, "TXTrecord") if test.err == "" { require.NoError(t, err) } else { @@ -116,7 +115,7 @@ func TestClient_RemoveSubdomain(t *testing.T) { client := NewClient("apiuser", test.password) client.BaseURL = serverURL + "/" - err := client.RemoveSubdomain(context.Background(), test.domain, exampleSubDomain) + err := client.RemoveSubdomain(t.Context(), test.domain, exampleSubDomain) if test.err == "" { require.NoError(t, err) } else { @@ -173,7 +172,7 @@ func TestClient_RemoveZoneRecord(t *testing.T) { client := NewClient("apiuser", test.password) client.BaseURL = serverURL + "/" - err := client.RemoveTXTRecord(context.Background(), test.domain, exampleSubDomain, 12345678) + err := client.RemoveTXTRecord(t.Context(), test.domain, exampleSubDomain, 12345678) if test.err == "" { require.NoError(t, err) } else { @@ -194,7 +193,7 @@ func TestClient_GetZoneRecord(t *testing.T) { client := NewClient("apiuser", "goodpassword") client.BaseURL = serverURL + "/" - recordObjs, err := client.GetTXTRecords(context.Background(), exampleDomain, exampleSubDomain) + recordObjs, err := client.GetTXTRecords(t.Context(), exampleDomain, exampleSubDomain) require.NoError(t, err) expected := []RecordObj{ @@ -238,7 +237,7 @@ func TestClient_rpcCall_404(t *testing.T) { client := NewClient("apiuser", "apipassword") client.BaseURL = server.URL + "/" - err := client.rpcCall(context.Background(), call, &responseString{}) + err := client.rpcCall(t.Context(), call, &responseString{}) require.EqualError(t, err, "unexpected status code: [status code: 404] body: ") } @@ -269,7 +268,7 @@ func TestClient_rpcCall_RPCError(t *testing.T) { client := NewClient("apiuser", "apipassword") client.BaseURL = server.URL + "/" - err := client.rpcCall(context.Background(), call, &responseString{}) + err := client.rpcCall(t.Context(), call, &responseString{}) require.EqualError(t, err, "RPC Error: (201) Method signature error: 42") } diff --git a/providers/dns/loopia/loopia.go b/providers/dns/loopia/loopia.go index 3ba6018b9..8389ae5f6 100644 --- a/providers/dns/loopia/loopia.go +++ b/providers/dns/loopia/loopia.go @@ -34,9 +34,9 @@ const minTTL = 300 var _ challenge.ProviderTimeout = (*DNSProvider)(nil) type dnsClient interface { - AddTXTRecord(ctx context.Context, domain string, subdomain string, ttl int, value string) error - RemoveTXTRecord(ctx context.Context, domain string, subdomain string, recordID int) error - GetTXTRecords(ctx context.Context, domain string, subdomain string) ([]internal.RecordObj, error) + AddTXTRecord(ctx context.Context, domain, subdomain string, ttl int, value string) error + RemoveTXTRecord(ctx context.Context, domain, subdomain string, recordID int) error + GetTXTRecords(ctx context.Context, domain, subdomain string) ([]internal.RecordObj, error) RemoveSubdomain(ctx context.Context, domain, subdomain string) error } diff --git a/providers/dns/loopia/loopia_mock_test.go b/providers/dns/loopia/loopia_mock_test.go index 93f26af06..fb0bcaa2b 100644 --- a/providers/dns/loopia/loopia_mock_test.go +++ b/providers/dns/loopia/loopia_mock_test.go @@ -215,17 +215,17 @@ type mockedClient struct { mock.Mock } -func (c *mockedClient) RemoveTXTRecord(ctx context.Context, domain string, subdomain string, recordID int) error { +func (c *mockedClient) RemoveTXTRecord(ctx context.Context, domain, subdomain string, recordID int) error { args := c.Called(domain, subdomain, recordID) return args.Error(0) } -func (c *mockedClient) AddTXTRecord(ctx context.Context, domain string, subdomain string, ttl int, value string) error { +func (c *mockedClient) AddTXTRecord(ctx context.Context, domain, subdomain string, ttl int, value string) error { args := c.Called(domain, subdomain, ttl, value) return args.Error(0) } -func (c *mockedClient) GetTXTRecords(ctx context.Context, domain string, subdomain string) ([]internal.RecordObj, error) { +func (c *mockedClient) GetTXTRecords(ctx context.Context, domain, subdomain string) ([]internal.RecordObj, error) { args := c.Called(domain, subdomain) return args.Get(0).([]internal.RecordObj), args.Error(1) } diff --git a/providers/dns/luadns/internal/client_test.go b/providers/dns/luadns/internal/client_test.go index 1fd3efd74..1b09814ef 100644 --- a/providers/dns/luadns/internal/client_test.go +++ b/providers/dns/luadns/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -57,7 +56,7 @@ func TestClient_ListZones(t *testing.T) { } }) - zones, err := client.ListZones(context.Background()) + zones, err := client.ListZones(t.Context()) require.NoError(t, err) expected := []DNSZone{ @@ -126,7 +125,7 @@ func TestClient_CreateRecord(t *testing.T) { TTL: 300, } - newRecord, err := client.CreateRecord(context.Background(), zone, record) + newRecord, err := client.CreateRecord(t.Context(), zone, record) require.NoError(t, err) expected := &DNSRecord{ @@ -179,6 +178,6 @@ func TestClient_DeleteRecord(t *testing.T) { ZoneID: 1, } - err := client.DeleteRecord(context.Background(), record) + err := client.DeleteRecord(t.Context(), record) require.NoError(t, err) } diff --git a/providers/dns/manageengine/internal/client.go b/providers/dns/manageengine/internal/client.go index 89c426b02..b360840f0 100644 --- a/providers/dns/manageengine/internal/client.go +++ b/providers/dns/manageengine/internal/client.go @@ -75,7 +75,7 @@ func (c *Client) GetAllZoneRecords(ctx context.Context, zoneID int) ([]ZoneRecor // DeleteZoneRecord deletes a "zone record". // https://pitstop.manageengine.com/portal/en/kb/articles/manageengine-clouddns-rest-api-documentation#DEL_Delete_10 -func (c *Client) DeleteZoneRecord(ctx context.Context, zoneID int, domainID int) error { +func (c *Client) DeleteZoneRecord(ctx context.Context, zoneID, domainID int) error { endpoint := c.baseURL.JoinPath("dns", "domain", strconv.Itoa(zoneID), "records", "SPF_TXT", strconv.Itoa(domainID)) req, err := newRequest(ctx, http.MethodDelete, endpoint, nil) diff --git a/providers/dns/manageengine/internal/client_test.go b/providers/dns/manageengine/internal/client_test.go index edf046222..a47d0b9a8 100644 --- a/providers/dns/manageengine/internal/client_test.go +++ b/providers/dns/manageengine/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "io" "net/http" "net/http/httptest" @@ -43,7 +42,7 @@ func setupTest(t *testing.T, pattern string, status int, filename string) *Clien } }) - client := NewClient(context.Background(), "abc", "secret") + client := NewClient(t.Context(), "abc", "secret") client.httpClient = server.Client() client.baseURL, _ = url.Parse(server.URL) @@ -54,7 +53,7 @@ func setupTest(t *testing.T, pattern string, status int, filename string) *Clien func TestClient_GetAllZones(t *testing.T) { client := setupTest(t, "GET /dns/domain", http.StatusOK, "zone_domains_all.json") - groups, err := client.GetAllZones(context.Background()) + groups, err := client.GetAllZones(t.Context()) require.NoError(t, err) expected := []Zone{ @@ -135,7 +134,7 @@ func TestClient_GetAllZones(t *testing.T) { func TestClient_GetAllZones_error(t *testing.T) { client := setupTest(t, "GET /dns/domain", http.StatusUnauthorized, "error.json") - _, err := client.GetAllZones(context.Background()) + _, err := client.GetAllZones(t.Context()) require.Error(t, err) require.EqualError(t, err, "[status code: 401] Authentication credentials were not provided.") @@ -144,7 +143,7 @@ func TestClient_GetAllZones_error(t *testing.T) { func TestClient_GetAllZoneRecords(t *testing.T) { client := setupTest(t, "GET /dns/domain/4/records/SPF_TXT", http.StatusOK, "zone_records_all.json") - groups, err := client.GetAllZoneRecords(context.Background(), 4) + groups, err := client.GetAllZoneRecords(t.Context(), 4) require.NoError(t, err) expected := []ZoneRecord{ @@ -182,7 +181,7 @@ func TestClient_GetAllZoneRecords(t *testing.T) { func TestClient_GetAllZoneRecords_error(t *testing.T) { client := setupTest(t, "GET /dns/domain/4/records/SPF_TXT", http.StatusUnauthorized, "error.json") - _, err := client.GetAllZoneRecords(context.Background(), 4) + _, err := client.GetAllZoneRecords(t.Context(), 4) require.Error(t, err) require.EqualError(t, err, "[status code: 401] Authentication credentials were not provided.") @@ -191,14 +190,14 @@ func TestClient_GetAllZoneRecords_error(t *testing.T) { func TestClient_DeleteZoneRecord(t *testing.T) { client := setupTest(t, "DELETE /dns/domain/4/records/SPF_TXT/6", http.StatusOK, "zone_record_delete.json") - err := client.DeleteZoneRecord(context.Background(), 4, 6) + err := client.DeleteZoneRecord(t.Context(), 4, 6) require.NoError(t, err) } func TestClient_DeleteZoneRecord_error(t *testing.T) { client := setupTest(t, "DELETE /dns/domain/4/records/SPF_TXT/6", http.StatusUnauthorized, "error.json") - err := client.DeleteZoneRecord(context.Background(), 4, 6) + err := client.DeleteZoneRecord(t.Context(), 4, 6) require.Error(t, err) require.EqualError(t, err, "[status code: 401] Authentication credentials were not provided.") @@ -209,7 +208,7 @@ func TestClient_CreateZoneRecord(t *testing.T) { record := ZoneRecord{} - err := client.CreateZoneRecord(context.Background(), 4, record) + err := client.CreateZoneRecord(t.Context(), 4, record) require.NoError(t, err) } @@ -218,7 +217,7 @@ func TestClient_CreateZoneRecord_error(t *testing.T) { record := ZoneRecord{} - err := client.CreateZoneRecord(context.Background(), 4, record) + err := client.CreateZoneRecord(t.Context(), 4, record) require.Error(t, err) require.EqualError(t, err, "[status code: 401] Authentication credentials were not provided.") @@ -229,7 +228,7 @@ func TestClient_CreateZoneRecord_error_bad_request(t *testing.T) { record := ZoneRecord{} - err := client.CreateZoneRecord(context.Background(), 4, record) + err := client.CreateZoneRecord(t.Context(), 4, record) require.Error(t, err) require.EqualError(t, err, "[status code: 400] Invalid record format, Record should be in list.") @@ -243,7 +242,7 @@ func TestClient_UpdateZoneRecord(t *testing.T) { ZoneID: 4, } - err := client.UpdateZoneRecord(context.Background(), record) + err := client.UpdateZoneRecord(t.Context(), record) require.NoError(t, err) } @@ -255,7 +254,7 @@ func TestClient_UpdateZoneRecord_error(t *testing.T) { ZoneID: 4, } - err := client.UpdateZoneRecord(context.Background(), record) + err := client.UpdateZoneRecord(t.Context(), record) require.Error(t, err) require.EqualError(t, err, "[status code: 401] Authentication credentials were not provided.") diff --git a/providers/dns/metaregistrar/internal/client_test.go b/providers/dns/metaregistrar/internal/client_test.go index 1015ba8c8..8486fc899 100644 --- a/providers/dns/metaregistrar/internal/client_test.go +++ b/providers/dns/metaregistrar/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "io" "net/http" "net/http/httptest" @@ -64,7 +63,7 @@ func TestClient_UpdateDNSZone(t *testing.T) { }}, } - response, err := client.UpdateDNSZone(context.Background(), "example.com", updateRequest) + response, err := client.UpdateDNSZone(t.Context(), "example.com", updateRequest) require.NoError(t, err) expected := &DNSZoneUpdateResponse{ @@ -107,7 +106,7 @@ func TestClient_UpdateDNSZone_error(t *testing.T) { }}, } - _, err := client.UpdateDNSZone(context.Background(), "example.com", updateRequest) + _, err := client.UpdateDNSZone(t.Context(), "example.com", updateRequest) require.EqualError(t, err, test.expected) }) } diff --git a/providers/dns/mijnhost/internal/client_test.go b/providers/dns/mijnhost/internal/client_test.go index 876ca5e1c..a1dc326b7 100644 --- a/providers/dns/mijnhost/internal/client_test.go +++ b/providers/dns/mijnhost/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -30,7 +29,7 @@ func setupTest(t *testing.T) (*Client, *http.ServeMux) { return client, mux } -func testHandler(filename string, method string, statusCode int) http.HandlerFunc { +func testHandler(filename, method string, statusCode int) http.HandlerFunc { return func(rw http.ResponseWriter, req *http.Request) { if req.Method != method { http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) @@ -66,7 +65,7 @@ func TestClient_ListDomains(t *testing.T) { mux.HandleFunc("/domains", testHandler("./list-domains.json", http.MethodGet, http.StatusOK)) - domains, err := client.ListDomains(context.Background()) + domains, err := client.ListDomains(t.Context()) require.NoError(t, err) expected := []Domain{{ @@ -86,7 +85,7 @@ func TestClient_GetRecords(t *testing.T) { mux.HandleFunc("/domains/example.com/dns", testHandler("./get-dns-records.json", http.MethodGet, http.StatusOK)) - records, err := client.GetRecords(context.Background(), "example.com") + records, err := client.GetRecords(t.Context(), "example.com") require.NoError(t, err) expected := []Record{ @@ -124,6 +123,6 @@ func TestClient_UpdateRecords(t *testing.T) { mux.HandleFunc("/domains/example.com/dns", testHandler("./update-dns-records.json", http.MethodPut, http.StatusOK)) - err := client.UpdateRecords(context.Background(), "example.com", nil) + err := client.UpdateRecords(t.Context(), "example.com", nil) require.NoError(t, err) } diff --git a/providers/dns/mijnhost/mijnhost.go b/providers/dns/mijnhost/mijnhost.go index bbdd810b9..21aca3c9e 100644 --- a/providers/dns/mijnhost/mijnhost.go +++ b/providers/dns/mijnhost/mijnhost.go @@ -12,7 +12,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/mijnhost/internal" - "github.com/miekg/dns" ) // Environment variables names. @@ -184,11 +183,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } func findDomain(domains []internal.Domain, fqdn string) (internal.Domain, error) { - labelIndexes := dns.Split(fqdn) - - for _, index := range labelIndexes { - domain := dns01.UnFqdn(fqdn[index:]) - + for domain := range dns01.UnFqdnDomainsSeq(fqdn) { for _, dom := range domains { if dom.Domain == domain { return dom, nil diff --git a/providers/dns/mittwald/internal/client_test.go b/providers/dns/mittwald/internal/client_test.go index 63fc52004..f73a36cc1 100644 --- a/providers/dns/mittwald/internal/client_test.go +++ b/providers/dns/mittwald/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -69,7 +68,7 @@ func testHandler(method string, statusCode int, filename string) http.HandlerFun func TestClient_ListDomains(t *testing.T) { client := setupTest(t, "/domains", testHandler(http.MethodGet, http.StatusOK, "domain-list-domains.json")) - domains, err := client.ListDomains(context.Background()) + domains, err := client.ListDomains(t.Context()) require.NoError(t, err) require.Len(t, domains, 1) @@ -86,14 +85,14 @@ func TestClient_ListDomains(t *testing.T) { func TestClient_ListDomains_error(t *testing.T) { client := setupTest(t, "/domains", testHandler(http.MethodGet, http.StatusBadRequest, "error-client.json")) - _, err := client.ListDomains(context.Background()) + _, err := client.ListDomains(t.Context()) require.EqualError(t, err, "[status code 400] ValidationError: Validation failed [format: should be string (.address.street, email)]") } func TestClient_ListDNSZones(t *testing.T) { client := setupTest(t, "/projects/my-project-id/dns-zones", testHandler(http.MethodGet, http.StatusOK, "dns-list-dns-zones.json")) - zones, err := client.ListDNSZones(context.Background(), "my-project-id") + zones, err := client.ListDNSZones(t.Context(), "my-project-id") require.NoError(t, err) require.Len(t, zones, 1) @@ -112,7 +111,7 @@ func TestClient_ListDNSZones(t *testing.T) { func TestClient_GetDNSZone(t *testing.T) { client := setupTest(t, "/dns-zones/my-zone-id", testHandler(http.MethodGet, http.StatusOK, "dns-get-dns-zone.json")) - zone, err := client.GetDNSZone(context.Background(), "my-zone-id") + zone, err := client.GetDNSZone(t.Context(), "my-zone-id") require.NoError(t, err) expected := &DNSZone{ @@ -134,7 +133,7 @@ func TestClient_CreateDNSZone(t *testing.T) { ParentZoneID: "my-parent-zone-id", } - zone, err := client.CreateDNSZone(context.Background(), request) + zone, err := client.CreateDNSZone(t.Context(), request) require.NoError(t, err) expected := &DNSZone{ @@ -154,20 +153,20 @@ func TestClient_UpdateTXTRecord(t *testing.T) { Entries: []string{"txt"}, } - err := client.UpdateTXTRecord(context.Background(), "my-zone-id", record) + err := client.UpdateTXTRecord(t.Context(), "my-zone-id", record) require.NoError(t, err) } func TestClient_DeleteDNSZone(t *testing.T) { client := setupTest(t, "/dns-zones/my-zone-id", testHandler(http.MethodDelete, http.StatusOK, "")) - err := client.DeleteDNSZone(context.Background(), "my-zone-id") + err := client.DeleteDNSZone(t.Context(), "my-zone-id") require.NoError(t, err) } func TestClient_DeleteDNSZone_error(t *testing.T) { client := setupTest(t, "/dns-zones/my-zone-id", testHandler(http.MethodDelete, http.StatusInternalServerError, "error.json")) - err := client.DeleteDNSZone(context.Background(), "my-zone-id") + err := client.DeleteDNSZone(t.Context(), "my-zone-id") assert.EqualError(t, err, "[status code 500] InternalServerError: Something went wrong") } diff --git a/providers/dns/mittwald/mittwald.go b/providers/dns/mittwald/mittwald.go index 47c62be52..2c3c5a8f3 100644 --- a/providers/dns/mittwald/mittwald.go +++ b/providers/dns/mittwald/mittwald.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/mittwald/internal" - "github.com/miekg/dns" ) // Environment variables names. @@ -212,11 +211,7 @@ func (d *DNSProvider) getOrCreateZone(ctx context.Context, fqdn string) (*intern } func findDomain(domains []internal.Domain, fqdn string) (internal.Domain, error) { - labelIndexes := dns.Split(fqdn) - - for _, index := range labelIndexes { - domain := dns01.UnFqdn(fqdn[index:]) - + for domain := range dns01.UnFqdnDomainsSeq(fqdn) { for _, dom := range domains { if dom.Domain == domain { return dom, nil @@ -228,11 +223,7 @@ func findDomain(domains []internal.Domain, fqdn string) (internal.Domain, error) } func findZone(zones []internal.DNSZone, fqdn string) (internal.DNSZone, error) { - labelIndexes := dns.Split(fqdn) - - for _, index := range labelIndexes { - domain := dns01.UnFqdn(fqdn[index:]) - + for domain := range dns01.UnFqdnDomainsSeq(fqdn) { for _, zon := range zones { if zon.Domain == domain { return zon, nil diff --git a/providers/dns/myaddr/internal/client_test.go b/providers/dns/myaddr/internal/client_test.go index f74e42eb1..794a501fb 100644 --- a/providers/dns/myaddr/internal/client_test.go +++ b/providers/dns/myaddr/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "io" "net/http" "net/http/httptest" @@ -58,20 +57,20 @@ func setupTest(t *testing.T, pattern string, status int, filename string) *Clien func TestClient_AddTXTRecord(t *testing.T) { client := setupTest(t, "POST /update", http.StatusOK, "") - err := client.AddTXTRecord(context.Background(), "example", "txt") + err := client.AddTXTRecord(t.Context(), "example", "txt") require.NoError(t, err) } func TestClient_AddTXTRecord_error(t *testing.T) { client := setupTest(t, "POST /update", http.StatusBadRequest, "error.txt") - err := client.AddTXTRecord(context.Background(), "example", "txt") + err := client.AddTXTRecord(t.Context(), "example", "txt") require.EqualError(t, err, `unexpected status code: [status code: 400] body: invalid value for "key"`) } func TestClient_AddTXTRecord_error_credentials(t *testing.T) { client := setupTest(t, "POST /update", http.StatusOK, "") - err := client.AddTXTRecord(context.Background(), "nx", "txt") + err := client.AddTXTRecord(t.Context(), "nx", "txt") require.EqualError(t, err, "subdomain nx not found in credentials, check your credentials map") } diff --git a/providers/dns/myaddr/myaddr.go b/providers/dns/myaddr/myaddr.go index 57d46f514..df280f2f4 100644 --- a/providers/dns/myaddr/myaddr.go +++ b/providers/dns/myaddr/myaddr.go @@ -66,7 +66,7 @@ func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() - credentials, err := parseCredentials(values[EnvPrivateKeysMapping]) + credentials, err := env.ParsePairs(values[EnvPrivateKeysMapping]) if err != nil { return nil, fmt.Errorf("myaddr: %w", err) } @@ -142,19 +142,3 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { func (d *DNSProvider) Sequential() time.Duration { return d.config.SequenceInterval } - -func parseCredentials(raw string) (map[string]string, error) { - credentials := make(map[string]string) - - credStrings := strings.Split(strings.TrimSuffix(raw, ","), ",") - for _, credPair := range credStrings { - data := strings.Split(credPair, ":") - if len(data) != 2 { - return nil, fmt.Errorf("incorrect credential pair: %s", credPair) - } - - credentials[strings.TrimSpace(data[0])] = strings.TrimSpace(data[1]) - } - - return credentials, nil -} diff --git a/providers/dns/mydnsjp/internal/client.go b/providers/dns/mydnsjp/internal/client.go index 9859ed685..8b6824c29 100644 --- a/providers/dns/mydnsjp/internal/client.go +++ b/providers/dns/mydnsjp/internal/client.go @@ -23,7 +23,7 @@ type Client struct { } // NewClient Creates a new Client. -func NewClient(masterID string, password string) *Client { +func NewClient(masterID, password string) *Client { baseURL, _ := url.Parse(defaultBaseURL) return &Client{ diff --git a/providers/dns/mydnsjp/internal/client_test.go b/providers/dns/mydnsjp/internal/client_test.go index a68f6888b..a0f9ab8c7 100644 --- a/providers/dns/mydnsjp/internal/client_test.go +++ b/providers/dns/mydnsjp/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -80,13 +79,13 @@ func setupTest(t *testing.T, cmdName string) *Client { func TestClient_AddTXTRecord(t *testing.T) { client := setupTest(t, "REGIST") - err := client.AddTXTRecord(context.Background(), "example.com", "txt") + err := client.AddTXTRecord(t.Context(), "example.com", "txt") require.NoError(t, err) } func TestClient_DeleteTXTRecord(t *testing.T) { client := setupTest(t, "DELETE") - err := client.DeleteTXTRecord(context.Background(), "example.com", "txt") + err := client.DeleteTXTRecord(t.Context(), "example.com", "txt") require.NoError(t, err) } diff --git a/providers/dns/mythicbeasts/internal/client.go b/providers/dns/mythicbeasts/internal/client.go index 91fbbaf54..87464553c 100644 --- a/providers/dns/mythicbeasts/internal/client.go +++ b/providers/dns/mythicbeasts/internal/client.go @@ -35,7 +35,7 @@ type Client struct { } // NewClient Creates a new Client. -func NewClient(username string, password string) *Client { +func NewClient(username, password string) *Client { apiEndpoint, _ := url.Parse(APIBaseURL) authEndpoint, _ := url.Parse(AuthBaseURL) diff --git a/providers/dns/mythicbeasts/internal/client_test.go b/providers/dns/mythicbeasts/internal/client_test.go index 7e3857986..1e5f83e3c 100644 --- a/providers/dns/mythicbeasts/internal/client_test.go +++ b/providers/dns/mythicbeasts/internal/client_test.go @@ -57,13 +57,13 @@ func writeFixtureHandler(method, filename string) http.HandlerFunc { func TestClient_CreateTXTRecord(t *testing.T) { client := setupTest(t, "/zones/example.com/records/foo/TXT", writeFixtureHandler(http.MethodPost, "post-zoneszonerecords.json")) - err := client.CreateTXTRecord(mockContext(), "example.com", "foo", "txt", 120) + err := client.CreateTXTRecord(mockContext(t), "example.com", "foo", "txt", 120) require.NoError(t, err) } func TestClient_RemoveTXTRecord(t *testing.T) { client := setupTest(t, "/zones/example.com/records/foo/TXT", writeFixtureHandler(http.MethodDelete, "delete-zoneszonerecords.json")) - err := client.RemoveTXTRecord(mockContext(), "example.com", "foo", "txt") + err := client.RemoveTXTRecord(mockContext(t), "example.com", "foo", "txt") require.NoError(t, err) } diff --git a/providers/dns/mythicbeasts/internal/identity_test.go b/providers/dns/mythicbeasts/internal/identity_test.go index 9d8daf827..e26bad6aa 100644 --- a/providers/dns/mythicbeasts/internal/identity_test.go +++ b/providers/dns/mythicbeasts/internal/identity_test.go @@ -13,8 +13,10 @@ import ( "github.com/stretchr/testify/require" ) -func mockContext() context.Context { - return context.WithValue(context.Background(), tokenKey, &Token{Token: "xxx"}) +func mockContext(t *testing.T) context.Context { + t.Helper() + + return context.WithValue(t.Context(), tokenKey, &Token{Token: "xxx"}) } func tokenHandler(rw http.ResponseWriter, req *http.Request) { @@ -49,7 +51,7 @@ func TestClient_obtainToken(t *testing.T) { assert.Nil(t, client.token) - tok, err := client.obtainToken(context.Background()) + tok, err := client.obtainToken(t.Context()) require.NoError(t, err) assert.NotNil(t, tok) @@ -70,7 +72,7 @@ func TestClient_CreateAuthenticatedContext(t *testing.T) { assert.Nil(t, client.token) - ctx, err := client.CreateAuthenticatedContext(context.Background()) + ctx, err := client.CreateAuthenticatedContext(t.Context()) require.NoError(t, err) tok := getToken(ctx) diff --git a/providers/dns/namecheap/internal/client.go b/providers/dns/namecheap/internal/client.go index f7ca8f66f..0fb32b1be 100644 --- a/providers/dns/namecheap/internal/client.go +++ b/providers/dns/namecheap/internal/client.go @@ -32,7 +32,7 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(apiUser string, apiKey string, clientIP string) *Client { +func NewClient(apiUser, apiKey, clientIP string) *Client { return &Client{ apiUser: apiUser, apiKey: apiKey, diff --git a/providers/dns/namecheap/internal/client_test.go b/providers/dns/namecheap/internal/client_test.go index 9d78ee213..6a6ba201a 100644 --- a/providers/dns/namecheap/internal/client_test.go +++ b/providers/dns/namecheap/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -69,7 +68,7 @@ func TestClient_GetHosts(t *testing.T) { writeFixture(rw, "getHosts.xml") }) - hosts, err := client.GetHosts(context.Background(), "foo", "example.com") + hosts, err := client.GetHosts(t.Context(), "foo", "example.com") require.NoError(t, err) expected := []Record{ @@ -90,7 +89,7 @@ func TestClient_GetHosts_error(t *testing.T) { writeFixture(rw, "getHosts_errorBadAPIKey1.xml") }) - _, err := client.GetHosts(context.Background(), "foo", "example.com") + _, err := client.GetHosts(t.Context(), "foo", "example.com") require.ErrorAs(t, err, &apiError{}) } @@ -149,7 +148,7 @@ func TestClient_SetHosts(t *testing.T) { {Name: "_acme-challenge.test.example.org", Type: "TXT", Address: "txtTXTtxt", MXPref: "10", TTL: "120"}, } - err := client.SetHosts(context.Background(), "foo", "example.com", records) + err := client.SetHosts(t.Context(), "foo", "example.com", records) require.NoError(t, err) } @@ -168,6 +167,6 @@ func TestClient_SetHosts_error(t *testing.T) { {Name: "_acme-challenge.test.example.org", Type: "TXT", Address: "txtTXTtxt", MXPref: "10", TTL: "120"}, } - err := client.SetHosts(context.Background(), "foo", "example.com", records) + err := client.SetHosts(t.Context(), "foo", "example.com", records) require.ErrorAs(t, err, &apiError{}) } diff --git a/providers/dns/nearlyfreespeech/internal/client.go b/providers/dns/nearlyfreespeech/internal/client.go index 08d8d511f..69ed8d064 100644 --- a/providers/dns/nearlyfreespeech/internal/client.go +++ b/providers/dns/nearlyfreespeech/internal/client.go @@ -34,7 +34,7 @@ type Client struct { HTTPClient *http.Client } -func NewClient(login string, apiKey string) *Client { +func NewClient(login, apiKey string) *Client { baseURL, _ := url.Parse(apiURL) return &Client{ @@ -114,7 +114,7 @@ func NewSigner() *Signer { return &Signer{saltShaker: getRandomSalt, clock: time.Now} } -func (c Signer) Sign(uri string, body, login, apiKey string) string { +func (c Signer) Sign(uri, body, login, apiKey string) string { // Header is "login;timestamp;salt;hash". // hash is SHA1("login;timestamp;salt;api-key;request-uri;body-hash") // and body-hash is SHA1(body). diff --git a/providers/dns/nearlyfreespeech/internal/client_test.go b/providers/dns/nearlyfreespeech/internal/client_test.go index 935ee4fff..9c0329978 100644 --- a/providers/dns/nearlyfreespeech/internal/client_test.go +++ b/providers/dns/nearlyfreespeech/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -97,7 +96,7 @@ func TestClient_AddRecord(t *testing.T) { TTL: 30, } - err := client.AddRecord(context.Background(), "example.com", record) + err := client.AddRecord(t.Context(), "example.com", record) require.NoError(t, err) } @@ -113,7 +112,7 @@ func TestClient_AddRecord_error(t *testing.T) { TTL: 30, } - err := client.AddRecord(context.Background(), "example.com", record) + err := client.AddRecord(t.Context(), "example.com", record) require.Error(t, err) } @@ -134,7 +133,7 @@ func TestClient_RemoveRecord(t *testing.T) { Data: "txtTXTtxt", } - err := client.RemoveRecord(context.Background(), "example.com", record) + err := client.RemoveRecord(t.Context(), "example.com", record) require.NoError(t, err) } @@ -149,7 +148,7 @@ func TestClient_RemoveRecord_error(t *testing.T) { Data: "txtTXTtxt", } - err := client.RemoveRecord(context.Background(), "example.com", record) + err := client.RemoveRecord(t.Context(), "example.com", record) require.Error(t, err) } diff --git a/providers/dns/netcup/internal/client.go b/providers/dns/netcup/internal/client.go index 9573c09c8..553733175 100644 --- a/providers/dns/netcup/internal/client.go +++ b/providers/dns/netcup/internal/client.go @@ -142,7 +142,7 @@ func GetDNSRecordIdx(records []DNSRecord, record DNSRecord) (int, error) { return -1, errors.New("no DNS Record found") } -func newJSONRequest(ctx context.Context, method string, endpoint string, payload any) (*http.Request, error) { +func newJSONRequest(ctx context.Context, method, endpoint string, payload any) (*http.Request, error) { buf := new(bytes.Buffer) if payload != nil { diff --git a/providers/dns/netcup/internal/client_test.go b/providers/dns/netcup/internal/client_test.go index da70e65e0..501629e8f 100644 --- a/providers/dns/netcup/internal/client_test.go +++ b/providers/dns/netcup/internal/client_test.go @@ -2,7 +2,6 @@ package internal import ( "bytes" - "context" "fmt" "io" "net/http" @@ -212,7 +211,7 @@ func TestClient_GetDNSRecords(t *testing.T) { State: "yes", }} - records, err := client.GetDNSRecords(context.Background(), "example.com") + records, err := client.GetDNSRecords(t.Context(), "example.com") require.NoError(t, err) assert.Equal(t, expected, records) @@ -292,7 +291,7 @@ func TestClient_GetDNSRecords_errors(t *testing.T) { mux.HandleFunc("/", test.handler) - records, err := client.GetDNSRecords(context.Background(), "example.com") + records, err := client.GetDNSRecords(t.Context(), "example.com") require.Error(t, err) assert.Empty(t, records) }) @@ -313,7 +312,7 @@ func TestClient_GetDNSRecords_Live(t *testing.T) { envTest.GetValue("NETCUP_API_PASSWORD")) require.NoError(t, err) - ctx, err := client.CreateSessionContext(context.Background()) + ctx, err := client.CreateSessionContext(t.Context()) require.NoError(t, err) info := dns01.GetChallengeInfo(envTest.GetDomain(), "123d==") @@ -346,7 +345,7 @@ func TestClient_UpdateDNSRecord_Live(t *testing.T) { envTest.GetValue("NETCUP_API_PASSWORD")) require.NoError(t, err) - ctx, err := client.CreateSessionContext(context.Background()) + ctx, err := client.CreateSessionContext(t.Context()) require.NoError(t, err) info := dns01.GetChallengeInfo(envTest.GetDomain(), "123d==") diff --git a/providers/dns/netcup/internal/session_test.go b/providers/dns/netcup/internal/session_test.go index c5048500e..ceec56708 100644 --- a/providers/dns/netcup/internal/session_test.go +++ b/providers/dns/netcup/internal/session_test.go @@ -13,8 +13,10 @@ import ( "github.com/stretchr/testify/require" ) -func mockContext() context.Context { - return context.WithValue(context.Background(), sessionIDKey, "session-id") +func mockContext(t *testing.T) context.Context { + t.Helper() + + return context.WithValue(t.Context(), sessionIDKey, "session-id") } func TestClient_Login(t *testing.T) { @@ -53,7 +55,7 @@ func TestClient_Login(t *testing.T) { } }) - sessionID, err := client.login(context.Background()) + sessionID, err := client.login(t.Context()) require.NoError(t, err) assert.Equal(t, "api-session-id", sessionID) @@ -122,7 +124,7 @@ func TestClient_Login_errors(t *testing.T) { mux.HandleFunc("/", test.handler) - sessionID, err := client.login(context.Background()) + sessionID, err := client.login(t.Context()) assert.Error(t, err) assert.Empty(t, sessionID) }) @@ -162,7 +164,7 @@ func TestClient_Logout(t *testing.T) { } }) - err := client.Logout(mockContext()) + err := client.Logout(mockContext(t)) require.NoError(t, err) } @@ -208,7 +210,7 @@ func TestClient_Logout_errors(t *testing.T) { mux.HandleFunc("/", test.handler) - err := client.Logout(context.Background()) + err := client.Logout(t.Context()) require.Error(t, err) }) } @@ -232,7 +234,7 @@ func TestLiveClientAuth(t *testing.T) { t.Run("Test_"+strconv.Itoa(i+1), func(t *testing.T) { t.Parallel() - ctx, err := client.CreateSessionContext(context.Background()) + ctx, err := client.CreateSessionContext(t.Context()) require.NoError(t, err) err = client.Logout(ctx) diff --git a/providers/dns/netlify/internal/client.go b/providers/dns/netlify/internal/client.go index 06651bdec..a8e3b35c3 100644 --- a/providers/dns/netlify/internal/client.go +++ b/providers/dns/netlify/internal/client.go @@ -124,7 +124,7 @@ func (c *Client) RemoveRecord(ctx context.Context, zoneID, recordID string) erro return nil } -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload interface{}) (*http.Request, error) { +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { buf := new(bytes.Buffer) if payload != nil { diff --git a/providers/dns/netlify/internal/client_test.go b/providers/dns/netlify/internal/client_test.go index e06a579b7..a1e9e09a3 100644 --- a/providers/dns/netlify/internal/client_test.go +++ b/providers/dns/netlify/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -58,7 +57,7 @@ func TestClient_GetRecords(t *testing.T) { } }) - records, err := client.GetRecords(context.Background(), "zoneID") + records, err := client.GetRecords(t.Context(), "zoneID") require.NoError(t, err) expected := []DNSRecord{ @@ -108,7 +107,7 @@ func TestClient_CreateRecord(t *testing.T) { Value: "txtxtxtxtxtxt", } - result, err := client.CreateRecord(context.Background(), "zoneID", record) + result, err := client.CreateRecord(t.Context(), "zoneID", record) require.NoError(t, err) expected := &DNSRecord{ @@ -140,6 +139,6 @@ func TestClient_RemoveRecord(t *testing.T) { rw.WriteHeader(http.StatusNoContent) }) - err := client.RemoveRecord(context.Background(), "zoneID", "recordID") + err := client.RemoveRecord(t.Context(), "zoneID", "recordID") require.NoError(t, err) } diff --git a/providers/dns/nicmanager/internal/client_test.go b/providers/dns/nicmanager/internal/client_test.go index 822ec0db2..9c8679bea 100644 --- a/providers/dns/nicmanager/internal/client_test.go +++ b/providers/dns/nicmanager/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -18,7 +17,7 @@ import ( func TestClient_GetZone(t *testing.T) { client := setupTest(t, "/anycast/nicmanager-anycastdns4.net", testHandler(http.MethodGet, http.StatusOK, "zone.json")) - zone, err := client.GetZone(context.Background(), "nicmanager-anycastdns4.net") + zone, err := client.GetZone(t.Context(), "nicmanager-anycastdns4.net") require.NoError(t, err) expected := &Zone{ @@ -41,7 +40,7 @@ func TestClient_GetZone(t *testing.T) { func TestClient_GetZone_error(t *testing.T) { client := setupTest(t, "/anycast/foo", testHandler(http.MethodGet, http.StatusNotFound, "error.json")) - _, err := client.GetZone(context.Background(), "foo") + _, err := client.GetZone(t.Context(), "foo") require.Error(t, err) } @@ -55,7 +54,7 @@ func TestClient_AddRecord(t *testing.T) { TTL: 3600, } - err := client.AddRecord(context.Background(), "zonedomain.tld", record) + err := client.AddRecord(t.Context(), "zonedomain.tld", record) require.NoError(t, err) } @@ -69,21 +68,21 @@ func TestClient_AddRecord_error(t *testing.T) { TTL: 3600, } - err := client.AddRecord(context.Background(), "zonedomain.tld", record) + err := client.AddRecord(t.Context(), "zonedomain.tld", record) require.Error(t, err) } func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, "/anycast/zonedomain.tld/records/6", testHandler(http.MethodDelete, http.StatusAccepted, "error.json")) - err := client.DeleteRecord(context.Background(), "zonedomain.tld", 6) + err := client.DeleteRecord(t.Context(), "zonedomain.tld", 6) require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { client := setupTest(t, "/anycast/zonedomain.tld/records/6", testHandler(http.MethodDelete, http.StatusNoContent, "")) - err := client.DeleteRecord(context.Background(), "zonedomain.tld", 7) + err := client.DeleteRecord(t.Context(), "zonedomain.tld", 7) require.Error(t, err) } diff --git a/providers/dns/nicru/internal/client.go b/providers/dns/nicru/internal/client.go index 912de9692..37acd68f1 100644 --- a/providers/dns/nicru/internal/client.go +++ b/providers/dns/nicru/internal/client.go @@ -136,7 +136,7 @@ func (c *Client) GetRecords(ctx context.Context, serviceName, zoneName string) ( return records, nil } -func (c *Client) DeleteRecord(ctx context.Context, serviceName, zoneName string, id string) error { +func (c *Client) DeleteRecord(ctx context.Context, serviceName, zoneName, id string) error { endpoint := c.baseURL.JoinPath("services", serviceName, "zones", zoneName, "records", id) req, err := newXMLRequest(ctx, http.MethodDelete, endpoint, nil) diff --git a/providers/dns/nicru/internal/client_test.go b/providers/dns/nicru/internal/client_test.go index 461189e62..d49aa4014 100644 --- a/providers/dns/nicru/internal/client_test.go +++ b/providers/dns/nicru/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -32,7 +31,7 @@ func setupTest(t *testing.T, pattern string, handler http.HandlerFunc) *Client { return client } -func writeFixtures(method string, filename string, status int) http.HandlerFunc { +func writeFixtures(method, filename string, status int) http.HandlerFunc { return func(rw http.ResponseWriter, req *http.Request) { if req.Method != method { http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) @@ -60,7 +59,7 @@ func TestClient_GetServices(t *testing.T) { client := setupTest(t, "/services", writeFixtures(http.MethodGet, "services_GET.xml", http.StatusOK)) - zones, err := client.GetServices(context.Background()) + zones, err := client.GetServices(t.Context()) require.NoError(t, err) expected := []Service{ @@ -95,7 +94,7 @@ func TestClient_ListZones(t *testing.T) { client := setupTest(t, "/zones", writeFixtures(http.MethodGet, "zones_all_GET.xml", http.StatusOK)) - zones, err := client.ListZones(context.Background()) + zones, err := client.ListZones(t.Context()) require.NoError(t, err) expected := []Zone{ @@ -141,7 +140,7 @@ func TestClient_ListZones_error(t *testing.T) { client := setupTest(t, "/zones", writeFixtures(http.MethodGet, "errors.xml", http.StatusOK)) - _, err := client.ListZones(context.Background()) + _, err := client.ListZones(t.Context()) require.ErrorIs(t, err, Error{ Text: "Access token expired or not found", Code: "4097", @@ -152,7 +151,7 @@ func TestClient_GetZonesByService(t *testing.T) { client := setupTest(t, "/services/test/zones", writeFixtures(http.MethodGet, "zones_GET.xml", http.StatusOK)) - zones, err := client.GetZonesByService(context.Background(), "test") + zones, err := client.GetZonesByService(t.Context(), "test") require.NoError(t, err) expected := []Zone{ @@ -198,7 +197,7 @@ func TestClient_GetZonesByService_error(t *testing.T) { client := setupTest(t, "/services/test/zones", writeFixtures(http.MethodGet, "errors.xml", http.StatusOK)) - _, err := client.GetZonesByService(context.Background(), "test") + _, err := client.GetZonesByService(t.Context(), "test") require.ErrorIs(t, err, Error{ Text: "Access token expired or not found", Code: "4097", @@ -209,7 +208,7 @@ func TestClient_GetRecords(t *testing.T) { client := setupTest(t, "/services/test/zones/example.com./records", writeFixtures(http.MethodGet, "records_GET.xml", http.StatusOK)) - records, err := client.GetRecords(context.Background(), "test", "example.com.") + records, err := client.GetRecords(t.Context(), "test", "example.com.") require.NoError(t, err) expected := []RR{ @@ -274,7 +273,7 @@ func TestClient_GetRecords_error(t *testing.T) { client := setupTest(t, "/services/test/zones/example.com./records", writeFixtures(http.MethodGet, "errors.xml", http.StatusOK)) - _, err := client.GetRecords(context.Background(), "test", "example.com.") + _, err := client.GetRecords(t.Context(), "test", "example.com.") require.ErrorIs(t, err, Error{ Text: "Access token expired or not found", Code: "4097", @@ -298,7 +297,7 @@ func TestClient_AddRecord(t *testing.T) { }, } - response, err := client.AddRecords(context.Background(), "test", "example.com.", rrs) + response, err := client.AddRecords(t.Context(), "test", "example.com.", rrs) require.NoError(t, err) expected := []Zone{ @@ -354,7 +353,7 @@ func TestClient_AddRecord_error(t *testing.T) { }, } - _, err := client.AddRecords(context.Background(), "test", "example.com.", rrs) + _, err := client.AddRecords(t.Context(), "test", "example.com.", rrs) require.ErrorIs(t, err, Error{ Text: "Access token expired or not found", Code: "4097", @@ -365,7 +364,7 @@ func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, "/services/test/zones/example.com./records/123", writeFixtures(http.MethodDelete, "record_DELETE.xml", http.StatusUnauthorized)) - err := client.DeleteRecord(context.Background(), "test", "example.com.", "123") + err := client.DeleteRecord(t.Context(), "test", "example.com.", "123") require.NoError(t, err) } @@ -373,7 +372,7 @@ func TestClient_DeleteRecord_error(t *testing.T) { client := setupTest(t, "/services/test/zones/example.com./records/123", writeFixtures(http.MethodDelete, "errors.xml", http.StatusUnauthorized)) - err := client.DeleteRecord(context.Background(), "test", "example.com.", "123") + err := client.DeleteRecord(t.Context(), "test", "example.com.", "123") require.ErrorIs(t, err, Error{ Text: "Access token expired or not found", Code: "4097", @@ -383,14 +382,14 @@ func TestClient_DeleteRecord_error(t *testing.T) { func TestClient_CommitZone(t *testing.T) { client := setupTest(t, "/services/test/zones/example.com./commit", writeFixtures(http.MethodPost, "commit_POST.xml", http.StatusOK)) - err := client.CommitZone(context.Background(), "test", "example.com.") + err := client.CommitZone(t.Context(), "test", "example.com.") require.NoError(t, err) } func TestClient_CommitZone_error(t *testing.T) { client := setupTest(t, "/services/test/zones/example.com./commit", writeFixtures(http.MethodPost, "errors.xml", http.StatusOK)) - err := client.CommitZone(context.Background(), "test", "example.com.") + err := client.CommitZone(t.Context(), "test", "example.com.") require.ErrorIs(t, err, Error{ Text: "Access token expired or not found", Code: "4097", diff --git a/providers/dns/nifcloud/internal/client_test.go b/providers/dns/nifcloud/internal/client_test.go index 06c4921e0..91f9d36e2 100644 --- a/providers/dns/nifcloud/internal/client_test.go +++ b/providers/dns/nifcloud/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -45,7 +44,7 @@ func TestChangeResourceRecordSets(t *testing.T) { client := setupTest(t, responseBody, http.StatusOK) - res, err := client.ChangeResourceRecordSets(context.Background(), "example.com", ChangeResourceRecordSetsRequest{}) + res, err := client.ChangeResourceRecordSets(t.Context(), "example.com", ChangeResourceRecordSetsRequest{}) require.NoError(t, err) assert.Equal(t, "xxxxx", res.ChangeInfo.ID) @@ -92,7 +91,7 @@ func TestChangeResourceRecordSetsErrors(t *testing.T) { t.Run(test.desc, func(t *testing.T) { client := setupTest(t, test.responseBody, test.statusCode) - res, err := client.ChangeResourceRecordSets(context.Background(), "example.com", ChangeResourceRecordSetsRequest{}) + res, err := client.ChangeResourceRecordSets(t.Context(), "example.com", ChangeResourceRecordSetsRequest{}) assert.Nil(t, res) assert.EqualError(t, err, test.expected) }) @@ -112,7 +111,7 @@ func TestGetChange(t *testing.T) { client := setupTest(t, responseBody, http.StatusOK) - res, err := client.GetChange(context.Background(), "12345") + res, err := client.GetChange(t.Context(), "12345") require.NoError(t, err) assert.Equal(t, "xxxxx", res.ChangeInfo.ID) @@ -159,7 +158,7 @@ func TestGetChangeErrors(t *testing.T) { t.Run(test.desc, func(t *testing.T) { client := setupTest(t, test.responseBody, test.statusCode) - res, err := client.GetChange(context.Background(), "12345") + res, err := client.GetChange(t.Context(), "12345") assert.Nil(t, res) assert.EqualError(t, err, test.expected) }) diff --git a/providers/dns/njalla/internal/client.go b/providers/dns/njalla/internal/client.go index f7e0023ae..f64db3c80 100644 --- a/providers/dns/njalla/internal/client.go +++ b/providers/dns/njalla/internal/client.go @@ -55,7 +55,7 @@ func (c *Client) AddRecord(ctx context.Context, record Record) (*Record, error) } // RemoveRecord removes a record. -func (c *Client) RemoveRecord(ctx context.Context, id string, domain string) error { +func (c *Client) RemoveRecord(ctx context.Context, id, domain string) error { data := APIRequest{ Method: "remove-record", Params: Record{ @@ -127,7 +127,7 @@ func (c *Client) do(req *http.Request, result Response) error { return result.GetError() } -func newJSONRequest(ctx context.Context, method string, endpoint string, payload any) (*http.Request, error) { +func newJSONRequest(ctx context.Context, method, endpoint string, payload any) (*http.Request, error) { buf := new(bytes.Buffer) if payload != nil { diff --git a/providers/dns/njalla/internal/client_test.go b/providers/dns/njalla/internal/client_test.go index 3f173db62..9ad58f24b 100644 --- a/providers/dns/njalla/internal/client_test.go +++ b/providers/dns/njalla/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "encoding/json" "fmt" "net/http" @@ -59,7 +58,7 @@ func TestClient_AddRecord(t *testing.T) { apiReq.Params.ID = "123" - resp := map[string]interface{}{ + resp := map[string]any{ "jsonrpc": "2.0", "id": "897", "result": apiReq.Params, @@ -80,7 +79,7 @@ func TestClient_AddRecord(t *testing.T) { Type: "TXT", } - result, err := client.AddRecord(context.Background(), record) + result, err := client.AddRecord(t.Context(), record) require.NoError(t, err) expected := &Record{ @@ -106,7 +105,7 @@ func TestClient_AddRecord_error(t *testing.T) { Type: "TXT", } - result, err := client.AddRecord(context.Background(), record) + result, err := client.AddRecord(t.Context(), record) require.Error(t, err) assert.Nil(t, result) @@ -125,7 +124,7 @@ func TestClient_ListRecords(t *testing.T) { return } - resp := map[string]interface{}{ + resp := map[string]any{ "jsonrpc": "2.0", "id": "897", "result": Records{ @@ -157,7 +156,7 @@ func TestClient_ListRecords(t *testing.T) { } }) - records, err := client.ListRecords(context.Background(), "example.com") + records, err := client.ListRecords(t.Context(), "example.com") require.NoError(t, err) expected := []Record{ @@ -186,7 +185,7 @@ func TestClient_ListRecords_error(t *testing.T) { client := setupTest(t, nil) client.token = "invalid" - records, err := client.ListRecords(context.Background(), "example.com") + records, err := client.ListRecords(t.Context(), "example.com") require.Error(t, err) assert.Empty(t, records) @@ -218,7 +217,7 @@ func TestClient_RemoveRecord(t *testing.T) { _, _ = rw.Write([]byte(`{"jsonrpc":"2.0"}`)) }) - err := client.RemoveRecord(context.Background(), "123", "example.com") + err := client.RemoveRecord(t.Context(), "123", "example.com") require.NoError(t, err) } @@ -226,6 +225,6 @@ func TestClient_RemoveRecord_error(t *testing.T) { client := setupTest(t, nil) client.token = "invalid" - err := client.RemoveRecord(context.Background(), "123", "example.com") + err := client.RemoveRecord(t.Context(), "123", "example.com") require.Error(t, err) } diff --git a/providers/dns/otc/internal/client.go b/providers/dns/otc/internal/client.go index 59a685140..e3e225314 100644 --- a/providers/dns/otc/internal/client.go +++ b/providers/dns/otc/internal/client.go @@ -31,7 +31,7 @@ type Client struct { HTTPClient *http.Client } -func NewClient(username string, password string, domainName string, projectName string) *Client { +func NewClient(username, password, domainName, projectName string) *Client { return &Client{ username: username, password: password, @@ -196,7 +196,7 @@ func (c *Client) do(req *http.Request, result any) error { return nil } -func newJSONRequest[T string | *url.URL](ctx context.Context, method string, endpoint T, payload interface{}) (*http.Request, error) { +func newJSONRequest[T string | *url.URL](ctx context.Context, method string, endpoint T, payload any) (*http.Request, error) { buf := new(bytes.Buffer) if payload != nil { diff --git a/providers/dns/otc/internal/identity_test.go b/providers/dns/otc/internal/identity_test.go index 18627869a..c8bda7027 100644 --- a/providers/dns/otc/internal/identity_test.go +++ b/providers/dns/otc/internal/identity_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "net/url" "testing" @@ -16,7 +15,7 @@ func TestClient_Login(t *testing.T) { client := NewClient("user", "secret", "example.com", "test") client.IdentityEndpoint, _ = url.JoinPath(mock.GetServerURL(), "/v3/auth/token") - err := client.Login(context.Background()) + err := client.Login(t.Context()) require.NoError(t, err) serverURL, _ := url.Parse(mock.GetServerURL()) diff --git a/providers/dns/pdns/internal/client_test.go b/providers/dns/pdns/internal/client_test.go index b0eb9d2ed..d3919ace3 100644 --- a/providers/dns/pdns/internal/client_test.go +++ b/providers/dns/pdns/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -163,7 +162,7 @@ func TestClient_GetHostedZone(t *testing.T) { client := setupTest(t, http.MethodGet, "/api/v1/servers/server/zones/example.org.", http.StatusOK, "zone.json") client.apiVersion = 1 - zone, err := client.GetHostedZone(context.Background(), "example.org.") + zone, err := client.GetHostedZone(t.Context(), "example.org.") require.NoError(t, err) expected := &HostedZone{ @@ -206,7 +205,7 @@ func TestClient_GetHostedZone_error(t *testing.T) { client := setupTest(t, http.MethodGet, "/api/v1/servers/server/zones/example.org.", http.StatusUnprocessableEntity, "error.json") client.apiVersion = 1 - _, err := client.GetHostedZone(context.Background(), "example.org.") + _, err := client.GetHostedZone(t.Context(), "example.org.") require.ErrorAs(t, err, &apiError{}) } @@ -214,7 +213,7 @@ func TestClient_GetHostedZone_v0(t *testing.T) { client := setupTest(t, http.MethodGet, "/servers/server/zones/example.org.", http.StatusOK, "zone.json") client.apiVersion = 0 - zone, err := client.GetHostedZone(context.Background(), "example.org.") + zone, err := client.GetHostedZone(t.Context(), "example.org.") require.NoError(t, err) expected := &HostedZone{ @@ -279,7 +278,7 @@ func TestClient_UpdateRecords(t *testing.T) { }}, } - err := client.UpdateRecords(context.Background(), zone, rrSets) + err := client.UpdateRecords(t.Context(), zone, rrSets) require.NoError(t, err) } @@ -310,7 +309,7 @@ func TestClient_UpdateRecords_NonRootApi(t *testing.T) { }}, } - err := client.UpdateRecords(context.Background(), zone, rrSets) + err := client.UpdateRecords(t.Context(), zone, rrSets) require.NoError(t, err) } @@ -340,7 +339,7 @@ func TestClient_UpdateRecords_v0(t *testing.T) { }}, } - err := client.UpdateRecords(context.Background(), zone, rrSets) + err := client.UpdateRecords(t.Context(), zone, rrSets) require.NoError(t, err) } @@ -356,7 +355,7 @@ func TestClient_Notify(t *testing.T) { Kind: "Master", } - err := client.Notify(context.Background(), zone) + err := client.Notify(t.Context(), zone) require.NoError(t, err) } @@ -373,7 +372,7 @@ func TestClient_Notify_NonRootApi(t *testing.T) { Kind: "Master", } - err := client.Notify(context.Background(), zone) + err := client.Notify(t.Context(), zone) require.NoError(t, err) } @@ -388,14 +387,14 @@ func TestClient_Notify_v0(t *testing.T) { Kind: "Master", } - err := client.Notify(context.Background(), zone) + err := client.Notify(t.Context(), zone) require.NoError(t, err) } func TestClient_getAPIVersion(t *testing.T) { client := setupTest(t, http.MethodGet, "/api", http.StatusOK, "versions.json") - version, err := client.getAPIVersion(context.Background()) + version, err := client.getAPIVersion(t.Context()) require.NoError(t, err) assert.Equal(t, 4, version) diff --git a/providers/dns/plesk/internal/client.go b/providers/dns/plesk/internal/client.go index 9dd9d5ee3..5a2e2f4b8 100644 --- a/providers/dns/plesk/internal/client.go +++ b/providers/dns/plesk/internal/client.go @@ -24,7 +24,7 @@ type Client struct { } // NewClient created a new Client. -func NewClient(baseURL *url.URL, login string, password string) *Client { +func NewClient(baseURL *url.URL, login, password string) *Client { return &Client{ login: login, password: password, diff --git a/providers/dns/plesk/internal/client_test.go b/providers/dns/plesk/internal/client_test.go index 5d59a4c87..b61bce4c2 100644 --- a/providers/dns/plesk/internal/client_test.go +++ b/providers/dns/plesk/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -66,7 +65,7 @@ func setupTest(t *testing.T, filename string) *Client { func TestClient_GetSite(t *testing.T) { client := setupTest(t, "get-site.xml") - siteID, err := client.GetSite(context.Background(), "example.com") + siteID, err := client.GetSite(t.Context(), "example.com") require.NoError(t, err) assert.Equal(t, 82, siteID) @@ -75,7 +74,7 @@ func TestClient_GetSite(t *testing.T) { func TestClient_GetSite_error(t *testing.T) { client := setupTest(t, "get-site-error.xml") - siteID, err := client.GetSite(context.Background(), "example.com") + siteID, err := client.GetSite(t.Context(), "example.com") require.Error(t, err) assert.Equal(t, 0, siteID) @@ -84,7 +83,7 @@ func TestClient_GetSite_error(t *testing.T) { func TestClient_GetSite_system_error(t *testing.T) { client := setupTest(t, "global-error.xml") - siteID, err := client.GetSite(context.Background(), "example.com") + siteID, err := client.GetSite(t.Context(), "example.com") require.Error(t, err) assert.Equal(t, 0, siteID) @@ -93,7 +92,7 @@ func TestClient_GetSite_system_error(t *testing.T) { func TestClient_AddRecord(t *testing.T) { client := setupTest(t, "add-record.xml") - recordID, err := client.AddRecord(context.Background(), 123, "_acme-challenge.example.com", "txtTXTtxt") + recordID, err := client.AddRecord(t.Context(), 123, "_acme-challenge.example.com", "txtTXTtxt") require.NoError(t, err) assert.Equal(t, 4537, recordID) @@ -102,7 +101,7 @@ func TestClient_AddRecord(t *testing.T) { func TestClient_AddRecord_error(t *testing.T) { client := setupTest(t, "add-record-error.xml") - recordID, err := client.AddRecord(context.Background(), 123, "_acme-challenge.example.com", "txtTXTtxt") + recordID, err := client.AddRecord(t.Context(), 123, "_acme-challenge.example.com", "txtTXTtxt") require.ErrorAs(t, err, new(RecResult)) assert.Equal(t, 0, recordID) @@ -111,7 +110,7 @@ func TestClient_AddRecord_error(t *testing.T) { func TestClient_AddRecord_system_error(t *testing.T) { client := setupTest(t, "global-error.xml") - recordID, err := client.AddRecord(context.Background(), 123, "_acme-challenge.example.com", "txtTXTtxt") + recordID, err := client.AddRecord(t.Context(), 123, "_acme-challenge.example.com", "txtTXTtxt") require.ErrorAs(t, err, new(*System)) assert.Equal(t, 0, recordID) @@ -120,7 +119,7 @@ func TestClient_AddRecord_system_error(t *testing.T) { func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, "delete-record.xml") - recordID, err := client.DeleteRecord(context.Background(), 4537) + recordID, err := client.DeleteRecord(t.Context(), 4537) require.NoError(t, err) assert.Equal(t, 4537, recordID) @@ -129,7 +128,7 @@ func TestClient_DeleteRecord(t *testing.T) { func TestClient_DeleteRecord_error(t *testing.T) { client := setupTest(t, "delete-record-error.xml") - recordID, err := client.DeleteRecord(context.Background(), 4537) + recordID, err := client.DeleteRecord(t.Context(), 4537) require.ErrorAs(t, err, new(RecResult)) assert.Equal(t, 0, recordID) @@ -138,7 +137,7 @@ func TestClient_DeleteRecord_error(t *testing.T) { func TestClient_DeleteRecord_system_error(t *testing.T) { client := setupTest(t, "global-error.xml") - recordID, err := client.DeleteRecord(context.Background(), 4537) + recordID, err := client.DeleteRecord(t.Context(), 4537) require.ErrorAs(t, err, new(*System)) assert.Equal(t, 0, recordID) diff --git a/providers/dns/rackspace/internal/client.go b/providers/dns/rackspace/internal/client.go index cbfdd1bfa..de25f8d0e 100644 --- a/providers/dns/rackspace/internal/client.go +++ b/providers/dns/rackspace/internal/client.go @@ -21,7 +21,7 @@ type Client struct { HTTPClient *http.Client } -func NewClient(endpoint string, token string) (*Client, error) { +func NewClient(endpoint, token string) (*Client, error) { baseURL, err := url.Parse(endpoint) if err != nil { return nil, err @@ -120,7 +120,7 @@ func (c *Client) listDomainsByName(ctx context.Context, domain string) (*ZoneSea } // FindTxtRecord searches a DNS zone for a TXT record with a specific name. -func (c *Client) FindTxtRecord(ctx context.Context, fqdn string, zoneID string) (*Record, error) { +func (c *Client) FindTxtRecord(ctx context.Context, fqdn, zoneID string) (*Record, error) { records, err := c.searchRecords(ctx, zoneID, dns01.UnFqdn(fqdn), "TXT") if err != nil { return nil, err @@ -191,7 +191,7 @@ func (c *Client) do(req *http.Request, result any) error { return nil } -func newJSONRequest[T string | *url.URL](ctx context.Context, method string, endpoint T, payload interface{}) (*http.Request, error) { +func newJSONRequest[T string | *url.URL](ctx context.Context, method string, endpoint T, payload any) (*http.Request, error) { buf := new(bytes.Buffer) if payload != nil { diff --git a/providers/dns/rackspace/internal/client_test.go b/providers/dns/rackspace/internal/client_test.go index 993d34d9f..ce25d107c 100644 --- a/providers/dns/rackspace/internal/client_test.go +++ b/providers/dns/rackspace/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -61,21 +60,21 @@ func writeFixtureHandler(method, filename string) http.HandlerFunc { func TestClient_AddRecord(t *testing.T) { client := setupTest(t, "/domains/1234/records", writeFixtureHandler(http.MethodPost, "add-records.json")) - err := client.AddRecord(context.Background(), "1234", Record{}) + err := client.AddRecord(t.Context(), "1234", Record{}) require.NoError(t, err) } func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, "/domains/1234/records", writeFixtureHandler(http.MethodDelete, "")) - err := client.DeleteRecord(context.Background(), "1234", "2725233") + err := client.DeleteRecord(t.Context(), "1234", "2725233") require.NoError(t, err) } func TestClient_searchRecords(t *testing.T) { client := setupTest(t, "/domains/1234/records", writeFixtureHandler(http.MethodGet, "search-records.json")) - records, err := client.searchRecords(context.Background(), "1234", "2725233", "A") + records, err := client.searchRecords(t.Context(), "1234", "2725233", "A") require.NoError(t, err) expected := &Records{ @@ -96,7 +95,7 @@ func TestClient_searchRecords(t *testing.T) { func TestClient_listDomainsByName(t *testing.T) { client := setupTest(t, "/domains", writeFixtureHandler(http.MethodGet, "list-domains-by-name.json")) - domains, err := client.listDomainsByName(context.Background(), "1234") + domains, err := client.listDomainsByName(t.Context(), "1234") require.NoError(t, err) expected := &ZoneSearchResponse{ diff --git a/providers/dns/rackspace/internal/identity_test.go b/providers/dns/rackspace/internal/identity_test.go index 9ba5abb50..b976fdd2f 100644 --- a/providers/dns/rackspace/internal/identity_test.go +++ b/providers/dns/rackspace/internal/identity_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -45,7 +44,7 @@ func TestIdentifier_Login(t *testing.T) { mux.HandleFunc("/", writeIdentityFixtureHandler(http.MethodPost, "tokens.json")) - identity, err := identifier.Login(context.Background(), "user", "secret") + identity, err := identifier.Login(t.Context(), "user", "secret") require.NoError(t, err) expected := &Identity{ diff --git a/providers/dns/rainyun/internal/client_test.go b/providers/dns/rainyun/internal/client_test.go index ee6477c0c..1652bba39 100644 --- a/providers/dns/rainyun/internal/client_test.go +++ b/providers/dns/rainyun/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "io" "net/http" "net/http/httptest" @@ -55,7 +54,7 @@ func setupTest(t *testing.T, pattern string, status int, filename string) *Clien func TestClient_ListDomains(t *testing.T) { client := setupTest(t, "GET /domain", http.StatusOK, "domains.json") - domains, err := client.ListDomains(context.Background()) + domains, err := client.ListDomains(t.Context()) require.NoError(t, err) expected := []Domain{ @@ -69,7 +68,7 @@ func TestClient_ListDomains(t *testing.T) { func TestClient_ListDomains_error(t *testing.T) { client := setupTest(t, "GET /domain", http.StatusForbidden, "error.json") - _, err := client.ListDomains(context.Background()) + _, err := client.ListDomains(t.Context()) require.Error(t, err) assert.EqualError(t, err, "30039: 密钥认证错误或已失效") @@ -78,7 +77,7 @@ func TestClient_ListDomains_error(t *testing.T) { func TestClient_ListRecords(t *testing.T) { client := setupTest(t, "GET /domain/123/dns", http.StatusOK, "records.json") - records, err := client.ListRecords(context.Background(), 123) + records, err := client.ListRecords(t.Context(), 123) require.NoError(t, err) expected := []Record{ @@ -106,7 +105,7 @@ func TestClient_ListRecords(t *testing.T) { func TestClient_ListRecords_error(t *testing.T) { client := setupTest(t, "GET /domain/123/dns", http.StatusForbidden, "error.json") - _, err := client.ListRecords(context.Background(), 123) + _, err := client.ListRecords(t.Context(), 123) require.Error(t, err) assert.EqualError(t, err, "30039: 密钥认证错误或已失效") @@ -123,7 +122,7 @@ func TestClient_AddRecord(t *testing.T) { Value: "foo", } - err := client.AddRecord(context.Background(), 123, record) + err := client.AddRecord(t.Context(), 123, record) require.NoError(t, err) } @@ -138,7 +137,7 @@ func TestClient_AddRecord_error(t *testing.T) { Value: "foo", } - err := client.AddRecord(context.Background(), 123, record) + err := client.AddRecord(t.Context(), 123, record) require.Error(t, err) assert.EqualError(t, err, "30039: 密钥认证错误或已失效") @@ -147,14 +146,14 @@ func TestClient_AddRecord_error(t *testing.T) { func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, "DELETE /domain/123/dns", http.StatusOK, "") - err := client.DeleteRecord(context.Background(), 123, 456) + err := client.DeleteRecord(t.Context(), 123, 456) require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { client := setupTest(t, "DELETE /domain/123/dns", http.StatusForbidden, "error.json") - err := client.DeleteRecord(context.Background(), 123, 456) + err := client.DeleteRecord(t.Context(), 123, 456) require.Error(t, err) assert.EqualError(t, err, "30039: 密钥认证错误或已失效") diff --git a/providers/dns/rcodezero/internal/client_test.go b/providers/dns/rcodezero/internal/client_test.go index c19e6e5b8..0b54fa97f 100644 --- a/providers/dns/rcodezero/internal/client_test.go +++ b/providers/dns/rcodezero/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -72,7 +71,7 @@ func TestClient_UpdateRecords_error(t *testing.T) { Records: []Record{{Content: `"my-acme-challenge"`}}, }} - resp, err := client.UpdateRecords(context.Background(), "example.org", rrSet) + resp, err := client.UpdateRecords(t.Context(), "example.org", rrSet) require.ErrorAs(t, err, new(*APIResponse)) assert.Nil(t, resp) } @@ -87,7 +86,7 @@ func TestClient_UpdateRecords(t *testing.T) { Records: []Record{{Content: `"my-acme-challenge"`}}, }} - resp, err := client.UpdateRecords(context.Background(), "example.org", rrSet) + resp, err := client.UpdateRecords(t.Context(), "example.org", rrSet) require.NoError(t, err) expected := &APIResponse{Status: "ok", Message: "RRsets updated"} diff --git a/providers/dns/regru/internal/client_test.go b/providers/dns/regru/internal/client_test.go index fa3f16702..4b4a9c8f4 100644 --- a/providers/dns/regru/internal/client_test.go +++ b/providers/dns/regru/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "net/http" "net/url" "os" @@ -24,7 +23,7 @@ func TestRemoveRecord(t *testing.T) { client := NewClient(officialTestUser, officialTestPassword) client.HTTPClient = &http.Client{Timeout: 30 * time.Second} - err := client.RemoveTxtRecord(context.Background(), "test.ru", "_acme-challenge", "txttxttxt") + err := client.RemoveTxtRecord(t.Context(), "test.ru", "_acme-challenge", "txttxttxt") require.NoError(t, err) } @@ -68,7 +67,7 @@ func TestRemoveRecord_errors(t *testing.T) { client.HTTPClient = &http.Client{Timeout: 30 * time.Second} client.baseURL, _ = url.Parse(test.baseURL) - err := client.RemoveTxtRecord(context.Background(), test.domain, "_acme-challenge", "txttxttxt") + err := client.RemoveTxtRecord(t.Context(), test.domain, "_acme-challenge", "txttxttxt") require.EqualError(t, err, test.expected) }) } @@ -81,7 +80,7 @@ func TestAddTXTRecord(t *testing.T) { client := NewClient(officialTestUser, officialTestPassword) client.HTTPClient = &http.Client{Timeout: 30 * time.Second} - err := client.AddTXTRecord(context.Background(), "test.ru", "_acme-challenge", "txttxttxt") + err := client.AddTXTRecord(t.Context(), "test.ru", "_acme-challenge", "txttxttxt") require.NoError(t, err) } @@ -125,7 +124,7 @@ func TestAddTXTRecord_errors(t *testing.T) { client.HTTPClient = &http.Client{Timeout: 30 * time.Second} client.baseURL, _ = url.Parse(test.baseURL) - err := client.AddTXTRecord(context.Background(), test.domain, "_acme-challenge", "txttxttxt") + err := client.AddTXTRecord(t.Context(), test.domain, "_acme-challenge", "txttxttxt") require.EqualError(t, err, test.expected) }) } diff --git a/providers/dns/route53/route53.go b/providers/dns/route53/route53.go index 4d0a13a3d..db578eb00 100644 --- a/providers/dns/route53/route53.go +++ b/providers/dns/route53/route53.go @@ -345,10 +345,7 @@ func createAWSConfig(ctx context.Context, config *Config) (aws.Config, error) { // causing a high number of consecutive throttling errors. // For reference: Route 53 enforces an account-wide(!) 5req/s query limit. options.Backoff = retry.BackoffDelayerFunc(func(attempt int, err error) (time.Duration, error) { - retryCount := attempt - if retryCount > 7 { - retryCount = 7 - } + retryCount := min(attempt, 7) delay := (1 << uint(retryCount)) * (rand.Intn(50) + 200) return time.Duration(delay) * time.Millisecond, nil diff --git a/providers/dns/route53/route53_integration_test.go b/providers/dns/route53/route53_integration_test.go index 9467fb77a..b80294013 100644 --- a/providers/dns/route53/route53_integration_test.go +++ b/providers/dns/route53/route53_integration_test.go @@ -1,7 +1,6 @@ package route53 import ( - "context" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -29,7 +28,7 @@ func TestLiveTTL(t *testing.T) { // we need a separate R53 client here as the one in the DNS provider is unexported. fqdn := "_acme-challenge." + domain + "." - ctx := context.Background() + ctx := t.Context() cfg, err := awsconfig.LoadDefaultConfig(ctx) require.NoError(t, err) @@ -43,7 +42,7 @@ func TestLiveTTL(t *testing.T) { } }() - zoneID, err := provider.getHostedZoneID(context.Background(), fqdn) + zoneID, err := provider.getHostedZoneID(t.Context(), fqdn) require.NoError(t, err) params := &route53.ListResourceRecordSetsInput{ diff --git a/providers/dns/route53/route53_test.go b/providers/dns/route53/route53_test.go index 6ab37f674..60901de6d 100644 --- a/providers/dns/route53/route53_test.go +++ b/providers/dns/route53/route53_test.go @@ -1,7 +1,6 @@ package route53 import ( - "context" "os" "testing" "time" @@ -55,7 +54,7 @@ func Test_loadCredentials_FromEnv(t *testing.T) { _ = os.Setenv(EnvSecretAccessKey, "456") _ = os.Setenv(EnvRegion, "us-east-1") - ctx := context.Background() + ctx := t.Context() cfg, err := awsconfig.LoadDefaultConfig(ctx) require.NoError(t, err) @@ -79,7 +78,7 @@ func Test_loadRegion_FromEnv(t *testing.T) { _ = os.Setenv(EnvRegion, "foo") - cfg, err := awsconfig.LoadDefaultConfig(context.Background()) + cfg, err := awsconfig.LoadDefaultConfig(t.Context()) require.NoError(t, err) assert.Equal(t, "foo", cfg.Region, "Region") @@ -96,7 +95,7 @@ func Test_getHostedZoneID_FromEnv(t *testing.T) { provider, err := NewDNSProvider() require.NoError(t, err) - hostedZoneID, err := provider.getHostedZoneID(context.Background(), "whatever") + hostedZoneID, err := provider.getHostedZoneID(t.Context(), "whatever") require.NoError(t, err, "HostedZoneID") assert.Equal(t, expectedZoneID, hostedZoneID) @@ -268,7 +267,7 @@ func Test_createAWSConfig(t *testing.T) { envTest.Apply(test.env) - ctx := context.Background() + ctx := t.Context() cfg, err := createAWSConfig(ctx, test.config) requireErr(t, err, test.wantErr) diff --git a/providers/dns/safedns/internal/client_test.go b/providers/dns/safedns/internal/client_test.go index 6709277cd..c00a8b5a7 100644 --- a/providers/dns/safedns/internal/client_test.go +++ b/providers/dns/safedns/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -78,7 +77,7 @@ func TestClient_AddRecord(t *testing.T) { TTL: dns01.DefaultTTL, } - response, err := client.AddRecord(context.Background(), "example.com", record) + response, err := client.AddRecord(t.Context(), "example.com", record) require.NoError(t, err) expected := &AddRecordResponse{ @@ -114,6 +113,6 @@ func TestClient_RemoveRecord(t *testing.T) { rw.WriteHeader(http.StatusNoContent) }) - err := client.RemoveRecord(context.Background(), "example.com", 1234567) + err := client.RemoveRecord(t.Context(), "example.com", 1234567) require.NoError(t, err) } diff --git a/providers/dns/sakuracloud/wrapper_test.go b/providers/dns/sakuracloud/wrapper_test.go index 91cd3ce0a..15eb19618 100644 --- a/providers/dns/sakuracloud/wrapper_test.go +++ b/providers/dns/sakuracloud/wrapper_test.go @@ -1,7 +1,6 @@ package sakuracloud import ( - "context" "fmt" "sync" "testing" @@ -33,7 +32,7 @@ func fakeCaller() iaas.APICaller { func createDummyZone(t *testing.T, caller iaas.APICaller) { t.Helper() - ctx := context.Background() + ctx := t.Context() dnsOp := iaas.NewDNSOp(caller) @@ -50,7 +49,7 @@ func createDummyZone(t *testing.T, caller iaas.APICaller) { } // create dummy zone - _, err = iaas.NewDNSOp(caller).Create(context.Background(), &iaas.DNSCreateRequest{Name: "example.com"}) + _, err = iaas.NewDNSOp(caller).Create(t.Context(), &iaas.DNSCreateRequest{Name: "example.com"}) require.NoError(t, err) } diff --git a/providers/dns/selfhostde/internal/client_test.go b/providers/dns/selfhostde/internal/client_test.go index 8abda8fb6..88f627b02 100644 --- a/providers/dns/selfhostde/internal/client_test.go +++ b/providers/dns/selfhostde/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -49,7 +48,7 @@ func TestClient_UpdateTXTRecord(t *testing.T) { } }) - err := client.UpdateTXTRecord(context.Background(), "123456", "txt") + err := client.UpdateTXTRecord(t.Context(), "123456", "txt") require.NoError(t, err) } @@ -60,6 +59,6 @@ func TestClient_UpdateTXTRecord_error(t *testing.T) { http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) }) - err := client.UpdateTXTRecord(context.Background(), "123456", "txt") + err := client.UpdateTXTRecord(t.Context(), "123456", "txt") require.Error(t, err) } diff --git a/providers/dns/servercow/internal/client_test.go b/providers/dns/servercow/internal/client_test.go index 8597d7e12..b171b6408 100644 --- a/providers/dns/servercow/internal/client_test.go +++ b/providers/dns/servercow/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "encoding/json" "io" "net/http" @@ -51,7 +50,7 @@ func TestClient_GetRecords(t *testing.T) { } }) - records, err := client.GetRecords(context.Background(), "lego.wtf") + records, err := client.GetRecords(t.Context(), "lego.wtf") require.NoError(t, err) recordsJSON, err := json.Marshal(records) @@ -79,7 +78,7 @@ func TestClient_GetRecords_error(t *testing.T) { } }) - records, err := client.GetRecords(context.Background(), "lego.wtf") + records, err := client.GetRecords(t.Context(), "lego.wtf") require.Error(t, err) assert.Nil(t, records) @@ -121,7 +120,7 @@ func TestClient_CreateUpdateRecord(t *testing.T) { Content: Value{"aaa", "bbb"}, } - msg, err := client.CreateUpdateRecord(context.Background(), "lego.wtf", record) + msg, err := client.CreateUpdateRecord(t.Context(), "lego.wtf", record) require.NoError(t, err) expected := &Message{Message: "ok"} @@ -148,7 +147,7 @@ func TestClient_CreateUpdateRecord_error(t *testing.T) { Name: "_acme-challenge.www", } - msg, err := client.CreateUpdateRecord(context.Background(), "lego.wtf", record) + msg, err := client.CreateUpdateRecord(t.Context(), "lego.wtf", record) require.Error(t, err) assert.Nil(t, msg) @@ -188,7 +187,7 @@ func TestClient_DeleteRecord(t *testing.T) { Type: "TXT", } - msg, err := client.DeleteRecord(context.Background(), "lego.wtf", record) + msg, err := client.DeleteRecord(t.Context(), "lego.wtf", record) require.NoError(t, err) expected := &Message{Message: "ok"} @@ -215,7 +214,7 @@ func TestClient_DeleteRecord_error(t *testing.T) { Name: "_acme-challenge.www", } - msg, err := client.DeleteRecord(context.Background(), "lego.wtf", record) + msg, err := client.DeleteRecord(t.Context(), "lego.wtf", record) require.Error(t, err) assert.Nil(t, msg) diff --git a/providers/dns/shellrent/internal/client.go b/providers/dns/shellrent/internal/client.go index a361ccf1d..fbddf3120 100644 --- a/providers/dns/shellrent/internal/client.go +++ b/providers/dns/shellrent/internal/client.go @@ -29,7 +29,7 @@ type Client struct { } // NewClient Creates a new Client. -func NewClient(username string, token string) *Client { +func NewClient(username, token string) *Client { baseURL, _ := url.Parse(defaultBaseURL) return &Client{ @@ -142,7 +142,7 @@ func (c Client) CreateRecord(ctx context.Context, domainID int, record Record) ( // DeleteRecord deletes a record. // https://api.shellrent.com/eliminazione-record-dns-di-un-dominio -func (c Client) DeleteRecord(ctx context.Context, domainID int, recordID int) error { +func (c Client) DeleteRecord(ctx context.Context, domainID, recordID int) error { endpoint := c.baseURL.JoinPath("dns_record", "remove", strconv.Itoa(domainID), strconv.Itoa(recordID)) req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) diff --git a/providers/dns/shellrent/internal/client_test.go b/providers/dns/shellrent/internal/client_test.go index 0fe77c6fc..c160ddf56 100644 --- a/providers/dns/shellrent/internal/client_test.go +++ b/providers/dns/shellrent/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -65,7 +64,7 @@ func setupTest(t *testing.T, method, pattern string, status int, file string) *C func TestClient_ListServices(t *testing.T) { client := setupTest(t, http.MethodGet, "/purchase", http.StatusOK, "purchase.json") - services, err := client.ListServices(context.Background()) + services, err := client.ListServices(t.Context()) require.NoError(t, err) expected := []int{2018, 10039, 10128} @@ -76,21 +75,21 @@ func TestClient_ListServices(t *testing.T) { func TestClient_ListServices_error(t *testing.T) { client := setupTest(t, http.MethodGet, "/purchase", http.StatusOK, "error.json") - _, err := client.ListServices(context.Background()) + _, err := client.ListServices(t.Context()) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_ListServices_error_status(t *testing.T) { client := setupTest(t, http.MethodGet, "/purchase", http.StatusUnauthorized, "error.json") - _, err := client.ListServices(context.Background()) + _, err := client.ListServices(t.Context()) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_GetServiceDetails(t *testing.T) { client := setupTest(t, http.MethodGet, "/purchase/details/123", http.StatusOK, "purchase-details.json") - services, err := client.GetServiceDetails(context.Background(), 123) + services, err := client.GetServiceDetails(t.Context(), 123) require.NoError(t, err) expected := &ServiceDetails{ID: 123, Name: "example", DomainID: 456} @@ -101,21 +100,21 @@ func TestClient_GetServiceDetails(t *testing.T) { func TestClient_GetServiceDetails_error(t *testing.T) { client := setupTest(t, http.MethodGet, "/purchase/details/123", http.StatusOK, "error.json") - _, err := client.GetServiceDetails(context.Background(), 123) + _, err := client.GetServiceDetails(t.Context(), 123) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_GetServiceDetails_error_status(t *testing.T) { client := setupTest(t, http.MethodGet, "/purchase/details/123", http.StatusUnauthorized, "error.json") - _, err := client.GetServiceDetails(context.Background(), 123) + _, err := client.GetServiceDetails(t.Context(), 123) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_GetDomainDetails(t *testing.T) { client := setupTest(t, http.MethodGet, "/domain/details/123", http.StatusOK, "domain-details.json") - services, err := client.GetDomainDetails(context.Background(), 123) + services, err := client.GetDomainDetails(t.Context(), 123) require.NoError(t, err) expected := &DomainDetails{ID: 123, DomainName: "example.com", DomainNameASCII: "example.com"} @@ -126,21 +125,21 @@ func TestClient_GetDomainDetails(t *testing.T) { func TestClient_GetDomainDetails_error(t *testing.T) { client := setupTest(t, http.MethodGet, "/domain/details/123", http.StatusOK, "error.json") - _, err := client.GetDomainDetails(context.Background(), 123) + _, err := client.GetDomainDetails(t.Context(), 123) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_GetDomainDetails_error_status(t *testing.T) { client := setupTest(t, http.MethodGet, "/domain/details/123", http.StatusUnauthorized, "error.json") - _, err := client.GetDomainDetails(context.Background(), 123) + _, err := client.GetDomainDetails(t.Context(), 123) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_CreateRecord(t *testing.T) { client := setupTest(t, http.MethodPost, "/dns_record/store/123", http.StatusOK, "dns_record-store.json") - services, err := client.CreateRecord(context.Background(), 123, Record{}) + services, err := client.CreateRecord(t.Context(), 123, Record{}) require.NoError(t, err) expected := 2255674 @@ -151,35 +150,35 @@ func TestClient_CreateRecord(t *testing.T) { func TestClient_CreateRecord_error(t *testing.T) { client := setupTest(t, http.MethodPost, "/dns_record/store/123", http.StatusOK, "error.json") - _, err := client.CreateRecord(context.Background(), 123, Record{}) + _, err := client.CreateRecord(t.Context(), 123, Record{}) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_CreateRecord_error_status(t *testing.T) { client := setupTest(t, http.MethodPost, "/dns_record/store/123", http.StatusUnauthorized, "error.json") - _, err := client.CreateRecord(context.Background(), 123, Record{}) + _, err := client.CreateRecord(t.Context(), 123, Record{}) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, http.MethodDelete, "/dns_record/remove/123/456", http.StatusOK, "dns_record-remove.json") - err := client.DeleteRecord(context.Background(), 123, 456) + err := client.DeleteRecord(t.Context(), 123, 456) require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { client := setupTest(t, http.MethodDelete, "/dns_record/remove/123/456", http.StatusOK, "error.json") - err := client.DeleteRecord(context.Background(), 123, 456) + err := client.DeleteRecord(t.Context(), 123, 456) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_DeleteRecord_error_status(t *testing.T) { client := setupTest(t, http.MethodDelete, "/dns_record/remove/123/456", http.StatusUnauthorized, "error.json") - err := client.DeleteRecord(context.Background(), 123, 456) + err := client.DeleteRecord(t.Context(), 123, 456) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } diff --git a/providers/dns/simply/internal/client.go b/providers/dns/simply/internal/client.go index b57bf2102..74f5fe671 100644 --- a/providers/dns/simply/internal/client.go +++ b/providers/dns/simply/internal/client.go @@ -28,7 +28,7 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(accountName string, apiKey string) (*Client, error) { +func NewClient(accountName, apiKey string) (*Client, error) { if accountName == "" { return nil, errors.New("credentials missing: accountName") } @@ -110,7 +110,7 @@ func (c *Client) DeleteRecord(ctx context.Context, zoneName string, id int64) er return c.do(req, &apiResponse[json.RawMessage, json.RawMessage]{}) } -func (c *Client) createEndpoint(zoneName string, uri string) *url.URL { +func (c *Client) createEndpoint(zoneName, uri string) *url.URL { return c.baseURL.JoinPath(c.accountName, c.apiKey, "my", "products", zoneName, "dns", "records", strings.TrimSuffix(uri, "/")) } diff --git a/providers/dns/simply/internal/client_test.go b/providers/dns/simply/internal/client_test.go index c9b97e94c..e822b03cf 100644 --- a/providers/dns/simply/internal/client_test.go +++ b/providers/dns/simply/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -21,7 +20,7 @@ func TestClient_GetRecords(t *testing.T) { mux.HandleFunc("/accountname/apikey/my/products/azone01/dns/records", mockHandler(http.MethodGet, http.StatusOK, "get_records.json")) - records, err := client.GetRecords(context.Background(), "azone01") + records, err := client.GetRecords(t.Context(), "azone01") require.NoError(t, err) expected := []Record{ @@ -67,7 +66,7 @@ func TestClient_GetRecords_error(t *testing.T) { mux.HandleFunc("/accountname/apikey/my/products/azone01/dns/records", mockHandler(http.MethodGet, http.StatusBadRequest, "bad_auth_error.json")) - records, err := client.GetRecords(context.Background(), "azone01") + records, err := client.GetRecords(t.Context(), "azone01") require.Error(t, err) assert.Nil(t, records) @@ -86,7 +85,7 @@ func TestClient_AddRecord(t *testing.T) { Priority: 0, } - recordID, err := client.AddRecord(context.Background(), "azone01", record) + recordID, err := client.AddRecord(t.Context(), "azone01", record) require.NoError(t, err) assert.EqualValues(t, 123456789, recordID) @@ -105,7 +104,7 @@ func TestClient_AddRecord_error(t *testing.T) { Priority: 0, } - recordID, err := client.AddRecord(context.Background(), "azone01", record) + recordID, err := client.AddRecord(t.Context(), "azone01", record) require.Error(t, err) assert.Zero(t, recordID) @@ -124,7 +123,7 @@ func TestClient_EditRecord(t *testing.T) { Priority: 0, } - err := client.EditRecord(context.Background(), "azone01", 123456789, record) + err := client.EditRecord(t.Context(), "azone01", 123456789, record) require.NoError(t, err) } @@ -141,7 +140,7 @@ func TestClient_EditRecord_error(t *testing.T) { Priority: 0, } - err := client.EditRecord(context.Background(), "azone01", 123456789, record) + err := client.EditRecord(t.Context(), "azone01", 123456789, record) require.Error(t, err) } @@ -150,7 +149,7 @@ func TestClient_DeleteRecord(t *testing.T) { mux.HandleFunc("/accountname/apikey/my/products/azone01/dns/records/123456789", mockHandler(http.MethodDelete, http.StatusOK, "success.json")) - err := client.DeleteRecord(context.Background(), "azone01", 123456789) + err := client.DeleteRecord(t.Context(), "azone01", 123456789) require.NoError(t, err) } @@ -159,7 +158,7 @@ func TestClient_DeleteRecord_error(t *testing.T) { mux.HandleFunc("/accountname/apikey/my/products/azone01/dns/records/123456789", mockHandler(http.MethodDelete, http.StatusNotFound, "invalid_record_id.json")) - err := client.DeleteRecord(context.Background(), "azone01", 123456789) + err := client.DeleteRecord(t.Context(), "azone01", 123456789) require.Error(t, err) } diff --git a/providers/dns/sonic/internal/client.go b/providers/dns/sonic/internal/client.go index aac85c636..3007a8248 100644 --- a/providers/dns/sonic/internal/client.go +++ b/providers/dns/sonic/internal/client.go @@ -42,7 +42,7 @@ func NewClient(userID, apiKey string) (*Client, error) { // SetRecord creates or updates a TXT records. // Sonic does not provide a delete record API endpoint. // https://public-api.sonic.net/dyndns#updating_or_adding_host_records -func (c *Client) SetRecord(ctx context.Context, hostname string, value string, ttl int) error { +func (c *Client) SetRecord(ctx context.Context, hostname, value string, ttl int) error { payload := &Record{ UserID: c.userID, APIKey: c.apiKey, diff --git a/providers/dns/sonic/internal/client_test.go b/providers/dns/sonic/internal/client_test.go index ac711387e..618538780 100644 --- a/providers/dns/sonic/internal/client_test.go +++ b/providers/dns/sonic/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -53,7 +52,7 @@ func TestClient_SetRecord(t *testing.T) { client := setupTest(t, test.response) - err := client.SetRecord(context.Background(), "example.com", "txttxttxt", 10) + err := client.SetRecord(t.Context(), "example.com", "txttxttxt", 10) test.assert(t, err) }) } diff --git a/providers/dns/spaceship/internal/client_test.go b/providers/dns/spaceship/internal/client_test.go index 19c90d5f8..ec6787f8e 100644 --- a/providers/dns/spaceship/internal/client_test.go +++ b/providers/dns/spaceship/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "io" "net/http" "net/http/httptest" @@ -61,7 +60,7 @@ func TestClient_AddRecord(t *testing.T) { TTL: 60, } - err := client.AddRecord(context.Background(), "example.com", record) + err := client.AddRecord(t.Context(), "example.com", record) require.NoError(t, err) } @@ -74,7 +73,7 @@ func TestClient_AddRecord_error(t *testing.T) { TTL: 60, } - err := client.AddRecord(context.Background(), "example.com", record) + err := client.AddRecord(t.Context(), "example.com", record) require.EqualError(t, err, "^$, name: The domain name contains invalid characters") } @@ -87,7 +86,7 @@ func TestClient_DeleteRecord(t *testing.T) { TTL: 60, } - err := client.DeleteRecord(context.Background(), "example.com", record) + err := client.DeleteRecord(t.Context(), "example.com", record) require.NoError(t, err) } @@ -100,14 +99,14 @@ func TestClient_DeleteRecord_error(t *testing.T) { TTL: 60, } - err := client.DeleteRecord(context.Background(), "example.com", record) + err := client.DeleteRecord(t.Context(), "example.com", record) require.EqualError(t, err, "^$, name: The domain name contains invalid characters") } func TestClient_GetRecords(t *testing.T) { client := setupTest(t, "GET /dns/records/example.com", http.StatusOK, "get-records.json") - records, err := client.GetRecords(context.Background(), "example.com") + records, err := client.GetRecords(t.Context(), "example.com") require.NoError(t, err) expected := []Record{ @@ -120,6 +119,6 @@ func TestClient_GetRecords(t *testing.T) { func TestClient_GetRecords_error(t *testing.T) { client := setupTest(t, "GET /dns/records/example.com", http.StatusUnprocessableEntity, "error.json") - _, err := client.GetRecords(context.Background(), "example.com") + _, err := client.GetRecords(t.Context(), "example.com") require.EqualError(t, err, "^$, name: The domain name contains invalid characters") } diff --git a/providers/dns/stackpath/internal/client_test.go b/providers/dns/stackpath/internal/client_test.go index 2de1d4761..cb56ef728 100644 --- a/providers/dns/stackpath/internal/client_test.go +++ b/providers/dns/stackpath/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "net/http" "net/http/httptest" "net/url" @@ -18,7 +17,7 @@ func setupTest(t *testing.T) (*Client, *http.ServeMux) { server := httptest.NewServer(mux) t.Cleanup(server.Close) - client := NewClient(context.Background(), "STACK_ID", "CLIENT_ID", "CLIENT_SECRET") + client := NewClient(t.Context(), "STACK_ID", "CLIENT_ID", "CLIENT_SECRET") client.httpClient = server.Client() client.baseURL, _ = url.Parse(server.URL + "/") @@ -44,7 +43,7 @@ func TestClient_GetZoneRecords(t *testing.T) { } }) - records, err := client.GetZoneRecords(context.Background(), "foo1", &Zone{ID: "A", Domain: "test"}) + records, err := client.GetZoneRecords(t.Context(), "foo1", &Zone{ID: "A", Domain: "test"}) require.NoError(t, err) expected := []Record{ @@ -73,7 +72,7 @@ func TestClient_GetZoneRecords_apiError(t *testing.T) { } }) - _, err := client.GetZoneRecords(context.Background(), "foo1", &Zone{ID: "A", Domain: "test"}) + _, err := client.GetZoneRecords(t.Context(), "foo1", &Zone{ID: "A", Domain: "test"}) expected := &ErrorResponse{Code: 401, Message: "an unauthorized request is attempted."} assert.Equal(t, expected, err) @@ -122,7 +121,7 @@ func TestClient_GetZones(t *testing.T) { } }) - zone, err := client.GetZones(context.Background(), "sub.foo.com") + zone, err := client.GetZones(t.Context(), "sub.foo.com") require.NoError(t, err) expected := &Zone{ID: "A", Domain: "foo.com"} diff --git a/providers/dns/technitium/internal/client_test.go b/providers/dns/technitium/internal/client_test.go index 326c1e8eb..f8b0d049b 100644 --- a/providers/dns/technitium/internal/client_test.go +++ b/providers/dns/technitium/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "io" "net/http" "net/http/httptest" @@ -13,7 +12,7 @@ import ( "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, filename string) *Client { +func setupTest(t *testing.T, pattern, filename string) *Client { t.Helper() mux := http.NewServeMux() @@ -53,7 +52,7 @@ func TestClient_AddRecord(t *testing.T) { Text: "txtTXTtxt", } - newRecord, err := client.AddRecord(context.Background(), record) + newRecord, err := client.AddRecord(t.Context(), record) require.NoError(t, err) expected := &Record{Name: "example.com", Type: "A"} @@ -70,7 +69,7 @@ func TestClient_AddRecord_error(t *testing.T) { Text: "txtTXTtxt", } - _, err := client.AddRecord(context.Background(), record) + _, err := client.AddRecord(t.Context(), record) require.Error(t, err) assert.EqualError(t, err, "Status: error, ErrorMessage: error message, StackTrace: application stack trace, InnerErrorMessage: inner exception message") @@ -85,7 +84,7 @@ func TestClient_DeleteRecord(t *testing.T) { Text: "txtTXTtxt", } - err := client.DeleteRecord(context.Background(), record) + err := client.DeleteRecord(t.Context(), record) require.NoError(t, err) } @@ -98,7 +97,7 @@ func TestClient_DeleteRecord_error(t *testing.T) { Text: "txtTXTtxt", } - err := client.DeleteRecord(context.Background(), record) + err := client.DeleteRecord(t.Context(), record) require.Error(t, err) assert.EqualError(t, err, "Status: error, ErrorMessage: error message, StackTrace: application stack trace, InnerErrorMessage: inner exception message") diff --git a/providers/dns/timewebcloud/internal/client_test.go b/providers/dns/timewebcloud/internal/client_test.go index 5bfa97fa0..c5a861f68 100644 --- a/providers/dns/timewebcloud/internal/client_test.go +++ b/providers/dns/timewebcloud/internal/client_test.go @@ -2,7 +2,6 @@ package internal import ( "bytes" - "context" "fmt" "io" "net/http" @@ -89,7 +88,7 @@ func TestClient_CreateRecord(t *testing.T) { SubDomain: "_acme-challenge", } - response, err := client.CreateRecord(context.Background(), "example.com.", payload) + response, err := client.CreateRecord(t.Context(), "example.com.", payload) require.NoError(t, err) expected := &DNSRecord{ @@ -111,7 +110,7 @@ func TestClient_CreateRecord_error(t *testing.T) { } }) - _, err := client.CreateRecord(context.Background(), "example.com.", DNSRecord{}) + _, err := client.CreateRecord(t.Context(), "example.com.", DNSRecord{}) require.Error(t, err) assert.EqualError(t, err, "400: Value must be a number conforming to the specified constraints (bad_request) [15095f25-aac3-4d60-a788-96cb5136f186]") @@ -130,7 +129,7 @@ func TestClient_DeleteRecord(t *testing.T) { rw.WriteHeader(http.StatusNoContent) }) - err := client.DeleteRecord(context.Background(), "example.com.", 123) + err := client.DeleteRecord(t.Context(), "example.com.", 123) require.NoError(t, err) } @@ -145,7 +144,7 @@ func TestClient_DeleteRecord_error(t *testing.T) { } }) - err := client.DeleteRecord(context.Background(), "example.com.", 123) + err := client.DeleteRecord(t.Context(), "example.com.", 123) require.Error(t, err) assert.EqualError(t, err, "401: Unauthorized (unauthorized) [15095f25-aac3-4d60-a788-96cb5136f186]") diff --git a/providers/dns/variomedia/internal/client_test.go b/providers/dns/variomedia/internal/client_test.go index c0017f24a..0daa64f7a 100644 --- a/providers/dns/variomedia/internal/client_test.go +++ b/providers/dns/variomedia/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -27,7 +26,7 @@ func setupTest(t *testing.T) (*Client, *http.ServeMux) { return client, mux } -func mockHandler(method string, filename string) http.HandlerFunc { +func mockHandler(method, filename string) http.HandlerFunc { return func(rw http.ResponseWriter, req *http.Request) { if req.Method != method { http.Error(rw, fmt.Sprintf("invalid method, got %s want %s", req.Method, method), http.StatusBadRequest) @@ -72,7 +71,7 @@ func TestClient_CreateDNSRecord(t *testing.T) { TTL: 300, } - resp, err := client.CreateDNSRecord(context.Background(), record) + resp, err := client.CreateDNSRecord(t.Context(), record) require.NoError(t, err) expected := &CreateDNSRecordResponse{ @@ -112,7 +111,7 @@ func TestClient_DeleteDNSRecord(t *testing.T) { mux.HandleFunc("/dns-records/test", mockHandler(http.MethodDelete, "DELETE_dns-records_pending.json")) - resp, err := client.DeleteDNSRecord(context.Background(), "test") + resp, err := client.DeleteDNSRecord(t.Context(), "test") require.NoError(t, err) expected := &DeleteRecordResponse{ @@ -147,7 +146,7 @@ func TestClient_GetJob(t *testing.T) { mux.HandleFunc("/queue-jobs/test", mockHandler(http.MethodGet, "GET_queue-jobs.json")) - resp, err := client.GetJob(context.Background(), "test") + resp, err := client.GetJob(t.Context(), "test") require.NoError(t, err) expected := &GetJobResponse{ diff --git a/providers/dns/variomedia/variomedia.go b/providers/dns/variomedia/variomedia.go index 0f2c73c05..548d8bab8 100644 --- a/providers/dns/variomedia/variomedia.go +++ b/providers/dns/variomedia/variomedia.go @@ -178,7 +178,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } -func (d *DNSProvider) waitJob(ctx context.Context, domain string, id string) error { +func (d *DNSProvider) waitJob(ctx context.Context, domain, id string) error { return wait.For("variomedia: apply change on "+domain, d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) { result, err := d.client.GetJob(ctx, id) if err != nil { diff --git a/providers/dns/vercel/internal/client.go b/providers/dns/vercel/internal/client.go index 4bc59ba0c..d852689ae 100644 --- a/providers/dns/vercel/internal/client.go +++ b/providers/dns/vercel/internal/client.go @@ -61,7 +61,7 @@ func (c *Client) CreateRecord(ctx context.Context, zone string, record Record) ( // DeleteRecord deletes a DNS record. // https://vercel.com/docs/rest-api#endpoints/dns/delete-a-dns-record -func (c *Client) DeleteRecord(ctx context.Context, zone string, recordID string) error { +func (c *Client) DeleteRecord(ctx context.Context, zone, recordID string) error { endpoint := c.baseURL.JoinPath("v2", "domains", dns01.UnFqdn(zone), "records", recordID) req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) diff --git a/providers/dns/vercel/internal/client_test.go b/providers/dns/vercel/internal/client_test.go index 771349b25..2a8b4eaea 100644 --- a/providers/dns/vercel/internal/client_test.go +++ b/providers/dns/vercel/internal/client_test.go @@ -2,7 +2,6 @@ package internal import ( "bytes" - "context" "fmt" "io" "net/http" @@ -75,7 +74,7 @@ func TestClient_CreateRecord(t *testing.T) { TTL: 60, } - resp, err := client.CreateRecord(context.Background(), "example.com.", record) + resp, err := client.CreateRecord(t.Context(), "example.com.", record) require.NoError(t, err) expected := &CreateRecordResponse{ @@ -109,6 +108,6 @@ func TestClient_DeleteRecord(t *testing.T) { rw.WriteHeader(http.StatusOK) }) - err := client.DeleteRecord(context.Background(), "example.com.", "1234567") + err := client.DeleteRecord(t.Context(), "example.com.", "1234567") require.NoError(t, err) } diff --git a/providers/dns/versio/internal/client.go b/providers/dns/versio/internal/client.go index 6f70aacd2..e91913556 100644 --- a/providers/dns/versio/internal/client.go +++ b/providers/dns/versio/internal/client.go @@ -26,7 +26,7 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(username string, password string) *Client { +func NewClient(username, password string) *Client { baseURL, _ := url.Parse(DefaultBaseURL) return &Client{ diff --git a/providers/dns/versio/internal/client_test.go b/providers/dns/versio/internal/client_test.go index f1015d28a..63b80ce4a 100644 --- a/providers/dns/versio/internal/client_test.go +++ b/providers/dns/versio/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -58,7 +57,7 @@ func TestClient_GetDomain(t *testing.T) { writeFixture(rw, "get-domain.json") }) - records, err := client.GetDomain(context.Background(), "example.com") + records, err := client.GetDomain(t.Context(), "example.com") require.NoError(t, err) expected := &DomainInfoResponse{DomainInfo: DomainInfo{DNSRecords: []Record{ @@ -91,7 +90,7 @@ func TestClient_GetDomain_error(t *testing.T) { writeFixture(rw, "get-domain-error.json") }) - _, err := client.GetDomain(context.Background(), "example.com") + _, err := client.GetDomain(t.Context(), "example.com") require.ErrorAs(t, err, &ErrorMessage{}) } @@ -126,7 +125,7 @@ func TestClient_UpdateDomain(t *testing.T) { {Type: "A", Name: "redirect.example.com", Value: "localhost", Priority: 10, TTL: 14400}, }} - records, err := client.UpdateDomain(context.Background(), "example.com", msg) + records, err := client.UpdateDomain(t.Context(), "example.com", msg) require.NoError(t, err) expected := &DomainInfoResponse{DomainInfo: DomainInfo{DNSRecords: []Record{ @@ -174,6 +173,6 @@ func TestClient_UpdateDomain_error(t *testing.T) { {Type: "A", Name: "redirect.example.com", Value: "localhost", Priority: 10, TTL: 14400}, }} - _, err := client.UpdateDomain(context.Background(), "example.com", msg) + _, err := client.UpdateDomain(t.Context(), "example.com", msg) require.ErrorAs(t, err, &ErrorMessage{}) } diff --git a/providers/dns/volcengine/volcengine.go b/providers/dns/volcengine/volcengine.go index a26fb88b0..a271e0f26 100644 --- a/providers/dns/volcengine/volcengine.go +++ b/providers/dns/volcengine/volcengine.go @@ -13,7 +13,6 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/internal/ptr" - "github.com/miekg/dns" "github.com/volcengine/volc-sdk-golang/base" volc "github.com/volcengine/volc-sdk-golang/service/dns" ) @@ -175,9 +174,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } func (d *DNSProvider) getZone(ctx context.Context, fqdn string) (volc.TopZoneResponse, error) { - for _, index := range dns.Split(fqdn) { - domain := fqdn[index:] - + for domain := range dns01.UnFqdnDomainsSeq(fqdn) { lzr := &volc.ListZonesRequest{ Key: ptr.Pointer(dns01.UnFqdn(domain)), SearchMode: ptr.Pointer("exact"), diff --git a/providers/dns/vultr/vultr_test.go b/providers/dns/vultr/vultr_test.go index 71d8ad414..aed891628 100644 --- a/providers/dns/vultr/vultr_test.go +++ b/providers/dns/vultr/vultr_test.go @@ -1,7 +1,6 @@ package vultr import ( - "context" "encoding/json" "fmt" "net/http" @@ -189,10 +188,7 @@ func TestDNSProvider_getHostedZone(t *testing.T) { start = cursor * len(domains) } - end := (cursor + 1) * perPage - if len(domains) < end { - end = len(domains) - } + end := min(len(domains), (cursor+1)*perPage) db := domainsBase{ Domains: domains[start:end], @@ -209,7 +205,7 @@ func TestDNSProvider_getHostedZone(t *testing.T) { } }) - zone, err := p.getHostedZone(context.Background(), test.domain) + zone, err := p.getHostedZone(t.Context(), test.domain) require.NoError(t, err) assert.Equal(t, test.expected, zone) diff --git a/providers/dns/webnames/internal/client_test.go b/providers/dns/webnames/internal/client_test.go index 8885c50d6..ae14829a6 100644 --- a/providers/dns/webnames/internal/client_test.go +++ b/providers/dns/webnames/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -106,7 +105,7 @@ func TestClient_AddTXTRecord(t *testing.T) { subDomain := "foo" content := "txtTXTtxt" - err := client.AddTXTRecord(context.Background(), domain, subDomain, content) + err := client.AddTXTRecord(t.Context(), domain, subDomain, content) test.require(t, err) }) } @@ -146,7 +145,7 @@ func TestClient_RemoveTxtRecord(t *testing.T) { subDomain := "foo" content := "txtTXTtxt" - err := client.RemoveTXTRecord(context.Background(), domain, subDomain, content) + err := client.RemoveTXTRecord(t.Context(), domain, subDomain, content) test.require(t, err) }) } diff --git a/providers/dns/wedos/internal/client.go b/providers/dns/wedos/internal/client.go index defcabf6c..1f573e397 100644 --- a/providers/dns/wedos/internal/client.go +++ b/providers/dns/wedos/internal/client.go @@ -26,7 +26,7 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(username string, password string) *Client { +func NewClient(username, password string) *Client { return &Client{ username: username, password: password, @@ -87,7 +87,7 @@ func (c *Client) AddRecord(ctx context.Context, zone string, record DNSRow) erro // DeleteRecord deletes a record from the zone. // If a record does not have an ID, it will be looked up. // https://kb.wedos.com/en/wapi-api-interface/wapi-command-dns-row-delete/ -func (c *Client) DeleteRecord(ctx context.Context, zone string, recordID string) error { +func (c *Client) DeleteRecord(ctx context.Context, zone, recordID string) error { payload := DNSRowRequest{ Domain: dns01.UnFqdn(zone), ID: recordID, diff --git a/providers/dns/wedos/internal/client_test.go b/providers/dns/wedos/internal/client_test.go index 30c7d4863..4e011816b 100644 --- a/providers/dns/wedos/internal/client_test.go +++ b/providers/dns/wedos/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -13,7 +12,7 @@ import ( "github.com/stretchr/testify/require" ) -func setupNew(t *testing.T, expectedForm string, filename string) *Client { +func setupNew(t *testing.T, expectedForm, filename string) *Client { t.Helper() mux := http.NewServeMux() @@ -58,7 +57,7 @@ func TestClient_GetRecords(t *testing.T) { expectedForm := `{"request":{"user":"user","auth":"xxx","command":"dns-rows-list","data":{"domain":"example.com"}}}` client := setupNew(t, expectedForm, commandDNSRowsList) - records, err := client.GetRecords(context.Background(), "example.com.") + records, err := client.GetRecords(t.Context(), "example.com.") require.NoError(t, err) assert.Len(t, records, 4) @@ -107,7 +106,7 @@ func TestClient_AddRecord(t *testing.T) { Data: "foobar", } - err := client.AddRecord(context.Background(), "example.com.", record) + err := client.AddRecord(t.Context(), "example.com.", record) require.NoError(t, err) } @@ -124,7 +123,7 @@ func TestClient_AddRecord_update(t *testing.T) { Data: "foobar", } - err := client.AddRecord(context.Background(), "example.com.", record) + err := client.AddRecord(t.Context(), "example.com.", record) require.NoError(t, err) } @@ -133,7 +132,7 @@ func TestClient_DeleteRecord(t *testing.T) { client := setupNew(t, expectedForm, commandDNSRowDelete) - err := client.DeleteRecord(context.Background(), "example.com.", "1") + err := client.DeleteRecord(t.Context(), "example.com.", "1") require.NoError(t, err) } @@ -142,6 +141,6 @@ func TestClient_Commit(t *testing.T) { client := setupNew(t, expectedForm, commandDNSDomainCommit) - err := client.Commit(context.Background(), "example.com.") + err := client.Commit(t.Context(), "example.com.") require.NoError(t, err) } diff --git a/providers/dns/wedos/internal/token.go b/providers/dns/wedos/internal/token.go index 6be590f67..dd126b442 100644 --- a/providers/dns/wedos/internal/token.go +++ b/providers/dns/wedos/internal/token.go @@ -8,7 +8,7 @@ import ( "time" ) -func authToken(userName string, wapiPass string) string { +func authToken(userName, wapiPass string) string { return sha1string(userName + sha1string(wapiPass) + czechHourString()) } diff --git a/providers/dns/westcn/internal/client_test.go b/providers/dns/westcn/internal/client_test.go index ed0c7dc1a..6e21d7f61 100644 --- a/providers/dns/westcn/internal/client_test.go +++ b/providers/dns/westcn/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -128,7 +127,7 @@ func TestClientAddRecord(t *testing.T) { TTL: 60, } - id, err := client.AddRecord(context.Background(), record) + id, err := client.AddRecord(t.Context(), record) require.NoError(t, err) assert.Equal(t, 123456, id) @@ -145,7 +144,7 @@ func TestClientAddRecord_error(t *testing.T) { TTL: 60, } - _, err := client.AddRecord(context.Background(), record) + _, err := client.AddRecord(t.Context(), record) require.Error(t, err) require.EqualError(t, err, "10000: username,time,token必传 (500)") @@ -157,14 +156,14 @@ func TestClientDeleteRecord(t *testing.T) { expectValue("domain", "example.com"), ) - err := client.DeleteRecord(context.Background(), "example.com", 123) + err := client.DeleteRecord(t.Context(), "example.com", 123) require.NoError(t, err) } func TestClientDeleteRecord_error(t *testing.T) { client := setupTest(t, "error.json", noop()) - err := client.DeleteRecord(context.Background(), "example.com", 123) + err := client.DeleteRecord(t.Context(), "example.com", 123) require.Error(t, err) require.EqualError(t, err, "10000: username,time,token必传 (500)") diff --git a/providers/dns/yandex/internal/client_test.go b/providers/dns/yandex/internal/client_test.go index 67166ee85..55de81bc7 100644 --- a/providers/dns/yandex/internal/client_test.go +++ b/providers/dns/yandex/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "encoding/json" "net/http" "net/http/httptest" @@ -124,7 +123,7 @@ func TestAddRecord(t *testing.T) { mux.HandleFunc("/add", test.handler) - record, err := client.AddRecord(context.Background(), test.data) + record, err := client.AddRecord(t.Context(), test.data) if test.expectError { require.Error(t, err) require.Nil(t, record) @@ -219,7 +218,7 @@ func TestRemoveRecord(t *testing.T) { mux.HandleFunc("/del", test.handler) - id, err := client.RemoveRecord(context.Background(), test.data) + id, err := client.RemoveRecord(t.Context(), test.data) if test.expectError { require.Error(t, err) require.Equal(t, 0, id) @@ -315,7 +314,7 @@ func TestGetRecords(t *testing.T) { mux.HandleFunc("/list", test.handler) - records, err := client.GetRecords(context.Background(), test.domain) + records, err := client.GetRecords(t.Context(), test.domain) if test.expectError { require.Error(t, err) require.Empty(t, records) diff --git a/providers/dns/yandex360/internal/client_test.go b/providers/dns/yandex360/internal/client_test.go index d0ddac0c3..83f66800f 100644 --- a/providers/dns/yandex360/internal/client_test.go +++ b/providers/dns/yandex360/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -63,7 +62,7 @@ func TestClient_AddRecord(t *testing.T) { Type: "TXT", } - newRecord, err := client.AddRecord(context.Background(), "example.com", record) + newRecord, err := client.AddRecord(t.Context(), "example.com", record) require.NoError(t, err) expected := &Record{ @@ -87,7 +86,7 @@ func TestClient_AddRecord_error(t *testing.T) { Type: "TXT", } - newRecord, err := client.AddRecord(context.Background(), "example.com", record) + newRecord, err := client.AddRecord(t.Context(), "example.com", record) require.Error(t, err) assert.Nil(t, newRecord) @@ -96,13 +95,13 @@ func TestClient_AddRecord_error(t *testing.T) { func TestClient_DeleteRecord(t *testing.T) { client := setupTest(t, "/directory/v1/org/123456/domains/example.com/dns/789456", http.MethodDelete, http.StatusOK, "delete-record.json") - err := client.DeleteRecord(context.Background(), "example.com", 789456) + err := client.DeleteRecord(t.Context(), "example.com", 789456) require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { client := setupTest(t, "/directory/v1/org/123456/domains/example.com/dns/789456", http.MethodDelete, http.StatusUnauthorized, "error.json") - err := client.DeleteRecord(context.Background(), "example.com", 789456) + err := client.DeleteRecord(t.Context(), "example.com", 789456) require.Error(t, err) } diff --git a/providers/dns/zoneee/internal/client.go b/providers/dns/zoneee/internal/client.go index e4463b83e..9446cd771 100644 --- a/providers/dns/zoneee/internal/client.go +++ b/providers/dns/zoneee/internal/client.go @@ -26,7 +26,7 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(username string, apiKey string) *Client { +func NewClient(username, apiKey string) *Client { baseURL, _ := url.Parse(DefaultEndpoint) return &Client{ diff --git a/providers/dns/zoneee/internal/client_test.go b/providers/dns/zoneee/internal/client_test.go index 9e53117ac..04676877f 100644 --- a/providers/dns/zoneee/internal/client_test.go +++ b/providers/dns/zoneee/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "fmt" "io" "net/http" @@ -59,7 +58,7 @@ func setupTest(t *testing.T, method, pattern string, status int, file string) *C func TestClient_GetTxtRecords(t *testing.T) { client := setupTest(t, http.MethodGet, "/dns/example.com/txt", http.StatusOK, "get-txt-records.json") - records, err := client.GetTxtRecords(context.Background(), "example.com") + records, err := client.GetTxtRecords(t.Context(), "example.com") require.NoError(t, err) expected := []TXTRecord{ @@ -72,7 +71,7 @@ func TestClient_GetTxtRecords(t *testing.T) { func TestClient_AddTxtRecord(t *testing.T) { client := setupTest(t, http.MethodPost, "/dns/example.com/txt", http.StatusCreated, "create-txt-record.json") - records, err := client.AddTxtRecord(context.Background(), "example.com", TXTRecord{Name: "prefix.example.com", Destination: "server.example.com"}) + records, err := client.AddTxtRecord(t.Context(), "example.com", TXTRecord{Name: "prefix.example.com", Destination: "server.example.com"}) require.NoError(t, err) expected := []TXTRecord{ @@ -85,6 +84,6 @@ func TestClient_AddTxtRecord(t *testing.T) { func TestClient_RemoveTxtRecord(t *testing.T) { client := setupTest(t, http.MethodDelete, "/dns/example.com/txt/123", http.StatusNoContent, "") - err := client.RemoveTxtRecord(context.Background(), "example.com", "123") + err := client.RemoveTxtRecord(t.Context(), "example.com", "123") require.NoError(t, err) } From 96b18d764dced95d9cb4d0142e2c684d73c974fb Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 10 Jul 2025 12:13:19 +0200 Subject: [PATCH 118/298] feat: add option to define dynamically the renew date (#2574) Co-authored-by: Dominik Menke --- cmd/cmd_renew.go | 53 +++++++++++++++++++++++++++--------- cmd/cmd_renew_test.go | 55 +++++++++++++++++++++++++++++++++++++- docs/data/zz_cli_help.toml | 2 ++ 3 files changed, 96 insertions(+), 14 deletions(-) diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index a46de187c..7d968d2a3 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -20,7 +20,8 @@ import ( // Flag names. const ( - flgDays = "days" + flgRenewDays = "days" + flgRenewDynamic = "dynamic" flgARIDisable = "ari-disable" flgARIWaitToRenewDuration = "ari-wait-to-renew-duration" flgReuseKey = "reuse-key" @@ -52,10 +53,16 @@ func createRenew() *cli.Command { }, Flags: []cli.Flag{ &cli.IntFlag{ - Name: flgDays, + Name: flgRenewDays, Value: 30, Usage: "The number of days left on a certificate to renew it.", }, + // TODO(ldez): in v5, remove this flag, use this behavior as default. + &cli.BoolFlag{ + Name: flgRenewDynamic, + Value: false, + Usage: "Compute dynamically, based on the lifetime of the certificate(s), when to renew: use 1/3rd of the lifetime left, or 1/2 of the lifetime for short-lived certificates). This supersedes --days and will be the default behavior in Lego v5.", + }, &cli.BoolFlag{ Name: flgARIDisable, Usage: "Do not use the renewalInfo endpoint (RFC9773) to check if a certificate should be renewed.", @@ -187,7 +194,7 @@ func renewForDomains(ctx *cli.Context, account *Account, keyType certcrypto.KeyT certDomains := certcrypto.ExtractDomains(cert) - if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int(flgDays)) && + if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int(flgRenewDays), ctx.Bool(flgRenewDynamic)) && (!forceDomains || slices.Equal(certDomains, domains)) { return nil } @@ -304,7 +311,7 @@ func renewForCSR(ctx *cli.Context, account *Account, keyType certcrypto.KeyType, } } - if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int(flgDays)) { + if ariRenewalTime == nil && !needRenewal(cert, domain, ctx.Int(flgRenewDays), ctx.Bool(flgRenewDynamic)) { return nil } @@ -342,21 +349,41 @@ func renewForCSR(ctx *cli.Context, account *Account, keyType certcrypto.KeyType, return launchHook(ctx.String(flgRenewHook), ctx.Duration(flgRenewHookTimeout), meta) } -func needRenewal(x509Cert *x509.Certificate, domain string, days int) bool { +func needRenewal(x509Cert *x509.Certificate, domain string, days int, dynamic bool) bool { if x509Cert.IsCA { log.Fatalf("[%s] Certificate bundle starts with a CA certificate", domain) } - if days >= 0 { - notAfter := int(time.Until(x509Cert.NotAfter).Hours() / 24.0) - if notAfter > days { - log.Printf("[%s] The certificate expires in %d days, the number of days defined to perform the renewal is %d: no renewal.", - domain, notAfter, days) - return false - } + if dynamic { + return needRenewalDynamic(x509Cert, time.Now()) } - return true + if days < 0 { + return true + } + + notAfter := int(time.Until(x509Cert.NotAfter).Hours() / 24.0) + if notAfter <= days { + return true + } + + log.Printf("[%s] The certificate expires in %d days, the number of days defined to perform the renewal is %d: no renewal.", + domain, notAfter, days) + + return false +} + +func needRenewalDynamic(x509Cert *x509.Certificate, now time.Time) bool { + lifetime := x509Cert.NotAfter.Sub(x509Cert.NotBefore) + + var divisor int64 = 3 + if lifetime.Round(24*time.Hour).Hours()/24.0 <= 10 { + divisor = 2 + } + + dueDate := x509Cert.NotAfter.Add(-1 * time.Duration(lifetime.Nanoseconds()/divisor)) + + return dueDate.Before(now) } // getARIRenewalTime checks if the certificate needs to be renewed using the renewalInfo endpoint. diff --git a/cmd/cmd_renew_test.go b/cmd/cmd_renew_test.go index f88ad74c5..405dda5fa 100644 --- a/cmd/cmd_renew_test.go +++ b/cmd/cmd_renew_test.go @@ -108,9 +108,62 @@ func Test_needRenewal(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - actual := needRenewal(test.x509Cert, "foo.com", test.days) + actual := needRenewal(test.x509Cert, "foo.com", test.days, false) assert.Equal(t, test.expected, actual) }) } } + +func Test_needRenewalDynamic(t *testing.T) { + testCases := []struct { + desc string + now time.Time + notBefore, notAfter time.Time + expected assert.BoolAssertionFunc + }{ + { + desc: "higher than 1/3 of the certificate lifetime left (lifetime > 10 days)", + now: time.Date(2025, 1, 19, 1, 1, 1, 1, time.UTC), + notBefore: time.Date(2025, 1, 1, 1, 1, 1, 1, time.UTC), + notAfter: time.Date(2025, 1, 30, 1, 1, 1, 1, time.UTC), + expected: assert.False, + }, + { + desc: "lower than 1/3 of the certificate lifetime left(lifetime > 10 days)", + now: time.Date(2025, 1, 21, 1, 1, 1, 1, time.UTC), + notBefore: time.Date(2025, 1, 1, 1, 1, 1, 1, time.UTC), + notAfter: time.Date(2025, 1, 30, 1, 1, 1, 1, time.UTC), + expected: assert.True, + }, + { + desc: "higher than 1/2 of the certificate lifetime left (lifetime < 10 days)", + now: time.Date(2025, 1, 4, 1, 1, 1, 1, time.UTC), + notBefore: time.Date(2025, 1, 1, 1, 1, 1, 1, time.UTC), + notAfter: time.Date(2025, 1, 10, 1, 1, 1, 1, time.UTC), + expected: assert.False, + }, + { + desc: "lower than 1/2 of the certificate lifetime left (lifetime < 10 days)", + now: time.Date(2025, 1, 6, 1, 1, 1, 1, time.UTC), + notBefore: time.Date(2025, 1, 1, 1, 1, 1, 1, time.UTC), + notAfter: time.Date(2025, 1, 10, 1, 1, 1, 1, time.UTC), + expected: assert.True, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + x509Cert := &x509.Certificate{ + NotBefore: test.notBefore, + NotAfter: test.notAfter, + } + + ok := needRenewalDynamic(x509Cert, test.now) + + test.expected(t, ok) + }) + } +} diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 726392df5..723f063cd 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -23,6 +23,7 @@ GLOBAL OPTIONS: --server value, -s value CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. (default: "https://acme-v02.api.letsencrypt.org/directory") [$LEGO_SERVER] --accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service. (default: false) --email value, -m value Email used for registration and recovery contact. [$LEGO_EMAIL] + --disable-cn value Disable the use of the common name in the CSR. [$disable-cn] --csr value, -c value Certificate signing request filename, if an external CSR is to be used. --eab Use External Account Binding for account registration. Requires --kid and --hmac. (default: false) [$LEGO_EAB] --kid value Key identifier from External CA. Used for External Account Binding. [$LEGO_EAB_KID] @@ -93,6 +94,7 @@ USAGE: OPTIONS: --days value The number of days left on a certificate to renew it. (default: 30) + --dynamic Dynamically defines the renewal date. (1/3rd of the lifetime left or 1/2 of the lifetime left, if the lifetime is shorter than 10 days) (default: false) --ari-disable Do not use the renewalInfo endpoint (RFC9773) to check if a certificate should be renewed. (default: false) --ari-wait-to-renew-duration value The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint. (default: 0s) --reuse-key Used to indicate you want to reuse your current private key for the new certificate. (default: false) From bfa487cc48353077a8ffd94ab2a51e3fa361ad2a Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 10 Jul 2025 18:14:03 +0200 Subject: [PATCH 119/298] fix: enforce domain into renewal command (#2576) --- cmd/cmd_renew.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index 7d968d2a3..00bdf1b62 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -232,7 +232,7 @@ func renewForDomains(ctx *cli.Context, account *Account, keyType certcrypto.KeyT time.Sleep(sleepTime) } - renewalDomains := domains + renewalDomains := slices.Clone(domains) if !forceDomains { renewalDomains = merge(certDomains, domains) } @@ -258,6 +258,8 @@ func renewForDomains(ctx *cli.Context, account *Account, keyType certcrypto.KeyT log.Fatal(err) } + certRes.Domain = domain + certsStorage.SaveResource(certRes) addPathToMetadata(meta, domain, certRes, certsStorage) From fae73fdc5ddcc67fc0f7a4f457671278efa0eb2b Mon Sep 17 00:00:00 2001 From: Victor Hugo Date: Fri, 11 Jul 2025 17:34:25 -0300 Subject: [PATCH 120/298] vinyldns: add an option to add quotes around the TXT record value (#2580) Co-authored-by: Fernandez Ludovic --- cmd/zz_gen_cmd_dnshelp.go | 1 + docs/content/dns/zz_gen_vinyldns.md | 1 + providers/dns/vinyldns/vinyldns.go | 35 +++++++++++++++++++++------- providers/dns/vinyldns/vinyldns.toml | 1 + 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 8676edad5..8821d0d18 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -3249,6 +3249,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "VINYLDNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 4)`) ew.writeln(` - "VINYLDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "VINYLDNS_QUOTE_VALUE": Adds quotes around the TXT record value (Default: false)`) ew.writeln(` - "VINYLDNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 30)`) ew.writeln() diff --git a/docs/content/dns/zz_gen_vinyldns.md b/docs/content/dns/zz_gen_vinyldns.md index 03d4b6bb5..9a9c4bef0 100644 --- a/docs/content/dns/zz_gen_vinyldns.md +++ b/docs/content/dns/zz_gen_vinyldns.md @@ -53,6 +53,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). |--------------------------------|-------------| | `VINYLDNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 4) | | `VINYLDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `VINYLDNS_QUOTE_VALUE` | Adds quotes around the TXT record value (Default: false) | | `VINYLDNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 30) | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/providers/dns/vinyldns/vinyldns.go b/providers/dns/vinyldns/vinyldns.go index a206602da..9e36ccc51 100644 --- a/providers/dns/vinyldns/vinyldns.go +++ b/providers/dns/vinyldns/vinyldns.go @@ -4,6 +4,7 @@ package vinyldns import ( "errors" "fmt" + "strconv" "time" "github.com/go-acme/lego/v4/challenge" @@ -17,9 +18,10 @@ import ( const ( envNamespace = "VINYLDNS_" - EnvAccessKey = envNamespace + "ACCESS_KEY" - EnvSecretKey = envNamespace + "SECRET_KEY" - EnvHost = envNamespace + "HOST" + EnvAccessKey = envNamespace + "ACCESS_KEY" + EnvSecretKey = envNamespace + "SECRET_KEY" + EnvHost = envNamespace + "HOST" + EnvQuoteValue = envNamespace + "QUOTE_VALUE" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -30,9 +32,11 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - AccessKey string - SecretKey string - Host string + AccessKey string + SecretKey string + Host string + QuoteValue bool + TTL int PropagationTimeout time.Duration PollingInterval time.Duration @@ -66,6 +70,7 @@ func NewDNSProvider() (*DNSProvider, error) { config.AccessKey = values[EnvAccessKey] config.SecretKey = values[EnvSecretKey] config.Host = values[EnvHost] + config.QuoteValue = env.GetOrDefaultBool(EnvQuoteValue, false) return NewDNSProviderConfig(config) } @@ -105,7 +110,9 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("vinyldns: %w", err) } - record := vinyldns.Record{Text: info.Value} + value := d.formatValue(info.Value) + + record := vinyldns.Record{Text: value} if existingRecord == nil || existingRecord.ID == "" { err = d.createRecordSet(info.EffectiveFQDN, []vinyldns.Record{record}) @@ -117,7 +124,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { } for _, i := range existingRecord.Records { - if i.Text == info.Value { + if i.Text == value { return nil } } @@ -146,9 +153,11 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } + value := d.formatValue(info.Value) + var records []vinyldns.Record for _, i := range existingRecord.Records { - if i.Text != info.Value { + if i.Text != value { records = append(records, i) } } @@ -175,3 +184,11 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } + +func (d *DNSProvider) formatValue(v string) string { + if d.config.QuoteValue { + return strconv.Quote(v) + } + + return v +} diff --git a/providers/dns/vinyldns/vinyldns.toml b/providers/dns/vinyldns/vinyldns.toml index 6acc623fd..8c9f1b3a6 100644 --- a/providers/dns/vinyldns/vinyldns.toml +++ b/providers/dns/vinyldns/vinyldns.toml @@ -22,6 +22,7 @@ Users are required to have DELETE ACL level or zone admin permissions on the Vin VINYLDNS_SECRET_KEY = "The VinylDNS API Secret key" VINYLDNS_HOST = "The VinylDNS API URL" [Configuration.Additional] + VINYLDNS_QUOTE_VALUE = "Adds quotes around the TXT record value (Default: false)" VINYLDNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 4)" VINYLDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" VINYLDNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 30)" From 52e167c93000c231979692f5ae28ec71feda2f50 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 12 Jul 2025 13:57:15 +0200 Subject: [PATCH 121/298] test: server client mock (#2571) --- go.mod | 1 + platform/tester/servermock/builder.go | 72 ++++ platform/tester/servermock/handler_dump.go | 20 + platform/tester/servermock/handler_file.go | 77 ++++ platform/tester/servermock/handler_json.go | 39 ++ platform/tester/servermock/handler_noop.go | 45 ++ platform/tester/servermock/handler_raw.go | 61 +++ platform/tester/servermock/link_form.go | 94 +++++ platform/tester/servermock/link_headers.go | 177 ++++++++ platform/tester/servermock/link_query.go | 97 +++++ .../tester/servermock/link_request_body.go | 89 ++++ .../servermock/link_request_body_json.go | 99 +++++ providers/dns/acmedns/acmedns_test.go | 42 +- .../internal/fixtures/request-body.json | 7 + .../dns/acmedns/internal/http_storage_test.go | 86 ++-- providers/dns/allinkl/internal/client_test.go | 74 ++-- .../fixtures/add_dns_settings-request.xml | 7 + .../fixtures/delete_dns_settings-request.xml | 7 + .../fixtures/get_dns_settings-request.xml | 7 + .../dns/allinkl/internal/identity_test.go | 31 +- .../dns/arvancloud/internal/client_test.go | 108 ++--- .../fixtures/create_record-request.json | 8 + providers/dns/auroradns/auroradns_test.go | 128 +++--- providers/dns/autodns/internal/client_test.go | 77 ++-- .../internal/fixtures/add_record-request.json | 11 + .../{add-record.json => add_record.json} | 0 .../fixtures/remove_record-request.json | 11 + ...{remove-record.json => remove_record.json} | 0 .../dns/axelname/internal/client_test.go | 87 ++-- providers/dns/azion/azion_test.go | 65 ++- providers/dns/bluecat/internal/client_test.go | 40 +- .../dns/bluecat/internal/identity_test.go | 47 +-- .../dns/bookmyname/internal/client_test.go | 92 +++-- providers/dns/brandit/internal/client_test.go | 111 +++-- .../dns/checkdomain/internal/client_test.go | 273 +++---------- .../fixtures/create_record-request.json | 7 + .../fixtures/delete_txt_record-request.json | 16 + .../dns/clouddns/internal/client_test.go | 144 ++----- .../internal/fixtures/domain-request.json | 14 + .../fixtures/domain_search-request.json | 14 + .../internal/fixtures/domain_search.json | 8 + .../internal/fixtures/login-request.json | 4 + .../dns/clouddns/internal/fixtures/login.json | 5 + .../internal/fixtures/publish-request.json | 3 + .../internal/fixtures/record_txt-request.json | 6 + .../dns/clouddns/internal/identity_test.go | 32 +- providers/dns/cloudns/internal/client_test.go | 201 +++++---- providers/dns/cloudru/internal/client_test.go | 80 ++-- .../dns/cloudru/internal/identity_test.go | 90 ++-- providers/dns/conoha/internal/client_test.go | 123 ++---- .../dns/conoha/internal/fixtures/empty.json | 1 + .../dns/conoha/internal/identity_test.go | 22 +- .../dns/conohav3/internal/client_test.go | 124 ++---- .../dns/conohav3/internal/fixtures/empty.json | 1 + .../dns/conohav3/internal/identity_test.go | 28 +- .../dns/constellix/internal/domains_test.go | 74 +--- .../constellix/internal/txtrecords_test.go | 136 +----- .../dns/corenetworks/internal/client_test.go | 163 ++------ .../corenetworks/internal/identity_test.go | 24 ++ .../dns/cpanel/internal/cpanel/client_test.go | 108 ++--- .../dns/cpanel/internal/whm/client_test.go | 109 ++--- providers/dns/derak/internal/client_test.go | 159 +++----- .../dns/digitalocean/digitalocean_test.go | 83 ++-- .../dns/digitalocean/internal/client_test.go | 116 +----- .../dns/directadmin/internal/client_test.go | 133 +++--- .../dns/dnshomede/internal/client_test.go | 109 +++-- providers/dns/dnshomede/internal/readme.md | 4 +- providers/dns/dnsmadeeasy/internal/client.go | 5 +- .../dns/dnsmadeeasy/internal/client_test.go | 120 +++++- .../fixtures/create_record-request.json | 8 + .../internal/fixtures/get_records.json | 20 + providers/dns/dode/internal/client_test.go | 82 +--- .../dns/domeneshop/internal/client_test.go | 160 ++------ .../fixtures/create_record-request.json | 7 + .../internal/fixtures/create_record.json | 3 + .../internal/fixtures/delete_record.json | 9 + .../internal/fixtures/getDnsRecords.json | 9 + .../internal/fixtures/getDomains.json | 22 + providers/dns/dreamhost/dreamhost_test.go | 105 ++--- .../dns/dreamhost/internal/client_test.go | 46 ++- providers/dns/duckdns/internal/client.go | 4 +- providers/dns/duckdns/internal/client_test.go | 39 ++ providers/dns/dyn/internal/client_test.go | 108 ++--- providers/dns/dyn/internal/session_test.go | 19 +- .../dns/dyndnsfree/internal/client_test.go | 50 +-- providers/dns/dynu/internal/client_test.go | 102 ++--- .../fixtures/add_new_record-request.json | 9 + providers/dns/easydns/easydns_test.go | 377 +++++++---------- providers/dns/easydns/internal/client_test.go | 88 ++-- .../dns/efficientip/internal/client_test.go | 114 ++---- providers/dns/epik/internal/client_test.go | 117 +++--- providers/dns/f5xc/internal/client_test.go | 98 ++--- providers/dns/gandi/gandi_test.go | 48 ++- providers/dns/gandi/internal/client_test.go | 99 +++++ .../fixtures/add_txt_record-request.xml | 49 +++ .../internal/fixtures/clone_zone-request.xml | 31 ++ .../gandi/internal/fixtures/clone_zone.xml | 22 + .../internal/fixtures/delete_zone-request.xml | 14 + .../gandi/internal/fixtures/delete_zone.xml | 9 + .../dns/gandi/internal/fixtures/empty.xml | 2 + .../internal/fixtures/get_zone_id-request.xml | 14 + .../gandi/internal/fixtures/get_zone_id.xml | 22 + .../fixtures/new_zone_version-request.xml | 14 + .../internal/fixtures/new_zone_version.xml | 9 + .../internal/fixtures/set_zone-request.xml | 19 + .../dns/gandi/internal/fixtures/set_zone.xml | 22 + .../fixtures/set_zone_version-request.xml | 19 + .../internal/fixtures/set_zone_version.xml | 9 + providers/dns/gandiv5/gandiv5_test.go | 91 +---- providers/dns/gandiv5/internal/client_test.go | 48 +++ .../internal/fixtures/add_txt_record_get.json | 8 + .../internal/fixtures/api_response.json | 4 + providers/dns/gcloud/googlecloud_test.go | 361 +++++++--------- providers/dns/gcore/internal/client_test.go | 272 +++++------- providers/dns/glesys/internal/client_test.go | 73 +--- providers/dns/godaddy/internal/client_test.go | 115 ++---- .../fixtures/update_records-request.json | 38 ++ providers/dns/hetzner/internal/client_test.go | 160 ++------ .../fixtures/create_txt_record-request.json | 7 + .../dns/hosttech/internal/client_test.go | 136 +++--- providers/dns/httpreq/httpreq_test.go | 202 ++++----- .../dns/hurricane/internal/client_test.go | 44 +- .../dns/hyperone/internal/client_test.go | 146 +++---- .../dns/infomaniak/internal/client_test.go | 141 ++----- .../fixtures/create_dns_record-request.json | 6 + .../internal/fixtures/get_domain_name.json | 13 + .../dns/internal/active24/client_test.go | 99 +++-- .../dns/internal/hostingde/client_test.go | 104 +---- .../fixtures/zoneConfigsFind-request.json | 9 + .../fixtures/zoneUpdate-request.json | 35 ++ .../dns/internal/rimuhosting/client_test.go | 386 +++++++++--------- .../dns/internal/selectel/client_test.go | 156 ++----- .../selectel/fixtures/add_record-request.json | 7 + .../selectel/fixtures/add_record.json | 8 + .../dns/internetbs/internal/client_test.go | 114 +++--- .../internal/fixtures/auth_error.json | 6 + providers/dns/ionos/internal/client.go | 5 +- providers/dns/ionos/internal/client_test.go | 125 +++--- providers/dns/ipv64/internal/client_test.go | 90 ++-- .../dns/iwantmyname/internal/client_test.go | 72 +--- .../dns/joker/internal/dmapi/client_test.go | 51 ++- .../dns/joker/internal/dmapi/identity_test.go | 127 +++--- .../dns/joker/internal/svc/client_test.go | 91 ++--- providers/dns/liara/internal/client_test.go | 112 ++--- providers/dns/lightsail/lightsail_test.go | 37 +- providers/dns/lightsail/mock_server_test.go | 44 -- .../dns/limacity/internal/client_test.go | 132 +++--- providers/dns/linode/linode_test.go | 322 +++++++-------- providers/dns/liquidweb/liquidweb_test.go | 22 +- providers/dns/liquidweb/servermock_test.go | 140 +++---- providers/dns/loopia/internal/client_test.go | 187 ++++----- providers/dns/luadns/internal/client_test.go | 120 ++---- .../dns/manageengine/internal/client_test.go | 139 ++++--- .../dns/metaregistrar/internal/client.go | 4 +- .../dns/metaregistrar/internal/client_test.go | 63 ++- .../dns/mijnhost/internal/client_test.go | 85 ++-- .../dns/mittwald/internal/client_test.go | 106 +++-- providers/dns/myaddr/internal/client_test.go | 72 ++-- providers/dns/mydnsjp/internal/client_test.go | 93 ++--- .../dns/mythicbeasts/internal/client_test.go | 73 ++-- .../mythicbeasts/internal/fixtures/token.json | 5 + .../mythicbeasts/internal/identity_test.go | 70 ++-- .../dns/namecheap/internal/client_test.go | 165 +++----- providers/dns/namecheap/namecheap_test.go | 181 ++++---- .../nearlyfreespeech/internal/client_test.go | 127 ++---- .../dns/netcup/internal/client_live_test.go | 137 +++++++ providers/dns/netcup/internal/client_test.go | 261 ++---------- .../fixtures/get_dns_records-request.json | 9 + .../internal/fixtures/get_dns_records.json | 32 ++ .../fixtures/get_dns_records_error.json | 10 + .../get_dns_records_error_unmarshal.json | 10 + .../internal/fixtures/login-request.json | 8 + .../dns/netcup/internal/fixtures/login.json | 12 + .../netcup/internal/fixtures/login_error.json | 10 + .../fixtures/login_error_unmarshal.json | 10 + .../internal/fixtures/logout-request.json | 8 + .../dns/netcup/internal/fixtures/logout.json | 10 + .../internal/fixtures/logout_error.json | 10 + providers/dns/netcup/internal/session_test.go | 207 ++-------- providers/dns/netlify/internal/client_test.go | 120 ++---- .../dns/nicmanager/internal/client_test.go | 127 +++--- providers/dns/nicru/internal/client_test.go | 133 +++--- .../dns/nifcloud/internal/client_test.go | 62 +-- providers/dns/njalla/internal/client_test.go | 181 +++----- .../internal/fixtures/add_record-request.json | 10 + .../njalla/internal/fixtures/add_record.json | 12 + .../njalla/internal/fixtures/auth_error.json | 7 + .../fixtures/list_records-request.json | 6 + .../internal/fixtures/list_records.json | 24 ++ .../fixtures/remove_record-request.json | 7 + .../remove_record_error_missing_domain.json | 7 + .../remove_record_error_missing_id.json | 7 + providers/dns/otc/internal/client_test.go | 109 +++++ providers/dns/otc/internal/identity_test.go | 22 +- providers/dns/otc/internal/mock.go | 140 +------ providers/dns/otc/otc_test.go | 359 +++++++++++----- providers/dns/pdns/internal/client.go | 5 +- providers/dns/pdns/internal/client_test.go | 118 +++--- .../pdns/internal/fixtures/zone-request.json | 19 + providers/dns/plesk/internal/client_test.go | 98 ++--- providers/dns/rackspace/fixtures/delete.json | 7 + .../dns/rackspace/fixtures/identity.json | 31 ++ providers/dns/rackspace/fixtures/record.json | 8 + .../rackspace/fixtures/record_details.json | 13 + .../dns/rackspace/fixtures/zone_details.json | 12 + providers/dns/rackspace/internal/client.go | 6 +- .../dns/rackspace/internal/client_test.go | 86 ++-- .../dns/rackspace/internal/identity_test.go | 38 +- .../dns/rackspace/rackspace_mock_test.go | 87 ---- providers/dns/rackspace/rackspace_test.go | 194 ++++----- providers/dns/rainyun/internal/client_test.go | 97 +++-- .../dns/rcodezero/internal/client_test.go | 59 +-- providers/dns/regru/internal/client_test.go | 96 ++--- .../internal/fixtures/add_txt_record.json | 14 + .../fixtures/add_txt_record_error_auth.json | 10 + .../fixtures/add_txt_record_error_domain.json | 14 + .../internal/fixtures/remove_record.json | 14 + .../fixtures/remove_record_error_auth.json | 10 + .../fixtures/remove_record_error_domain.json | 14 + providers/dns/regru/internal/readme.md | 6 + .../changeResourceRecordSetsResponse.xml | 8 + .../route53/fixtures/getChangeResponse.xml | 8 + .../listHostedZonesByNameResponse.xml | 19 + providers/dns/route53/fixtures_test.go | 39 -- providers/dns/route53/mock_test.go | 51 --- providers/dns/route53/route53_test.go | 65 +-- providers/dns/safedns/internal/client_test.go | 119 +++--- .../internal/fixtures/add_record-request.json | 6 + .../safedns/internal/fixtures/add_record.json | 8 + .../dns/safedns/internal/fixtures/error.json | 3 + providers/dns/selectelv2/selectelv2.go | 20 +- .../dns/selfhostde/internal/client_test.go | 57 +-- .../dns/servercow/internal/client_test.go | 167 ++------ .../dns/shellrent/internal/client_test.go | 129 +++--- providers/dns/simply/internal/client_test.go | 126 +++--- providers/dns/sonic/internal/client_test.go | 28 +- .../dns/spaceship/internal/client_test.go | 86 ++-- .../dns/stackpath/internal/client_test.go | 111 ++--- .../internal/fixtures/get_zone_records.json | 6 + .../internal/fixtures/get_zones.json | 30 ++ .../dns/technitium/internal/client_test.go | 73 ++-- .../dns/timewebcloud/internal/client_test.go | 127 ++---- .../dns/variomedia/internal/client_test.go | 82 ++-- .../dns/vegadns/fixtures/create_record.json | 12 + .../dns/vegadns/fixtures/record_delete.json | 3 + providers/dns/vegadns/fixtures/records.json | 43 ++ providers/dns/vegadns/fixtures/token.json | 5 + providers/dns/vegadns/vegadns_mock_test.go | 85 ---- providers/dns/vegadns/vegadns_test.go | 261 ++++-------- providers/dns/vercel/internal/client_test.go | 95 ++--- .../fixtures/error_failToCreateTXT.json | 6 + .../versio/fixtures/error_failToFindZone.json | 6 + providers/dns/versio/fixtures/token.json | 5 + providers/dns/versio/internal/client_test.go | 108 ++--- .../fixtures/update-domain-request.json | 78 ++++ providers/dns/versio/versio_mock_test.go | 13 - providers/dns/versio/versio_test.go | 169 +++----- providers/dns/vinyldns/mock_test.go | 114 ------ providers/dns/vinyldns/vinyldns_test.go | 81 ++-- providers/dns/vkcloud/vkcloud.go | 30 +- providers/dns/vultr/vultr_test.go | 77 ++-- .../dns/webnames/internal/client_test.go | 109 ++--- providers/dns/wedos/internal/client_test.go | 111 ++--- providers/dns/westcn/internal/client_test.go | 166 +++----- providers/dns/yandex/internal/client_test.go | 380 +++++------------ .../yandex/internal/fixtures/add_record.json | 13 + .../internal/fixtures/add_record_error.json | 5 + .../yandex/internal/fixtures/get_records.json | 24 ++ .../internal/fixtures/get_records_error.json | 5 + .../internal/fixtures/remove_record.json | 5 + .../fixtures/remove_record_error.json | 6 + .../dns/yandex360/internal/client_test.go | 75 ++-- providers/dns/yandexcloud/yandexcloud.go | 38 +- providers/dns/zoneee/internal/client_test.go | 71 ++-- providers/dns/zoneee/zoneee_test.go | 258 +++++------- 275 files changed, 8813 insertions(+), 10238 deletions(-) create mode 100644 platform/tester/servermock/builder.go create mode 100644 platform/tester/servermock/handler_dump.go create mode 100644 platform/tester/servermock/handler_file.go create mode 100644 platform/tester/servermock/handler_json.go create mode 100644 platform/tester/servermock/handler_noop.go create mode 100644 platform/tester/servermock/handler_raw.go create mode 100644 platform/tester/servermock/link_form.go create mode 100644 platform/tester/servermock/link_headers.go create mode 100644 platform/tester/servermock/link_query.go create mode 100644 platform/tester/servermock/link_request_body.go create mode 100644 platform/tester/servermock/link_request_body_json.go create mode 100644 providers/dns/acmedns/internal/fixtures/request-body.json create mode 100644 providers/dns/allinkl/internal/fixtures/add_dns_settings-request.xml create mode 100644 providers/dns/allinkl/internal/fixtures/delete_dns_settings-request.xml create mode 100644 providers/dns/allinkl/internal/fixtures/get_dns_settings-request.xml create mode 100644 providers/dns/arvancloud/internal/fixtures/create_record-request.json create mode 100644 providers/dns/autodns/internal/fixtures/add_record-request.json rename providers/dns/autodns/internal/fixtures/{add-record.json => add_record.json} (100%) create mode 100644 providers/dns/autodns/internal/fixtures/remove_record-request.json rename providers/dns/autodns/internal/fixtures/{remove-record.json => remove_record.json} (100%) create mode 100644 providers/dns/checkdomain/internal/fixtures/create_record-request.json create mode 100644 providers/dns/checkdomain/internal/fixtures/delete_txt_record-request.json create mode 100644 providers/dns/clouddns/internal/fixtures/domain-request.json create mode 100644 providers/dns/clouddns/internal/fixtures/domain_search-request.json create mode 100644 providers/dns/clouddns/internal/fixtures/domain_search.json create mode 100644 providers/dns/clouddns/internal/fixtures/login-request.json create mode 100644 providers/dns/clouddns/internal/fixtures/login.json create mode 100644 providers/dns/clouddns/internal/fixtures/publish-request.json create mode 100644 providers/dns/clouddns/internal/fixtures/record_txt-request.json create mode 100644 providers/dns/conoha/internal/fixtures/empty.json create mode 100644 providers/dns/conohav3/internal/fixtures/empty.json create mode 100644 providers/dns/corenetworks/internal/identity_test.go create mode 100644 providers/dns/dnsmadeeasy/internal/fixtures/create_record-request.json create mode 100644 providers/dns/dnsmadeeasy/internal/fixtures/get_records.json create mode 100644 providers/dns/domeneshop/internal/fixtures/create_record-request.json create mode 100644 providers/dns/domeneshop/internal/fixtures/create_record.json create mode 100644 providers/dns/domeneshop/internal/fixtures/delete_record.json create mode 100644 providers/dns/domeneshop/internal/fixtures/getDnsRecords.json create mode 100644 providers/dns/domeneshop/internal/fixtures/getDomains.json create mode 100644 providers/dns/dynu/internal/fixtures/add_new_record-request.json create mode 100644 providers/dns/gandi/internal/client_test.go create mode 100644 providers/dns/gandi/internal/fixtures/add_txt_record-request.xml create mode 100644 providers/dns/gandi/internal/fixtures/clone_zone-request.xml create mode 100644 providers/dns/gandi/internal/fixtures/clone_zone.xml create mode 100644 providers/dns/gandi/internal/fixtures/delete_zone-request.xml create mode 100644 providers/dns/gandi/internal/fixtures/delete_zone.xml create mode 100644 providers/dns/gandi/internal/fixtures/empty.xml create mode 100644 providers/dns/gandi/internal/fixtures/get_zone_id-request.xml create mode 100644 providers/dns/gandi/internal/fixtures/get_zone_id.xml create mode 100644 providers/dns/gandi/internal/fixtures/new_zone_version-request.xml create mode 100644 providers/dns/gandi/internal/fixtures/new_zone_version.xml create mode 100644 providers/dns/gandi/internal/fixtures/set_zone-request.xml create mode 100644 providers/dns/gandi/internal/fixtures/set_zone.xml create mode 100644 providers/dns/gandi/internal/fixtures/set_zone_version-request.xml create mode 100644 providers/dns/gandi/internal/fixtures/set_zone_version.xml create mode 100644 providers/dns/gandiv5/internal/client_test.go create mode 100644 providers/dns/gandiv5/internal/fixtures/add_txt_record_get.json create mode 100644 providers/dns/gandiv5/internal/fixtures/api_response.json create mode 100644 providers/dns/godaddy/internal/fixtures/update_records-request.json create mode 100644 providers/dns/hetzner/internal/fixtures/create_txt_record-request.json create mode 100644 providers/dns/infomaniak/internal/fixtures/create_dns_record-request.json create mode 100644 providers/dns/infomaniak/internal/fixtures/get_domain_name.json create mode 100644 providers/dns/internal/hostingde/fixtures/zoneConfigsFind-request.json create mode 100644 providers/dns/internal/hostingde/fixtures/zoneUpdate-request.json create mode 100644 providers/dns/internal/selectel/fixtures/add_record-request.json create mode 100644 providers/dns/internal/selectel/fixtures/add_record.json create mode 100644 providers/dns/internetbs/internal/fixtures/auth_error.json delete mode 100644 providers/dns/lightsail/mock_server_test.go create mode 100644 providers/dns/mythicbeasts/internal/fixtures/token.json create mode 100644 providers/dns/netcup/internal/client_live_test.go create mode 100644 providers/dns/netcup/internal/fixtures/get_dns_records-request.json create mode 100644 providers/dns/netcup/internal/fixtures/get_dns_records.json create mode 100644 providers/dns/netcup/internal/fixtures/get_dns_records_error.json create mode 100644 providers/dns/netcup/internal/fixtures/get_dns_records_error_unmarshal.json create mode 100644 providers/dns/netcup/internal/fixtures/login-request.json create mode 100644 providers/dns/netcup/internal/fixtures/login.json create mode 100644 providers/dns/netcup/internal/fixtures/login_error.json create mode 100644 providers/dns/netcup/internal/fixtures/login_error_unmarshal.json create mode 100644 providers/dns/netcup/internal/fixtures/logout-request.json create mode 100644 providers/dns/netcup/internal/fixtures/logout.json create mode 100644 providers/dns/netcup/internal/fixtures/logout_error.json create mode 100644 providers/dns/njalla/internal/fixtures/add_record-request.json create mode 100644 providers/dns/njalla/internal/fixtures/add_record.json create mode 100644 providers/dns/njalla/internal/fixtures/auth_error.json create mode 100644 providers/dns/njalla/internal/fixtures/list_records-request.json create mode 100644 providers/dns/njalla/internal/fixtures/list_records.json create mode 100644 providers/dns/njalla/internal/fixtures/remove_record-request.json create mode 100644 providers/dns/njalla/internal/fixtures/remove_record_error_missing_domain.json create mode 100644 providers/dns/njalla/internal/fixtures/remove_record_error_missing_id.json create mode 100644 providers/dns/otc/internal/client_test.go create mode 100644 providers/dns/pdns/internal/fixtures/zone-request.json create mode 100644 providers/dns/rackspace/fixtures/delete.json create mode 100644 providers/dns/rackspace/fixtures/identity.json create mode 100644 providers/dns/rackspace/fixtures/record.json create mode 100644 providers/dns/rackspace/fixtures/record_details.json create mode 100644 providers/dns/rackspace/fixtures/zone_details.json delete mode 100644 providers/dns/rackspace/rackspace_mock_test.go create mode 100644 providers/dns/regru/internal/fixtures/add_txt_record.json create mode 100644 providers/dns/regru/internal/fixtures/add_txt_record_error_auth.json create mode 100644 providers/dns/regru/internal/fixtures/add_txt_record_error_domain.json create mode 100644 providers/dns/regru/internal/fixtures/remove_record.json create mode 100644 providers/dns/regru/internal/fixtures/remove_record_error_auth.json create mode 100644 providers/dns/regru/internal/fixtures/remove_record_error_domain.json create mode 100644 providers/dns/regru/internal/readme.md create mode 100644 providers/dns/route53/fixtures/changeResourceRecordSetsResponse.xml create mode 100644 providers/dns/route53/fixtures/getChangeResponse.xml create mode 100644 providers/dns/route53/fixtures/listHostedZonesByNameResponse.xml delete mode 100644 providers/dns/route53/fixtures_test.go delete mode 100644 providers/dns/route53/mock_test.go create mode 100644 providers/dns/safedns/internal/fixtures/add_record-request.json create mode 100644 providers/dns/safedns/internal/fixtures/add_record.json create mode 100644 providers/dns/safedns/internal/fixtures/error.json create mode 100644 providers/dns/stackpath/internal/fixtures/get_zone_records.json create mode 100644 providers/dns/stackpath/internal/fixtures/get_zones.json create mode 100644 providers/dns/vegadns/fixtures/create_record.json create mode 100644 providers/dns/vegadns/fixtures/record_delete.json create mode 100644 providers/dns/vegadns/fixtures/records.json create mode 100644 providers/dns/vegadns/fixtures/token.json delete mode 100644 providers/dns/vegadns/vegadns_mock_test.go create mode 100644 providers/dns/versio/fixtures/error_failToCreateTXT.json create mode 100644 providers/dns/versio/fixtures/error_failToFindZone.json create mode 100644 providers/dns/versio/fixtures/token.json create mode 100644 providers/dns/versio/internal/fixtures/update-domain-request.json delete mode 100644 providers/dns/versio/versio_mock_test.go delete mode 100644 providers/dns/vinyldns/mock_test.go create mode 100644 providers/dns/yandex/internal/fixtures/add_record.json create mode 100644 providers/dns/yandex/internal/fixtures/add_record_error.json create mode 100644 providers/dns/yandex/internal/fixtures/get_records.json create mode 100644 providers/dns/yandex/internal/fixtures/get_records_error.json create mode 100644 providers/dns/yandex/internal/fixtures/remove_record.json create mode 100644 providers/dns/yandex/internal/fixtures/remove_record_error.json diff --git a/go.mod b/go.mod index 52f4d2eb5..0a15d03c0 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/exoscale/egoscale/v3 v3.1.13 github.com/go-jose/go-jose/v4 v4.0.5 github.com/go-viper/mapstructure/v2 v2.2.1 + github.com/google/go-cmp v0.7.0 github.com/google/go-querystring v1.1.0 github.com/gophercloud/gophercloud v1.14.1 github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 diff --git a/platform/tester/servermock/builder.go b/platform/tester/servermock/builder.go new file mode 100644 index 000000000..e3b41e5c3 --- /dev/null +++ b/platform/tester/servermock/builder.go @@ -0,0 +1,72 @@ +package servermock + +import ( + "net/http" + "net/http/httptest" + "slices" + "testing" + + "github.com/stretchr/testify/require" +) + +// Link represents a middleware interface, enabling middleware chaining. +type Link interface { + Bind(next http.Handler) http.Handler +} + +// LinkFunc defines a function type [Link]. +type LinkFunc func(next http.Handler) http.Handler + +func (f LinkFunc) Bind(next http.Handler) http.Handler { + return f(next) +} + +// ClientBuilder defines a function type for creating a client of type T based on a httptest.Server instance. +type ClientBuilder[T any] func(server *httptest.Server) (T, error) + +// Builder is a type that facilitates the construction of testable HTTP clients and server. +// It allows defining routes, attaching middleware, and creating custom HTTP clients. +type Builder[T any] struct { + mux *http.ServeMux + chain []Link + + clientBuilder ClientBuilder[T] +} + +func NewBuilder[T any](clientBuilder ClientBuilder[T], chain ...Link) *Builder[T] { + return &Builder[T]{ + mux: http.NewServeMux(), + chain: chain, + clientBuilder: clientBuilder, + } +} + +func (b *Builder[T]) Route(pattern string, handler http.Handler, chain ...Link) *Builder[T] { + if handler == nil { + handler = Noop() + } + + for _, link := range slices.Backward(b.chain) { + handler = link.Bind(handler) + } + + for _, link := range slices.Backward(chain) { + handler = link.Bind(handler) + } + + b.mux.Handle(pattern, handler) + + return b +} + +func (b *Builder[T]) Build(t *testing.T) T { + t.Helper() + + server := httptest.NewServer(b.mux) + t.Cleanup(server.Close) + + client, err := b.clientBuilder(server) + require.NoError(t, err) + + return client +} diff --git a/platform/tester/servermock/handler_dump.go b/platform/tester/servermock/handler_dump.go new file mode 100644 index 000000000..83f902980 --- /dev/null +++ b/platform/tester/servermock/handler_dump.go @@ -0,0 +1,20 @@ +package servermock + +import ( + "fmt" + "net/http" + "net/http/httputil" +) + +// DumpRequest logs the full HTTP request to the console, including the body if present. +func DumpRequest() http.HandlerFunc { + return func(rw http.ResponseWriter, req *http.Request) { + dump, err := httputil.DumpRequest(req, true) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + fmt.Println(string(dump)) + } +} diff --git a/platform/tester/servermock/handler_file.go b/platform/tester/servermock/handler_file.go new file mode 100644 index 000000000..d826c648a --- /dev/null +++ b/platform/tester/servermock/handler_file.go @@ -0,0 +1,77 @@ +package servermock + +import ( + "io" + "net/http" + "os" + "path/filepath" + "slices" +) + +// ResponseFromFileHandler handles HTTP responses using the content of a file. +type ResponseFromFileHandler struct { + statusCode int + headers http.Header + filename string +} + +func ResponseFromFile(filename string) *ResponseFromFileHandler { + return &ResponseFromFileHandler{ + statusCode: http.StatusOK, + headers: http.Header{}, + filename: filename, + } +} + +func ResponseFromFixture(filename string) *ResponseFromFileHandler { + return ResponseFromFile(filepath.Join("fixtures", filename)) +} + +func (h *ResponseFromFileHandler) ServeHTTP(rw http.ResponseWriter, _ *http.Request) { + for k, values := range h.headers { + for _, v := range values { + rw.Header().Add(k, v) + } + } + + if h.filename == "" { + rw.WriteHeader(h.statusCode) + return + } + + if filepath.Ext(h.filename) == ".json" { + rw.Header().Set(contentTypeHeader, applicationJSONMimeType) + } + + file, err := os.Open(h.filename) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + defer func() { _ = file.Close() }() + + rw.WriteHeader(h.statusCode) + + _, err = io.Copy(rw, file) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } +} + +func (h *ResponseFromFileHandler) WithStatusCode(status int) *ResponseFromFileHandler { + if h.statusCode >= http.StatusContinue { + h.statusCode = status + } + + return h +} + +func (h *ResponseFromFileHandler) WithHeader(name, value string, values ...string) *ResponseFromFileHandler { + for _, v := range slices.Concat([]string{value}, values) { + h.headers.Add(name, v) + } + + return h +} diff --git a/platform/tester/servermock/handler_json.go b/platform/tester/servermock/handler_json.go new file mode 100644 index 000000000..f1c2aa9ce --- /dev/null +++ b/platform/tester/servermock/handler_json.go @@ -0,0 +1,39 @@ +package servermock + +import ( + "encoding/json" + "net/http" +) + +// JSONEncodeHandler is a handler that encodes data into JSON and writes it to an HTTP response. +type JSONEncodeHandler struct { + data any + statusCode int +} + +func JSONEncode(data any) *JSONEncodeHandler { + return &JSONEncodeHandler{ + data: data, + statusCode: http.StatusOK, + } +} + +func (h *JSONEncodeHandler) ServeHTTP(rw http.ResponseWriter, _ *http.Request) { + rw.Header().Set(contentTypeHeader, applicationJSONMimeType) + + rw.WriteHeader(h.statusCode) + + err := json.NewEncoder(rw).Encode(h.data) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } +} + +func (h *JSONEncodeHandler) WithStatusCode(status int) *JSONEncodeHandler { + if h.statusCode >= http.StatusContinue { + h.statusCode = status + } + + return h +} diff --git a/platform/tester/servermock/handler_noop.go b/platform/tester/servermock/handler_noop.go new file mode 100644 index 000000000..6df5164e6 --- /dev/null +++ b/platform/tester/servermock/handler_noop.go @@ -0,0 +1,45 @@ +package servermock + +import ( + "net/http" + "slices" +) + +// NoopHandler is a simple HTTP handler that responds without processing requests. +type NoopHandler struct { + statusCode int + headers http.Header +} + +func Noop() *NoopHandler { + return &NoopHandler{ + statusCode: http.StatusOK, + headers: http.Header{}, + } +} + +func (h *NoopHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + for k, values := range h.headers { + for _, v := range values { + rw.Header().Add(k, v) + } + } + + rw.WriteHeader(h.statusCode) +} + +func (h *NoopHandler) WithStatusCode(status int) *NoopHandler { + if h.statusCode >= http.StatusContinue { + h.statusCode = status + } + + return h +} + +func (h *NoopHandler) WithHeader(name, value string, values ...string) *NoopHandler { + for _, v := range slices.Concat([]string{value}, values) { + h.headers.Add(name, v) + } + + return h +} diff --git a/platform/tester/servermock/handler_raw.go b/platform/tester/servermock/handler_raw.go new file mode 100644 index 000000000..d7c68f396 --- /dev/null +++ b/platform/tester/servermock/handler_raw.go @@ -0,0 +1,61 @@ +package servermock + +import ( + "net/http" + "slices" +) + +// RawResponseHandler is a custom HTTP handler that serves raw response data. +type RawResponseHandler struct { + statusCode int + headers http.Header + data []byte +} + +func RawResponse(data []byte) *RawResponseHandler { + return &RawResponseHandler{ + statusCode: http.StatusOK, + headers: http.Header{}, + data: data, + } +} + +func RawStringResponse(data string) *RawResponseHandler { + return RawResponse([]byte(data)) +} + +func (h *RawResponseHandler) ServeHTTP(rw http.ResponseWriter, _ *http.Request) { + for k, values := range h.headers { + for _, v := range values { + rw.Header().Add(k, v) + } + } + + rw.WriteHeader(h.statusCode) + + if len(h.data) == 0 { + return + } + + _, err := rw.Write(h.data) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } +} + +func (h *RawResponseHandler) WithStatusCode(status int) *RawResponseHandler { + if h.statusCode >= http.StatusContinue { + h.statusCode = status + } + + return h +} + +func (h *RawResponseHandler) WithHeader(name, value string, values ...string) *RawResponseHandler { + for _, v := range slices.Concat([]string{value}, values) { + h.headers.Add(name, v) + } + + return h +} diff --git a/platform/tester/servermock/link_form.go b/platform/tester/servermock/link_form.go new file mode 100644 index 000000000..e7541cefa --- /dev/null +++ b/platform/tester/servermock/link_form.go @@ -0,0 +1,94 @@ +package servermock + +import ( + "fmt" + "net/http" + "net/url" + "regexp" + "slices" +) + +// FormLink is a type used for validating and processing form data in HTTP requests. +// It supports strict validation, predefined values, and regex-based checks to ensure form compliance. +type FormLink struct { + values url.Values + regexes map[string]*regexp.Regexp + strict bool + usePostForm bool + statusCode int +} + +func CheckForm() *FormLink { + return &FormLink{ + values: url.Values{}, + regexes: map[string]*regexp.Regexp{}, + statusCode: http.StatusBadRequest, + } +} + +func (l *FormLink) Bind(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + err := req.ParseForm() + if err != nil { + http.Error(rw, err.Error(), l.statusCode) + return + } + + form := req.Form + if l.usePostForm { + form = req.PostForm + } + + if l.strict { + if len(form) != len(l.values)+len(l.regexes) { + msg := fmt.Sprintf("invalid query parameters, got %v, want %v", req.Form, l.values) + http.Error(rw, msg, l.statusCode) + return + } + } + + for k, v := range l.values { + value := form[k] + if !slices.Equal(v, value) { + msg := fmt.Sprintf("invalid %q form value, got %q, want %q", k, value, v) + http.Error(rw, msg, l.statusCode) + return + } + } + + for k, exp := range l.regexes { + value := form.Get(k) + if !exp.MatchString(value) { + msg := fmt.Sprintf("invalid %q form value, %q doesn't match to %q", k, value, exp) + http.Error(rw, msg, l.statusCode) + return + } + } + + next.ServeHTTP(rw, req) + }) +} + +func (l *FormLink) Strict() *FormLink { + l.strict = true + + return l +} + +func (l *FormLink) UsePostForm() *FormLink { + l.usePostForm = true + + return l +} + +func (l *FormLink) With(name, value string) *FormLink { + l.values.Set(name, value) + + return l +} + +func (l *FormLink) WithRegexp(name, exp string) *FormLink { + l.regexes[name] = regexp.MustCompile(exp) + + return l +} diff --git a/platform/tester/servermock/link_headers.go b/platform/tester/servermock/link_headers.go new file mode 100644 index 000000000..821c737fe --- /dev/null +++ b/platform/tester/servermock/link_headers.go @@ -0,0 +1,177 @@ +package servermock + +import ( + "fmt" + "net/http" + "regexp" + "slices" +) + +const ( + authorizationHeader = "Authorization" + contentTypeHeader = "Content-Type" + acceptHeader = "Accept" +) + +const ( + applicationJSONMimeType = "application/json" + applicationFormMimeType = "application/x-www-form-urlencoded" +) + +type basicAuth struct { + username, password string +} + +// HeaderLink validates HTTP request headers. +type HeaderLink struct { + values http.Header + regexes map[string]*regexp.Regexp + json bool + basicAuth *basicAuth + statusCode int +} + +func CheckHeader() *HeaderLink { + return &HeaderLink{ + values: http.Header{}, + regexes: map[string]*regexp.Regexp{}, + statusCode: http.StatusBadRequest, + } +} + +func (l *HeaderLink) Bind(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + for k, v := range l.values { + err := checkHeader(req, k, v) + if err != nil { + http.Error(rw, err.Error(), l.statusCode) + return + } + } + + for k, exp := range l.regexes { + value := req.Header.Get(k) + + if !exp.MatchString(value) { + msg := fmt.Sprintf("invalid %q header value, %q doesn't match to %q", k, value, exp) + http.Error(rw, msg, l.statusCode) + return + } + } + + if l.json && !l.checkJSONHeaders(rw, req) { + return + } + + if l.basicAuth != nil && !l.checkBasicAuth(rw, req) { + return + } + + next.ServeHTTP(rw, req) + }) +} + +func (l *HeaderLink) With(name, value string, values ...string) *HeaderLink { + for _, v := range slices.Concat([]string{value}, values) { + l.values.Add(name, v) + } + + return l +} + +func (l *HeaderLink) WithRegexp(name, exp string) *HeaderLink { + l.regexes[name] = regexp.MustCompile(exp) + + return l +} + +func (l *HeaderLink) WithJSONHeaders() *HeaderLink { + l.json = true + + return l +} + +func (l *HeaderLink) WithContentTypeFromURLEncoded() *HeaderLink { + l.values.Set(contentTypeHeader, applicationFormMimeType) + + return l +} + +func (l *HeaderLink) WithContentType(value string) *HeaderLink { + l.values.Set(contentTypeHeader, value) + + return l +} + +func (l *HeaderLink) WithAccept(value string) *HeaderLink { + l.values.Set(acceptHeader, value) + + return l +} + +func (l *HeaderLink) WithAuthorization(value string) *HeaderLink { + l.values.Set(authorizationHeader, value) + + return l +} + +func (l *HeaderLink) WithStatusCode(status int) *HeaderLink { + if l.statusCode >= http.StatusContinue { + l.statusCode = status + } + + return l +} + +func (l *HeaderLink) WithBasicAuth(username, password string) *HeaderLink { + l.basicAuth = &basicAuth{username: username, password: password} + + return l +} + +func (l *HeaderLink) checkBasicAuth(rw http.ResponseWriter, req *http.Request) bool { + usr, pwd, ok := req.BasicAuth() + if !ok { + http.Error(rw, "missing Basic auth", l.statusCode) + + return false + } + + if usr != l.basicAuth.username || pwd != l.basicAuth.password { + msg := fmt.Sprintf("invalid credentials: got [username: %q, password: %q], want [username: %q, password: %q]", + usr, pwd, l.basicAuth.username, l.basicAuth.password) + http.Error(rw, msg, l.statusCode) + + return false + } + + return true +} + +func (l *HeaderLink) checkJSONHeaders(rw http.ResponseWriter, req *http.Request) bool { + err := checkHeader(req, acceptHeader, []string{applicationJSONMimeType}) + if err != nil { + http.Error(rw, err.Error(), l.statusCode) + + return false + } + + if req.ContentLength > 0 { + err = checkHeader(req, contentTypeHeader, []string{applicationJSONMimeType}) + if err != nil { + http.Error(rw, err.Error(), l.statusCode) + + return false + } + } + + return true +} + +func checkHeader(req *http.Request, k string, v []string) error { + if !slices.Equal(req.Header[k], v) { + return fmt.Errorf("invalid %q header value, got %q, want %q", k, req.Header[k], v) + } + + return nil +} diff --git a/platform/tester/servermock/link_query.go b/platform/tester/servermock/link_query.go new file mode 100644 index 000000000..00d7450ae --- /dev/null +++ b/platform/tester/servermock/link_query.go @@ -0,0 +1,97 @@ +package servermock + +import ( + "fmt" + "net/http" + "net/url" + "regexp" +) + +// QueryParameterLink validates query parameters in HTTP requests. +// The strict flag enforces exact matches with specified query parameters. +type QueryParameterLink struct { + values map[string]string + regexes map[string]*regexp.Regexp + strict bool + statusCode int +} + +func CheckQueryParameter() *QueryParameterLink { + return &QueryParameterLink{ + values: map[string]string{}, + regexes: map[string]*regexp.Regexp{}, + statusCode: http.StatusBadRequest, + } +} + +func (l *QueryParameterLink) Bind(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + query := req.URL.Query() + + if l.strict { + if len(query) != len(l.values)+len(l.regexes) { + msg := fmt.Sprintf("invalid query parameters, got %v, want %v", query, l.values) + http.Error(rw, msg, l.statusCode) + return + } + } + + for k, v := range l.values { + p := query.Get(k) + if p != v { + msg := fmt.Sprintf("invalid %q query parameter value, got %q, want %q", k, p, v) + http.Error(rw, msg, l.statusCode) + return + } + } + + for k, exp := range l.regexes { + value := query.Get(k) + if !exp.MatchString(value) { + msg := fmt.Sprintf("invalid %q query parameter value, %q doesn't match to %q", k, value, exp) + http.Error(rw, msg, l.statusCode) + return + } + } + + next.ServeHTTP(rw, req) + }) +} + +func (l *QueryParameterLink) Strict() *QueryParameterLink { + l.strict = true + + return l +} + +func (l *QueryParameterLink) With(name, value string) *QueryParameterLink { + l.values[name] = value + + return l +} + +func (l *QueryParameterLink) WithRegexp(name, exp string) *QueryParameterLink { + l.regexes[name] = regexp.MustCompile(exp) + + return l +} + +func (l *QueryParameterLink) WithValues(values url.Values) *QueryParameterLink { + for k, v := range values { + if len(v) != 1 { + continue + } + + l.values[k] = v[0] + } + + return l +} + +func (l *QueryParameterLink) WithStatusCode(status int) *QueryParameterLink { + if l.statusCode >= http.StatusContinue { + l.statusCode = status + } + + return l +} diff --git a/platform/tester/servermock/link_request_body.go b/platform/tester/servermock/link_request_body.go new file mode 100644 index 000000000..67ab4ae3f --- /dev/null +++ b/platform/tester/servermock/link_request_body.go @@ -0,0 +1,89 @@ +package servermock + +import ( + "bytes" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "slices" +) + +// RequestBodyLink represents a handler utility to validate HTTP request bodies against a predefined byte slice. +type RequestBodyLink struct { + body []byte + filename string + ignoreWhitespace bool +} + +// CheckRequestBody creates a [RequestBodyLink] initialized with the provided request body string. +func CheckRequestBody(body string) *RequestBodyLink { + return &RequestBodyLink{body: []byte(body)} +} + +// CheckRequestBodyFromFile creates a [RequestBodyLink] initialized with the provided request body file. +func CheckRequestBodyFromFile(filename string) *RequestBodyLink { + return &RequestBodyLink{filename: filename} +} + +func (l *RequestBodyLink) Bind(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if req.ContentLength == 0 { + http.Error(rw, fmt.Sprintf("%s: empty request body", req.URL.Path), http.StatusBadRequest) + return + } + + body, err := io.ReadAll(req.Body) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + _ = req.Body.Close() + + expectedRaw := slices.Clone(l.body) + + if l.filename != "" { + expectedRaw, err = os.ReadFile(filepath.Join("fixtures", l.filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + } + + if len(expectedRaw) == 0 { + http.Error(rw, fmt.Sprintf("%s: empty expected request body", req.URL.Path), http.StatusBadRequest) + return + } + + if l.ignoreWhitespace { + body = trimLineSpace(body) + expectedRaw = trimLineSpace(expectedRaw) + } + + if !bytes.Equal(bytes.TrimSpace(expectedRaw), bytes.TrimSpace(body)) { + msg := fmt.Sprintf("%s: request body differences: got: %s, want: %s", req.URL.Path, + string(bytes.TrimSpace(body)), string(bytes.TrimSpace(expectedRaw))) + http.Error(rw, msg, http.StatusBadRequest) + return + } + + next.ServeHTTP(rw, req) + }) +} + +func (l *RequestBodyLink) IgnoreWhitespace() *RequestBodyLink { + l.ignoreWhitespace = true + + return l +} + +func trimLineSpace(body []byte) []byte { + buf := bytes.NewBuffer(nil) + for line := range bytes.Lines(body) { + buf.Write(bytes.TrimSpace(line)) + } + + return buf.Bytes() +} diff --git a/platform/tester/servermock/link_request_body_json.go b/platform/tester/servermock/link_request_body_json.go new file mode 100644 index 000000000..1d1fecce9 --- /dev/null +++ b/platform/tester/servermock/link_request_body_json.go @@ -0,0 +1,99 @@ +package servermock + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "slices" + + "github.com/google/go-cmp/cmp" +) + +// RequestBodyJSONLink validates JSON request bodies. +type RequestBodyJSONLink struct { + body []byte + filename string + data any +} + +// CheckRequestJSONBody creates a [RequestBodyJSONLink] initialized with a string. +func CheckRequestJSONBody(body string) *RequestBodyJSONLink { + return &RequestBodyJSONLink{body: []byte(body)} +} + +// CheckRequestJSONBodyFromStruct creates a [RequestBodyJSONLink] initialized with a struct. +func CheckRequestJSONBodyFromStruct(data any) *RequestBodyJSONLink { + return &RequestBodyJSONLink{data: data} +} + +// CheckRequestJSONBodyFromFile creates a [RequestBodyJSONLink] initialized with the provided request body file. +func CheckRequestJSONBodyFromFile(filename string) *RequestBodyJSONLink { + return &RequestBodyJSONLink{filename: filename} +} + +func (l *RequestBodyJSONLink) Bind(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if req.ContentLength == 0 { + http.Error(rw, fmt.Sprintf("%s: empty request body", req.URL.Path), http.StatusBadRequest) + return + } + + body, err := io.ReadAll(req.Body) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + _ = req.Body.Close() + + var expected, actual any + + expectedRaw := slices.Clone(l.body) + + switch { + case l.filename != "": + expectedRaw, err = os.ReadFile(filepath.Join("fixtures", l.filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + case l.data != nil: + expectedRaw, err = json.Marshal(l.data) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + } + + if len(expectedRaw) == 0 { + http.Error(rw, fmt.Sprintf("%s: empty expected request body", req.URL.Path), http.StatusBadRequest) + return + } + + err = json.Unmarshal(expectedRaw, &expected) + if err != nil { + msg := fmt.Sprintf("%s: the expected request body is not valid JSON: %v", req.URL.Path, err) + http.Error(rw, msg, http.StatusBadRequest) + return + } + + err = json.Unmarshal(body, &actual) + if err != nil { + msg := fmt.Sprintf("%s: request body is not valid JSON: %v", req.URL.Path, err) + http.Error(rw, msg, http.StatusBadRequest) + return + } + + if !cmp.Equal(actual, expected) { + msg := fmt.Sprintf("%s: request body differences: %s", req.URL.Path, cmp.Diff(actual, expected)) + http.Error(rw, msg, http.StatusBadRequest) + return + } + + next.ServeHTTP(rw, req) + }) +} diff --git a/providers/dns/acmedns/acmedns_test.go b/providers/dns/acmedns/acmedns_test.go index 3bc847b6d..e50c89d56 100644 --- a/providers/dns/acmedns/acmedns_test.go +++ b/providers/dns/acmedns/acmedns_test.go @@ -5,6 +5,7 @@ import ( "net/http/httptest" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/nrdcg/goacmedns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -166,11 +167,17 @@ func TestPresent_httpStorage(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) + config := servermock.NewBuilder(func(server *httptest.Server) (*Config, error) { + cfg := NewDefaultConfig() + cfg.StorageBaseURL = server.URL - config := NewDefaultConfig() - config.StorageBaseURL = server.URL + return cfg, nil + }). + // Fetch + Route("GET /example.com", servermock.Noop().WithStatusCode(http.StatusNotFound)). + // Put + Route("POST /example.com", servermock.Noop().WithStatusCode(test.StatusCode)). + Build(t) p, err := NewDNSProviderConfig(config) require.NoError(t, err) @@ -178,16 +185,6 @@ func TestPresent_httpStorage(t *testing.T) { client := newMockClient().WithRegisterAccount(egTestAccount) p.client = client - // Fetch - mux.HandleFunc("GET /example.com", func(rw http.ResponseWriter, reg *http.Request) { - rw.WriteHeader(http.StatusNotFound) - }) - - // Put - mux.HandleFunc("POST /example.com", func(rw http.ResponseWriter, req *http.Request) { - rw.WriteHeader(test.StatusCode) - }) - err = p.Present(egDomain, "foo", egKeyAuth) if test.ExpectedError != nil { assert.Equal(t, test.ExpectedError, err) @@ -225,22 +222,21 @@ func TestRegister_httpStorage(t *testing.T) { for _, test := range testCases { t.Run(test.Name, func(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) + config := servermock.NewBuilder(func(server *httptest.Server) (*Config, error) { + cfg := NewDefaultConfig() + cfg.StorageBaseURL = server.URL - config := NewDefaultConfig() - config.StorageBaseURL = server.URL + return cfg, nil + }). + // Put + Route("POST /example.com", servermock.Noop().WithStatusCode(test.StatusCode)). + Build(t) p, err := NewDNSProviderConfig(config) require.NoError(t, err) p.client = newMockClient().WithRegisterAccount(egTestAccount) - // Put - mux.HandleFunc("POST /example.com", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(test.StatusCode) - }) - acc, err := p.register(t.Context(), egDomain, egFQDN) if test.ExpectedError != nil { assert.Equal(t, test.ExpectedError, err) diff --git a/providers/dns/acmedns/internal/fixtures/request-body.json b/providers/dns/acmedns/internal/fixtures/request-body.json new file mode 100644 index 000000000..d29cebc5b --- /dev/null +++ b/providers/dns/acmedns/internal/fixtures/request-body.json @@ -0,0 +1,7 @@ +{ + "fulldomain": "foo.example.com", + "subdomain": "foo", + "username": "user", + "password": "secret", + "server_url": "https://example.com" +} diff --git a/providers/dns/acmedns/internal/http_storage_test.go b/providers/dns/acmedns/internal/http_storage_test.go index 14a5fd97c..0be6dd949 100644 --- a/providers/dns/acmedns/internal/http_storage_test.go +++ b/providers/dns/acmedns/internal/http_storage_test.go @@ -1,57 +1,35 @@ package internal import ( - "io" "net/http" "net/http/httptest" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/nrdcg/goacmedns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern, filename string, statusCode int) *HTTPStorage { - t.Helper() +func mockBuilder() *servermock.Builder[*HTTPStorage] { + return servermock.NewBuilder[*HTTPStorage]( + func(server *httptest.Server) (*HTTPStorage, error) { + storage, err := NewHTTPStorage(server.URL) + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + storage.client = server.Client() - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if filename == "" { - rw.WriteHeader(statusCode) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(statusCode) - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - storage, err := NewHTTPStorage(server.URL) - require.NoError(t, err) - - storage.client = server.Client() - - return storage + return storage, nil + }, + servermock.CheckHeader().WithJSONHeaders()) } func TestHTTPStorage_Fetch(t *testing.T) { - storage := setupTest(t, "GET /example.com", "fetch.json", http.StatusOK) + storage := mockBuilder(). + Route("GET /example.com", servermock.ResponseFromFixture("fetch.json")). + Build(t) account, err := storage.Fetch(t.Context(), "example.com") require.NoError(t, err) @@ -68,14 +46,20 @@ func TestHTTPStorage_Fetch(t *testing.T) { } func TestHTTPStorage_Fetch_error(t *testing.T) { - storage := setupTest(t, "GET /example.com", "error.json", http.StatusInternalServerError) + storage := mockBuilder(). + Route("GET /example.com", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusInternalServerError)). + Build(t) _, err := storage.Fetch(t.Context(), "example.com") require.Error(t, err) } func TestHTTPStorage_FetchAll(t *testing.T) { - storage := setupTest(t, "GET /", "fetch-all.json", http.StatusOK) + storage := mockBuilder(). + Route("GET /", servermock.ResponseFromFixture("fetch-all.json")). + Build(t) account, err := storage.FetchAll(t.Context()) require.NoError(t, err) @@ -101,14 +85,21 @@ func TestHTTPStorage_FetchAll(t *testing.T) { } func TestHTTPStorage_FetchAll_error(t *testing.T) { - storage := setupTest(t, "GET /", "error.json", http.StatusInternalServerError) + storage := mockBuilder(). + Route("GET /", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusInternalServerError)). + Build(t) _, err := storage.FetchAll(t.Context()) require.Error(t, err) } func TestHTTPStorage_Put(t *testing.T) { - storage := setupTest(t, "POST /example.com", "", http.StatusOK) + storage := mockBuilder(). + Route("POST /example.com", nil, + servermock.CheckRequestJSONBodyFromFile("request-body.json")). + Build(t) account := goacmedns.Account{ FullDomain: "foo.example.com", @@ -123,7 +114,11 @@ func TestHTTPStorage_Put(t *testing.T) { } func TestHTTPStorage_Put_error(t *testing.T) { - storage := setupTest(t, "POST /example.com", "error.json", http.StatusInternalServerError) + storage := mockBuilder(). + Route("POST /example.com", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusInternalServerError)). + Build(t) account := goacmedns.Account{ FullDomain: "foo.example.com", @@ -138,7 +133,12 @@ func TestHTTPStorage_Put_error(t *testing.T) { } func TestHTTPStorage_Put_CNAME_created(t *testing.T) { - storage := setupTest(t, "POST /example.com", "", http.StatusCreated) + storage := mockBuilder(). + Route("POST /example.com", + servermock.Noop(). + WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBodyFromFile("request-body.json")). + Build(t) account := goacmedns.Account{ FullDomain: "foo.example.com", diff --git a/providers/dns/allinkl/internal/client_test.go b/providers/dns/allinkl/internal/client_test.go index 6ccb1ebf6..5954e2463 100644 --- a/providers/dns/allinkl/internal/client_test.go +++ b/providers/dns/allinkl/internal/client_test.go @@ -1,27 +1,28 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestClient_GetDNSSettings(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/", testHandler("get_dns_settings.xml")) - +func setupClient(server *httptest.Server) (*Client, error) { client := NewClient("user") client.baseURL = server.URL + client.HTTPClient = server.Client() + + return client, nil +} + +func TestClient_GetDNSSettings(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /", servermock.ResponseFromFixture("get_dns_settings.xml"), + servermock.CheckRequestBodyFromFile("get_dns_settings-request.xml"). + IgnoreWhitespace()). + Build(t) records, err := client.GetDNSSettings(mockContext(t), "example.com", "") require.NoError(t, err) @@ -96,14 +97,11 @@ func TestClient_GetDNSSettings(t *testing.T) { } func TestClient_AddDNSSettings(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/", testHandler("add_dns_settings.xml")) - - client := NewClient("user") - client.baseURL = server.URL + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /", servermock.ResponseFromFixture("add_dns_settings.xml"), + servermock.CheckRequestBodyFromFile("add_dns_settings-request.xml"). + IgnoreWhitespace()). + Build(t) record := DNSRequest{ ZoneHost: "42cnc.de.", @@ -119,40 +117,14 @@ func TestClient_AddDNSSettings(t *testing.T) { } func TestClient_DeleteDNSSettings(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/", testHandler("delete_dns_settings.xml")) - - client := NewClient("user") - client.baseURL = server.URL + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /", servermock.ResponseFromFixture("delete_dns_settings.xml"), + servermock.CheckRequestBodyFromFile("delete_dns_settings-request.xml"). + IgnoreWhitespace()). + Build(t) r, err := client.DeleteDNSSettings(mockContext(t), "57347450") require.NoError(t, err) assert.Equal(t, "TRUE", r) } - -func testHandler(filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } -} diff --git a/providers/dns/allinkl/internal/fixtures/add_dns_settings-request.xml b/providers/dns/allinkl/internal/fixtures/add_dns_settings-request.xml new file mode 100644 index 000000000..e8cd12633 --- /dev/null +++ b/providers/dns/allinkl/internal/fixtures/add_dns_settings-request.xml @@ -0,0 +1,7 @@ + + + + {"kas_login":"user","kas_auth_type":"session","kas_auth_data":"593959ca04f0de9689b586c6a647d15d","kas_action":"add_dns_settings","KasRequestParams":{"zone_host":"42cnc.de.","record_type":"TXT","record_name":"lego","record_data":"abcdefgh","record_aux":0}} + + + diff --git a/providers/dns/allinkl/internal/fixtures/delete_dns_settings-request.xml b/providers/dns/allinkl/internal/fixtures/delete_dns_settings-request.xml new file mode 100644 index 000000000..a306a98a7 --- /dev/null +++ b/providers/dns/allinkl/internal/fixtures/delete_dns_settings-request.xml @@ -0,0 +1,7 @@ + + + + {"kas_login":"user","kas_auth_type":"session","kas_auth_data":"593959ca04f0de9689b586c6a647d15d","kas_action":"delete_dns_settings","KasRequestParams":{"record_id":"57347450"}} + + + diff --git a/providers/dns/allinkl/internal/fixtures/get_dns_settings-request.xml b/providers/dns/allinkl/internal/fixtures/get_dns_settings-request.xml new file mode 100644 index 000000000..b44941d2b --- /dev/null +++ b/providers/dns/allinkl/internal/fixtures/get_dns_settings-request.xml @@ -0,0 +1,7 @@ + + + + {"kas_login":"user","kas_auth_type":"session","kas_auth_data":"593959ca04f0de9689b586c6a647d15d","kas_action":"get_dns_settings","KasRequestParams":{"zone_host":"example.com"}} + + + diff --git a/providers/dns/allinkl/internal/identity_test.go b/providers/dns/allinkl/internal/identity_test.go index 2ef0a4ca4..dc55506f2 100644 --- a/providers/dns/allinkl/internal/identity_test.go +++ b/providers/dns/allinkl/internal/identity_test.go @@ -2,14 +2,21 @@ package internal import ( "context" - "net/http" "net/http/httptest" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func setupIdentifierClient(server *httptest.Server) (*Identifier, error) { + client := NewIdentifier("user", "secret") + client.authEndpoint = server.URL + + return client, nil +} + func mockContext(t *testing.T) context.Context { t.Helper() @@ -17,14 +24,9 @@ func mockContext(t *testing.T) context.Context { } func TestIdentifier_Authentication(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/", testHandler("auth.xml")) - - client := NewIdentifier("user", "secret") - client.authEndpoint = server.URL + client := servermock.NewBuilder[*Identifier](setupIdentifierClient). + Route("POST /", servermock.ResponseFromFixture("auth.xml")). + Build(t) credentialToken, err := client.Authentication(t.Context(), 60, false) require.NoError(t, err) @@ -33,14 +35,9 @@ func TestIdentifier_Authentication(t *testing.T) { } func TestIdentifier_Authentication_error(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/", testHandler("auth_fault.xml")) - - client := NewIdentifier("user", "secret") - client.authEndpoint = server.URL + client := servermock.NewBuilder[*Identifier](setupIdentifierClient). + Route("POST /", servermock.ResponseFromFixture("auth_fault.xml")). + Build(t) _, err := client.Authentication(t.Context(), 60, false) require.Error(t, err) diff --git a/providers/dns/arvancloud/internal/client_test.go b/providers/dns/arvancloud/internal/client_test.go index 2930dcb3d..38cb740c1 100644 --- a/providers/dns/arvancloud/internal/client_test.go +++ b/providers/dns/arvancloud/internal/client_test.go @@ -1,64 +1,39 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, apiKey string) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder(apiKey string) *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(apiKey) + client.baseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient(apiKey) - client.baseURL, _ = url.Parse(server.URL) - client.HTTPClient = server.Client() - - return client, mux + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization(apiKey)) } func TestClient_GetTxtRecord(t *testing.T) { const apiKey = "myKeyA" - client, mux := setupTest(t, apiKey) - const domain = "example.com" - mux.HandleFunc("/cdn/4.0/domains/"+domain+"/dns-records", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get(authorizationHeader) - if auth != apiKey { - http.Error(rw, fmt.Sprintf("invalid API key: %s", auth), http.StatusUnauthorized) - return - } - - file, err := os.Open("./fixtures/get_txt_record.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(apiKey). + Route("GET /cdn/4.0/domains/"+domain+"/dns-records", + servermock.ResponseFromFixture("get_txt_record.json"), + servermock.CheckQueryParameter().With("search", "acme-challenge")). + Build(t) _, err := client.GetTxtRecord(t.Context(), domain, "_acme-challenge", "txtxtxt") require.NoError(t, err) @@ -67,36 +42,14 @@ func TestClient_GetTxtRecord(t *testing.T) { func TestClient_CreateRecord(t *testing.T) { const apiKey = "myKeyB" - client, mux := setupTest(t, apiKey) - const domain = "example.com" - mux.HandleFunc("/cdn/4.0/domains/"+domain+"/dns-records", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get(authorizationHeader) - if auth != apiKey { - http.Error(rw, fmt.Sprintf("invalid API key: %s", auth), http.StatusUnauthorized) - return - } - - file, err := os.Open("./fixtures/create_txt_record.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - rw.WriteHeader(http.StatusCreated) - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(apiKey). + Route("POST /cdn/4.0/domains/"+domain+"/dns-records", + servermock.ResponseFromFixture("create_txt_record.json"). + WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBodyFromFile("create_record-request.json")). + Build(t) record := DNSRecord{ Name: "_acme-challenge", @@ -128,23 +81,12 @@ func TestClient_CreateRecord(t *testing.T) { func TestClient_DeleteRecord(t *testing.T) { const apiKey = "myKeyC" - client, mux := setupTest(t, apiKey) - const domain = "example.com" const recordID = "recordId" - mux.HandleFunc("/cdn/4.0/domains/"+domain+"/dns-records/"+recordID, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get(authorizationHeader) - if auth != apiKey { - http.Error(rw, fmt.Sprintf("invalid API key: %s", auth), http.StatusUnauthorized) - return - } - }) + client := mockBuilder(apiKey). + Route("DELETE /cdn/4.0/domains/"+domain+"/dns-records/"+recordID, nil). + Build(t) err := client.DeleteRecord(t.Context(), domain, recordID) require.NoError(t, err) diff --git a/providers/dns/arvancloud/internal/fixtures/create_record-request.json b/providers/dns/arvancloud/internal/fixtures/create_record-request.json new file mode 100644 index 000000000..48a7124f6 --- /dev/null +++ b/providers/dns/arvancloud/internal/fixtures/create_record-request.json @@ -0,0 +1,8 @@ +{ + "type": "txt", + "value": { + "text": "txtxtxt" + }, + "name": "_acme-challenge", + "ttl": 600 +} diff --git a/providers/dns/auroradns/auroradns_test.go b/providers/dns/auroradns/auroradns_test.go index cbd51b830..1619ee586 100644 --- a/providers/dns/auroradns/auroradns_test.go +++ b/providers/dns/auroradns/auroradns_test.go @@ -1,35 +1,32 @@ package auroradns import ( - "fmt" - "io" "net/http" "net/http/httptest" "testing" "github.com/go-acme/lego/v4/platform/tester" - "github.com/stretchr/testify/assert" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/nrdcg/auroradns" "github.com/stretchr/testify/require" ) var envTest = tester.NewEnvTest(EnvAPIKey, EnvSecret) -func setupTest(t *testing.T) (*DNSProvider, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.APIKey = "asdf1234" + config.Secret = "key" + config.BaseURL = server.URL - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - config := NewDefaultConfig() - config.APIKey = "asdf1234" - config.Secret = "key" - config.BaseURL = server.URL - - provider, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - return provider, mux + return NewDNSProviderConfig(config) + }, + servermock.CheckHeader(). + WithContentType("application/json"). + WithRegexp("Authorization", `AuroraDNSv1 .+`). + WithRegexp("X-Auroradns-Date", `[0-9TZ]+`)) } func TestNewDNSProvider(t *testing.T) { @@ -145,72 +142,47 @@ func TestNewDNSProviderConfig(t *testing.T) { } func TestDNSProvider_Present(t *testing.T) { - provider, mux := setupTest(t) - - mux.HandleFunc("/zones", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodGet, r.Method, "method") - - w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, `[{ - "id": "c56a4180-65aa-42ec-a945-5fd21dec0538", - "name": "example.com" - }]`) - }) - - mux.HandleFunc("/zones/c56a4180-65aa-42ec-a945-5fd21dec0538/records", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodPost, r.Method) - assert.Equal(t, "application/json", r.Header.Get("Content-Type"), "Content-Type") - - reqBody, err := io.ReadAll(r.Body) - require.NoError(t, err) - assert.JSONEq(t, `{"type":"TXT","name":"_acme-challenge","content":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI","ttl":300}`, string(reqBody)) - - w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, `{ - "id": "c56a4180-65aa-42ec-a945-5fd21dec0538", - "type": "TXT", - "name": "_acme-challenge", - "ttl": 300 - }`) - }) + provider := mockBuilder(). + Route("GET /zones", + servermock.JSONEncode([]auroradns.Zone{{ + ID: "c56a4180-65aa-42ec-a945-5fd21dec0538", + Name: "example.com", + }}). + WithStatusCode(http.StatusCreated)). + Route("POST /zones/c56a4180-65aa-42ec-a945-5fd21dec0538/records", + servermock.JSONEncode(auroradns.Record{ + ID: "ec56a4180-65aa-42ec-a945-5fd21dec0538", + RecordType: "TXT", + Name: "_acme-challenge", + TTL: 300, + }). + WithStatusCode(http.StatusCreated)). + Build(t) err := provider.Present("example.com", "", "foobar") require.NoError(t, err, "fail to create TXT record") } func TestDNSProvider_CleanUp(t *testing.T) { - provider, mux := setupTest(t) - - mux.HandleFunc("/zones", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodGet, r.Method) - - w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, `[{ - "id": "c56a4180-65aa-42ec-a945-5fd21dec0538", - "name": "example.com" - }]`) - }) - - mux.HandleFunc("/zones/c56a4180-65aa-42ec-a945-5fd21dec0538/records", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodPost, r.Method) - - w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, `{ - "id": "ec56a4180-65aa-42ec-a945-5fd21dec0538", - "type": "TXT", - "name": "_acme-challenge", - "ttl": 300 - }`) - }) - - mux.HandleFunc("/zones/c56a4180-65aa-42ec-a945-5fd21dec0538/records/ec56a4180-65aa-42ec-a945-5fd21dec0538", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodDelete, r.Method) - - assert.Equal(t, "application/json", r.Header.Get("Content-Type"), "Content-Type") - - w.WriteHeader(http.StatusCreated) - fmt.Fprintf(w, `{}`) - }) + provider := mockBuilder(). + Route("GET /zones", + servermock.JSONEncode([]auroradns.Zone{{ + ID: "c56a4180-65aa-42ec-a945-5fd21dec0538", + Name: "example.com", + }}). + WithStatusCode(http.StatusCreated)). + Route("POST /zones/c56a4180-65aa-42ec-a945-5fd21dec0538/records", + servermock.JSONEncode(auroradns.Record{ + ID: "ec56a4180-65aa-42ec-a945-5fd21dec0538", + RecordType: "TXT", + Name: "_acme-challenge", + TTL: 300, + }). + WithStatusCode(http.StatusCreated)). + Route("DELETE /zones/c56a4180-65aa-42ec-a945-5fd21dec0538/records/ec56a4180-65aa-42ec-a945-5fd21dec0538", + servermock.RawStringResponse("{}"). + WithStatusCode(http.StatusCreated)). + Build(t) err := provider.Present("example.com", "", "foobar") require.NoError(t, err, "fail to create TXT record") diff --git a/providers/dns/autodns/internal/client_test.go b/providers/dns/autodns/internal/client_test.go index d656e0ae9..5eb6486ea 100644 --- a/providers/dns/autodns/internal/client_test.go +++ b/providers/dns/autodns/internal/client_test.go @@ -1,68 +1,37 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, method, pattern string, status int, file string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret", 123) + client.HTTPClient = server.Client() + client.BaseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusBadRequest) - return - } - - apiUser, apiKey, ok := req.BasicAuth() - if apiUser != "user" || apiKey != "secret" || !ok { - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - if file == "" { - rw.WriteHeader(status) - return - } - - open, err := os.Open(filepath.Join("fixtures", file)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client := NewClient("user", "secret", 123) - client.HTTPClient = server.Client() - client.BaseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader(). + WithBasicAuth("user", "secret"). + WithJSONHeaders()) } func TestClient_AddTxtRecords(t *testing.T) { - client := setupTest(t, http.MethodPost, "/zone/example.com/_stream", http.StatusOK, "add-record.json") + client := mockBuilder(). + Route("POST /zone/example.com/_stream", + servermock.ResponseFromFixture("add_record.json"), + servermock.CheckRequestJSONBodyFromFile("add_record-request.json"), + servermock.CheckHeader(). + With("X-Domainrobot-Context", "123")). + Build(t) records := []*ResourceRecord{{}} @@ -86,7 +55,13 @@ func TestClient_AddTxtRecords(t *testing.T) { } func TestClient_RemoveTXTRecords(t *testing.T) { - client := setupTest(t, http.MethodPost, "/zone/example.com/_stream", http.StatusOK, "add-record.json") + client := mockBuilder(). + Route("POST /zone/example.com/_stream", + servermock.ResponseFromFixture("remove_record.json"), + servermock.CheckRequestJSONBodyFromFile("remove_record-request.json"), + servermock.CheckHeader(). + With("X-Domainrobot-Context", "123")). + Build(t) records := []*ResourceRecord{{}} diff --git a/providers/dns/autodns/internal/fixtures/add_record-request.json b/providers/dns/autodns/internal/fixtures/add_record-request.json new file mode 100644 index 000000000..b798b4fbd --- /dev/null +++ b/providers/dns/autodns/internal/fixtures/add_record-request.json @@ -0,0 +1,11 @@ +{ + "adds": [ + { + "name": "", + "ttl": 0, + "type": "", + "value": "" + } + ], + "rems": null +} diff --git a/providers/dns/autodns/internal/fixtures/add-record.json b/providers/dns/autodns/internal/fixtures/add_record.json similarity index 100% rename from providers/dns/autodns/internal/fixtures/add-record.json rename to providers/dns/autodns/internal/fixtures/add_record.json diff --git a/providers/dns/autodns/internal/fixtures/remove_record-request.json b/providers/dns/autodns/internal/fixtures/remove_record-request.json new file mode 100644 index 000000000..0702c7367 --- /dev/null +++ b/providers/dns/autodns/internal/fixtures/remove_record-request.json @@ -0,0 +1,11 @@ +{ + "adds": null, + "rems": [ + { + "name": "", + "ttl": 0, + "type": "", + "value": "" + } + ] +} diff --git a/providers/dns/autodns/internal/fixtures/remove-record.json b/providers/dns/autodns/internal/fixtures/remove_record.json similarity index 100% rename from providers/dns/autodns/internal/fixtures/remove-record.json rename to providers/dns/autodns/internal/fixtures/remove_record.json diff --git a/providers/dns/axelname/internal/client_test.go b/providers/dns/axelname/internal/client_test.go index 0ead4b180..7796f6047 100644 --- a/providers/dns/axelname/internal/client_test.go +++ b/providers/dns/axelname/internal/client_test.go @@ -1,58 +1,37 @@ package internal import ( - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, status int, filename string) *Client { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if filename == "" { - rw.WriteHeader(status) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - +func setupClient(server *httptest.Server) (*Client, error) { client, err := NewClient("user", "secret") - require.NoError(t, err) + if err != nil { + return nil, err + } client.HTTPClient = server.Client() client.baseURL, _ = url.Parse(server.URL) - return client + return client, nil } func TestClient_ListRecords(t *testing.T) { - client := setupTest(t, "GET /dns_list", http.StatusOK, "dns_list.json") + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /dns_list", + servermock.ResponseFromFixture("dns_list.json"), + servermock.CheckQueryParameter().Strict(). + With("domain", "example.com"). + With("nichdl", "user"). + With("token", "secret")). + Build(t) records, err := client.ListRecords(t.Context(), "example.com") require.NoError(t, err) @@ -68,14 +47,26 @@ func TestClient_ListRecords(t *testing.T) { } func TestClient_ListRecords_error(t *testing.T) { - client := setupTest(t, "GET /dns_list", http.StatusNotFound, "dns_list_error.json") + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /dns_list", + servermock.ResponseFromFixture("dns_list_error.json"). + WithStatusCode(http.StatusNotFound)). + Build(t) _, err := client.ListRecords(t.Context(), "example.com") require.EqualError(t, err, "error: Domain not found (1)") } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, "GET /dns_delete", http.StatusOK, "dns_delete.json") + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /dns_delete", + servermock.ResponseFromFixture("dns_delete.json"), + servermock.CheckQueryParameter().Strict(). + With("id", "74749"). + With("domain", "example.com"). + With("nichdl", "user"). + With("token", "secret")). + Build(t) record := Record{ID: "74749"} @@ -84,7 +75,11 @@ func TestClient_DeleteRecord(t *testing.T) { } func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, "GET /dns_delete", http.StatusNotFound, "dns_delete_error.json") + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /dns_delete", + servermock.ResponseFromFixture("dns_delete_error.json"). + WithStatusCode(http.StatusNotFound)). + Build(t) record := Record{ID: "74749"} @@ -93,7 +88,15 @@ func TestClient_DeleteRecord_error(t *testing.T) { } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, "GET /dns_add", http.StatusOK, "dns_add.json") + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /dns_add", + servermock.ResponseFromFixture("dns_add.json"), + servermock.CheckQueryParameter().Strict(). + With("id", "74749"). + With("domain", "example.com"). + With("nichdl", "user"). + With("token", "secret")). + Build(t) record := Record{ID: "74749"} @@ -102,7 +105,11 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, "GET /dns_add", http.StatusNotFound, "dns_add_error.json") + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /dns_add", + servermock.ResponseFromFixture("dns_add_error.json"). + WithStatusCode(http.StatusNotFound)). + Build(t) record := Record{ID: "74749"} diff --git a/providers/dns/azion/azion_test.go b/providers/dns/azion/azion_test.go index de25e7c69..b3b553114 100644 --- a/providers/dns/azion/azion_test.go +++ b/providers/dns/azion/azion_test.go @@ -2,15 +2,12 @@ package azion import ( "context" - "io" - "net/http" "net/http/httptest" - "os" - "path/filepath" "testing" "github.com/aziontech/azionapi-go-sdk/idns" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -123,8 +120,9 @@ func TestLiveCleanUp(t *testing.T) { } func TestDNSProvider_findZone(t *testing.T) { - provider, mux := setupTest(t) - mux.HandleFunc("GET /intelligent_dns", writeFixtureHandler("zones.json")) + provider := mockBuilder(). + Route("GET /intelligent_dns", servermock.ResponseFromFixture("zones.json")). + Build(t) testCases := []struct { desc string @@ -198,8 +196,9 @@ func TestDNSProvider_findZone_error(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - provider, mux := setupTest(t) - mux.HandleFunc("GET /intelligent_dns", writeFixtureHandler(test.response)) + provider := mockBuilder(). + Route("GET /intelligent_dns", servermock.ResponseFromFixture(test.response)). + Build(t) zone, err := provider.findZone(context.Background(), test.fqdn) require.EqualError(t, err, test.expected) @@ -209,41 +208,25 @@ func TestDNSProvider_findZone_error(t *testing.T) { } } -func setupTest(t *testing.T) (*DNSProvider, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.PersonalToken = "secret" - mux := http.NewServeMux() - server := httptest.NewServer(mux) + provider, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } - config := NewDefaultConfig() - config.PersonalToken = "secret" + clientConfig := provider.client.GetConfig() + clientConfig.HTTPClient = server.Client() + clientConfig.Servers = idns.ServerConfigurations{{ + URL: server.URL, + Description: "Production", + }} - provider, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - clientConfig := provider.client.GetConfig() - clientConfig.HTTPClient = server.Client() - clientConfig.Servers = idns.ServerConfigurations{ - { - URL: server.URL, - Description: "Production", + return provider, nil }, - } - - return provider, mux -} - -func writeFixtureHandler(filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - rw.Header().Set("Content-Type", "application/json") - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, _ = io.Copy(rw, file) - } + ) } diff --git a/providers/dns/bluecat/internal/client_test.go b/providers/dns/bluecat/internal/client_test.go index c06ae1b8b..9d79f46b3 100644 --- a/providers/dns/bluecat/internal/client_test.go +++ b/providers/dns/bluecat/internal/client_test.go @@ -6,33 +6,37 @@ import ( "net/http/httptest" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestClient_LookupParentZoneID(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - +func setupClient(server *httptest.Server) (*Client, error) { client := NewClient(server.URL, "user", "secret") client.HTTPClient = server.Client() - mux.HandleFunc("/Services/REST/v1/getEntityByName", func(rw http.ResponseWriter, req *http.Request) { - query := req.URL.Query() + return client, nil +} - if query.Get("name") == "com" { - _ = json.NewEncoder(rw).Encode(EntityResponse{ - ID: 2, - Name: "com", - Type: ZoneType, - Properties: "test", - }) - return - } +func TestClient_LookupParentZoneID(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /Services/REST/v1/getEntityByName", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + query := req.URL.Query() - http.Error(rw, "{}", http.StatusOK) - }) + if query.Get("name") == "com" { + _ = json.NewEncoder(rw).Encode(EntityResponse{ + ID: 2, + Name: "com", + Type: ZoneType, + Properties: "test", + }) + return + } + + _, _ = rw.Write([]byte(`{}`)) + })). + Build(t) parentID, name, err := client.LookupParentZoneID(t.Context(), 2, "foo.example.com") require.NoError(t, err) diff --git a/providers/dns/bluecat/internal/identity_test.go b/providers/dns/bluecat/internal/identity_test.go index 3d9e00c0e..9ad4c18e6 100644 --- a/providers/dns/bluecat/internal/identity_test.go +++ b/providers/dns/bluecat/internal/identity_test.go @@ -1,11 +1,9 @@ package internal import ( - "fmt" - "net/http" - "net/http/httptest" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -13,39 +11,16 @@ import ( const fakeToken = "BAMAuthToken: dQfuRMTUxNjc3MjcyNDg1ODppcGFybXM=" func TestClient_CreateAuthenticatedContext(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient(server.URL, "user", "secret") - client.HTTPClient = server.Client() - - mux.HandleFunc("/Services/REST/v1/login", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - query := req.URL.Query() - if query.Get("username") != "user" { - http.Error(rw, fmt.Sprintf("invalid username %s", query.Get("username")), http.StatusUnauthorized) - return - } - - if query.Get("password") != "secret" { - http.Error(rw, fmt.Sprintf("invalid password %s", query.Get("password")), http.StatusUnauthorized) - return - } - - _, _ = fmt.Fprint(rw, fakeToken) - }) - mux.HandleFunc("/Services/REST/v1/delete", func(rw http.ResponseWriter, req *http.Request) { - authorization := req.Header.Get(authorizationHeader) - if authorization != fakeToken { - http.Error(rw, fmt.Sprintf("invalid credential: %s", authorization), http.StatusUnauthorized) - return - } - }) + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /Services/REST/v1/login", + servermock.RawStringResponse(fakeToken), + servermock.CheckQueryParameter(). + With("username", "user"). + With("password", "secret")). + Route("DELETE /Services/REST/v1/delete", nil, + servermock.CheckHeader(). + WithAuthorization(fakeToken)). + Build(t) ctx, err := client.CreateAuthenticatedContext(t.Context()) require.NoError(t, err) diff --git a/providers/dns/bookmyname/internal/client_test.go b/providers/dns/bookmyname/internal/client_test.go index 26e5f7227..900d62fef 100644 --- a/providers/dns/bookmyname/internal/client_test.go +++ b/providers/dns/bookmyname/internal/client_test.go @@ -1,58 +1,42 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, filename string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("user", "secret") + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client.HTTPClient = server.Client() + client.baseURL = server.URL - mux.HandleFunc("GET /", func(rw http.ResponseWriter, req *http.Request) { - username, password, ok := req.BasicAuth() - if username != "user" || password != "secret" || !ok { - http.Error(rw, fmt.Sprintf("auth: user %s, password %s, malformed", username, password), http.StatusOK) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(http.StatusOK) - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client, err := NewClient("user", "secret") - require.NoError(t, err) - - client.HTTPClient = server.Client() - client.baseURL = server.URL - - return client + return client, nil + }, + servermock.CheckHeader(). + WithBasicAuth("user", "secret")) } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, "add_success.txt") + client := mockBuilder(). + Route("GET /", + servermock.ResponseFromFixture("add_success.txt"), + servermock.CheckQueryParameter().Strict(). + With("do", "add"). + With("hostname", "_acme-challenge.sub.example.com."). + With("type", "txt"). + With("value", "test"). + With("ttl", "300"), + ). + Build(t) record := Record{ Hostname: "_acme-challenge.sub.example.com.", @@ -66,7 +50,12 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, "error.txt") + client := mockBuilder(). + Route("GET /", + servermock.ResponseFromFixture("error.txt"), + servermock.CheckQueryParameter(). + With("do", "add")). + Build(t) record := Record{ Hostname: "_acme-challenge.sub.example.com.", @@ -82,7 +71,17 @@ func TestClient_AddRecord_error(t *testing.T) { } func TestClient_RemoveRecord(t *testing.T) { - client := setupTest(t, "remove_success.txt") + client := mockBuilder(). + Route("GET /", + servermock.ResponseFromFixture("remove_success.txt"), + servermock.CheckQueryParameter().Strict(). + With("do", "remove"). + With("hostname", "_acme-challenge.sub.example.com."). + With("type", "txt"). + With("value", "test"). + With("ttl", "300"), + ). + Build(t) record := Record{ Hostname: "_acme-challenge.sub.example.com.", @@ -96,7 +95,12 @@ func TestClient_RemoveRecord(t *testing.T) { } func TestClient_RemoveRecord_error(t *testing.T) { - client := setupTest(t, "error.txt") + client := mockBuilder(). + Route("GET /", + servermock.ResponseFromFixture("error.txt"), + servermock.CheckQueryParameter(). + With("do", "remove")). + Build(t) record := Record{ Hostname: "_acme-challenge.sub.example.com.", diff --git a/providers/dns/brandit/internal/client_test.go b/providers/dns/brandit/internal/client_test.go index 0e79e5799..cb779ef68 100644 --- a/providers/dns/brandit/internal/client_test.go +++ b/providers/dns/brandit/internal/client_test.go @@ -1,49 +1,42 @@ package internal import ( - "io" - "net/http" "net/http/httptest" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, filename string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("user", "secret") + if err != nil { + return nil, err + } - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } + client.HTTPClient = server.Client() + client.baseURL = server.URL - defer func() { _ = file.Close() }() - - rw.WriteHeader(http.StatusOK) - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - })) - t.Cleanup(server.Close) - - client, err := NewClient("test_user", "apiKey") - require.NoError(t, err) - - client.HTTPClient = server.Client() - client.baseURL = server.URL - - return client + return client, nil + }, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded()) } func TestClient_StatusDomain(t *testing.T) { - client := setupTest(t, "status-domain.json") + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("status-domain.json"), + servermock.CheckForm().Strict(). + WithRegexp("signature", "[a-z0-9]+"). + WithRegexp("timestamp", `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`). + With("command", "statusDomain"). + With("user", "user"). + With("domain", "example.com"), + ). + Build(t) domain, err := client.StatusDomain(t.Context(), "example.com") require.NoError(t, err) @@ -79,14 +72,26 @@ func TestClient_StatusDomain(t *testing.T) { } func TestClient_StatusDomain_error(t *testing.T) { - client := setupTest(t, "error.json") + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("error.json")). + Build(t) _, err := client.StatusDomain(t.Context(), "example.com") require.ErrorIs(t, err, APIError{Code: 402, Status: "error", Message: "Invalid user."}) } func TestClient_ListRecords(t *testing.T) { - client := setupTest(t, "list-records.json") + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("list-records.json"), + servermock.CheckForm().Strict(). + WithRegexp("signature", "[a-z0-9]+"). + WithRegexp("timestamp", `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`). + With("account", "example"). + With("command", "listDNSRR"). + With("user", "user"). + With("dnszone", "example.com"), + ). + Build(t) resp, err := client.ListRecords(t.Context(), "example", "example.com") require.NoError(t, err) @@ -105,14 +110,28 @@ func TestClient_ListRecords(t *testing.T) { } func TestClient_ListRecords_error(t *testing.T) { - client := setupTest(t, "error.json") + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("error.json")). + Build(t) _, err := client.ListRecords(t.Context(), "example", "example.com") require.ErrorIs(t, err, APIError{Code: 402, Status: "error", Message: "Invalid user."}) } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, "add-record.json") + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("add-record.json"), + servermock.CheckForm().Strict(). + WithRegexp("signature", "[a-z0-9]+"). + WithRegexp("timestamp", `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`). + With("account", "test"). + With("command", "addDNSRR"). + With("key", "2565"). + With("user", "user"). + With("rrdata", "example.com 600 IN TXT txttxttxt"). + With("dnszone", "example.com"), + ). + Build(t) testRecord := Record{ ID: 2565, @@ -139,7 +158,9 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, "error.json") + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("error.json")). + Build(t) testRecord := Record{ ID: 2565, @@ -154,14 +175,28 @@ func TestClient_AddRecord_error(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, "delete-record.json") + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("delete-record.json"), + servermock.CheckForm().Strict(). + WithRegexp("signature", "[a-z0-9]+"). + WithRegexp("timestamp", `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`). + With("account", "test"). + With("command", "deleteDNSRR"). + With("key", "2374"). + With("user", "user"). + With("rrdata", "example.com 600 IN TXT txttxttxt"). + With("dnszone", "example.com"), + ). + Build(t) err := client.DeleteRecord(t.Context(), "example.com", "test", "example.com 600 IN TXT txttxttxt", "2374") require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, "error.json") + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("error.json")). + Build(t) err := client.DeleteRecord(t.Context(), "example.com", "test", "example.com 600 IN TXT txttxttxt", "2374") require.ErrorIs(t, err, APIError{Code: 402, Status: "error", Message: "Invalid user."}) diff --git a/providers/dns/checkdomain/internal/client_test.go b/providers/dns/checkdomain/internal/client_test.go index 60d55ee5e..31d419a5f 100644 --- a/providers/dns/checkdomain/internal/client_test.go +++ b/providers/dns/checkdomain/internal/client_test.go @@ -1,70 +1,38 @@ package internal import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" - "reflect" "testing" "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(OAuthStaticAccessToken(server.Client(), "secret")) + client.BaseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient(OAuthStaticAccessToken(server.Client(), "secret")) - client.BaseURL, _ = url.Parse(server.URL) - - return client, mux -} - -func checkAuthorizationHeader(req *http.Request) error { - val := req.Header.Get("Authorization") - if val != "Bearer secret" { - return fmt.Errorf("invalid header value, got: %s want %s", val, "Bearer secret") - } - return nil + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Bearer secret")) } func TestClient_GetDomainIDByName(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - err := checkAuthorizationHeader(req) - if err != nil { - http.Error(rw, err.Error(), http.StatusUnauthorized) - return - } - - domainList := DomainListingResponse{ - Embedded: EmbeddedDomainList{Domains: []*Domain{ - {ID: 1, Name: "test.com"}, - {ID: 2, Name: "test.org"}, - }}, - } - - err = json.NewEncoder(rw).Encode(domainList) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("GET /v1/domains", + servermock.JSONEncode(DomainListingResponse{ + Embedded: EmbeddedDomainList{Domains: []*Domain{ + {ID: 1, Name: "test.com"}, + {ID: 2, Name: "test.org"}, + }}, + })). + Build(t) id, err := client.GetDomainIDByName(t.Context(), "test.com") require.NoError(t, err) @@ -73,65 +41,26 @@ func TestClient_GetDomainIDByName(t *testing.T) { } func TestClient_CheckNameservers(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/1/nameservers", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - err := checkAuthorizationHeader(req) - if err != nil { - http.Error(rw, err.Error(), http.StatusUnauthorized) - return - } - - nsResp := NameserverResponse{ - Nameservers: []*Nameserver{ - {Name: ns1}, - {Name: ns2}, - // {Name: "ns.fake.de"}, - }, - } - - err = json.NewEncoder(rw).Encode(nsResp) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("GET /v1/domains/1/nameservers", + servermock.JSONEncode(NameserverResponse{ + Nameservers: []*Nameserver{ + {Name: ns1}, + {Name: ns2}, + // {Name: "ns.fake.de"}, + }, + })). + Build(t) err := client.CheckNameservers(t.Context(), 1) require.NoError(t, err) } func TestClient_CreateRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/1/nameservers/records", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - err := checkAuthorizationHeader(req) - if err != nil { - http.Error(rw, err.Error(), http.StatusUnauthorized) - return - } - - content, err := io.ReadAll(req.Body) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - if string(bytes.TrimSpace(content)) != `{"name":"test.com","value":"value","ttl":300,"priority":0,"type":"TXT"}` { - http.Error(rw, "invalid request body: "+string(content), http.StatusBadRequest) - return - } - }) + client := mockBuilder(). + Route("POST /v1/domains/1/nameservers/records", nil, + servermock.CheckRequestJSONBodyFromFile("create_record-request.json")). + Build(t) record := &Record{ Name: "test.com", @@ -145,114 +74,44 @@ func TestClient_CreateRecord(t *testing.T) { } func TestClient_DeleteTXTRecord(t *testing.T) { - client, mux := setupTest(t) - domainName := "lego.test" recordValue := "test" - records := []*Record{ - { - Name: "_acme-challenge", - Value: recordValue, - Type: "TXT", - }, - { - Name: "_acme-challenge", - Value: recordValue, - Type: "A", - }, - { - Name: "foobar", - Value: recordValue, - Type: "TXT", - }, - } - - expectedRecords := []*Record{ - { - Name: "_acme-challenge", - Value: recordValue, - Type: "A", - }, - { - Name: "foobar", - Value: recordValue, - Type: "TXT", - }, - } - - mux.HandleFunc("/v1/domains/1", func(rw http.ResponseWriter, req *http.Request) { - err := checkAuthorizationHeader(req) - if err != nil { - http.Error(rw, err.Error(), http.StatusUnauthorized) - return - } - - resp := DomainResponse{ - ID: 1, - Name: domainName, - } - - err = json.NewEncoder(rw).Encode(resp) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - mux.HandleFunc("/v1/domains/1/nameservers", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - nsResp := NameserverResponse{ - Nameservers: []*Nameserver{{Name: ns1}, {Name: ns2}}, - } - - err := json.NewEncoder(rw).Encode(nsResp) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - mux.HandleFunc("/v1/domains/1/nameservers/records", func(rw http.ResponseWriter, req *http.Request) { - switch req.Method { - case http.MethodGet: - resp := RecordListingResponse{ + client := mockBuilder(). + Route("GET /v1/domains/", + servermock.JSONEncode(DomainResponse{ + ID: 1, + Name: domainName, + })). + Route("GET /v1/domains/1/nameservers", + servermock.JSONEncode(NameserverResponse{ + Nameservers: []*Nameserver{{Name: ns1}, {Name: ns2}}, + })). + Route("GET /v1/domains/1/nameservers/records", + servermock.JSONEncode(RecordListingResponse{ Embedded: EmbeddedRecordList{ - Records: records, + Records: []*Record{ + { + Name: "_acme-challenge", + Value: recordValue, + Type: "TXT", + }, + { + Name: "_acme-challenge", + Value: recordValue, + Type: "A", + }, + { + Name: "foobar", + Value: recordValue, + Type: "TXT", + }, + }, }, - } - - err := json.NewEncoder(rw).Encode(resp) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - case http.MethodPut: - var records []*Record - err := json.NewDecoder(req.Body).Decode(&records) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - if len(records) == 0 { - http.Error(rw, "empty request body", http.StatusBadRequest) - return - } - - if !reflect.DeepEqual(expectedRecords, records) { - http.Error(rw, fmt.Sprintf("invalid records: %v", records), http.StatusBadRequest) - return - } - default: - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - } - }) + })). + Route("PUT /v1/domains/1/nameservers/records", nil, + servermock.CheckRequestJSONBodyFromFile("delete_txt_record-request.json")). + Build(t) info := dns01.GetChallengeInfo(domainName, "abc") err := client.DeleteTXTRecord(t.Context(), 1, info.EffectiveFQDN, recordValue) diff --git a/providers/dns/checkdomain/internal/fixtures/create_record-request.json b/providers/dns/checkdomain/internal/fixtures/create_record-request.json new file mode 100644 index 000000000..af1d50625 --- /dev/null +++ b/providers/dns/checkdomain/internal/fixtures/create_record-request.json @@ -0,0 +1,7 @@ +{ + "name": "test.com", + "value": "value", + "ttl": 300, + "priority": 0, + "type": "TXT" +} diff --git a/providers/dns/checkdomain/internal/fixtures/delete_txt_record-request.json b/providers/dns/checkdomain/internal/fixtures/delete_txt_record-request.json new file mode 100644 index 000000000..67cb2570c --- /dev/null +++ b/providers/dns/checkdomain/internal/fixtures/delete_txt_record-request.json @@ -0,0 +1,16 @@ +[ + { + "name": "_acme-challenge", + "value": "test", + "ttl": 0, + "priority": 0, + "type": "A" + }, + { + "name": "foobar", + "value": "test", + "ttl": 0, + "priority": 0, + "type": "TXT" + } +] diff --git a/providers/dns/clouddns/internal/client_test.go b/providers/dns/clouddns/internal/client_test.go index 2dee0bd0f..a8092933c 100644 --- a/providers/dns/clouddns/internal/client_test.go +++ b/providers/dns/clouddns/internal/client_test.go @@ -1,127 +1,63 @@ package internal import ( - "encoding/json" - "net/http" "net/http/httptest" "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("clientID", "email@example.com", "secret", 300) + client.HTTPClient = server.Client() + client.apiBaseURL, _ = url.Parse(server.URL + "/api") + client.loginURL, _ = url.Parse(server.URL + "/login") - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient("clientID", "email@example.com", "secret", 300) - client.HTTPClient = server.Client() - client.apiBaseURL, _ = url.Parse(server.URL + "/api") - client.loginURL, _ = url.Parse(server.URL + "/login") - - return client, mux + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(), + ) } func TestClient_AddRecord(t *testing.T) { - client, mux := setupTest(t) + client := mockBuilder(). + Route("POST /api/domain/search", + servermock.ResponseFromFixture("domain_search.json"), + servermock.CheckRequestJSONBodyFromFile("domain_search-request.json")). + Route("POST /api/record-txt", nil, + servermock.CheckRequestJSONBodyFromFile("record_txt-request.json")). + Route("PUT /api/domain/A/publish", nil, + servermock.CheckRequestJSONBodyFromFile("publish-request.json")). + Route("POST /login", + servermock.ResponseFromFixture("login.json"), + servermock.CheckRequestJSONBodyFromFile("login-request.json")). + Build(t) - mux.HandleFunc("/api/domain/search", func(rw http.ResponseWriter, req *http.Request) { - response := SearchResponse{ - Items: []Domain{ - { - ID: "A", - DomainName: "example.com", - }, - }, - } + ctx, err := client.CreateAuthenticatedContext(t.Context()) + require.NoError(t, err) - err := json.NewEncoder(rw).Encode(response) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - mux.HandleFunc("/api/record-txt", func(rw http.ResponseWriter, req *http.Request) {}) - mux.HandleFunc("/api/domain/A/publish", func(rw http.ResponseWriter, req *http.Request) {}) - mux.HandleFunc("/login", func(rw http.ResponseWriter, req *http.Request) { - response := AuthResponse{ - Auth: Auth{ - AccessToken: "at", - RefreshToken: "", - }, - } - - err := json.NewEncoder(rw).Encode(response) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - err := client.AddRecord(t.Context(), "example.com", "_acme-challenge.example.com", "txt") + err = client.AddRecord(ctx, "example.com", "_acme-challenge.example.com", "txt") require.NoError(t, err) } func TestClient_DeleteRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/api/domain/search", func(rw http.ResponseWriter, req *http.Request) { - response := SearchResponse{ - Items: []Domain{ - { - ID: "A", - DomainName: "example.com", - }, - }, - } - - err := json.NewEncoder(rw).Encode(response) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - mux.HandleFunc("/api/domain/A", func(rw http.ResponseWriter, req *http.Request) { - response := DomainInfo{ - ID: "Z", - DomainName: "example.com", - LastDomainRecordList: []Record{ - { - ID: "R01", - DomainID: "A", - Name: "_acme-challenge.example.com", - Value: "txt", - Type: "TXT", - }, - }, - SoaTTL: 300, - } - - err := json.NewEncoder(rw).Encode(response) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - mux.HandleFunc("/api/record/R01", func(rw http.ResponseWriter, req *http.Request) {}) - mux.HandleFunc("/api/domain/A/publish", func(rw http.ResponseWriter, req *http.Request) {}) - mux.HandleFunc("/login", func(rw http.ResponseWriter, req *http.Request) { - response := AuthResponse{ - Auth: Auth{ - AccessToken: "at", - RefreshToken: "", - }, - } - - err := json.NewEncoder(rw).Encode(response) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("POST /api/domain/search", + servermock.ResponseFromFixture("domain_search.json"), + servermock.CheckRequestJSONBodyFromFile("domain_search-request.json")). + Route("GET /api/domain/A", + servermock.ResponseFromFixture("domain-request.json")). + Route("DELETE /api/record/R01", nil). + Route("PUT /api/domain/A/publish", nil, + servermock.CheckRequestJSONBodyFromFile("publish-request.json")). + Route("POST /login", + servermock.ResponseFromFixture("login.json"), + servermock.CheckRequestJSONBodyFromFile("login-request.json")). + Build(t) ctx, err := client.CreateAuthenticatedContext(t.Context()) require.NoError(t, err) diff --git a/providers/dns/clouddns/internal/fixtures/domain-request.json b/providers/dns/clouddns/internal/fixtures/domain-request.json new file mode 100644 index 000000000..00f60b9bd --- /dev/null +++ b/providers/dns/clouddns/internal/fixtures/domain-request.json @@ -0,0 +1,14 @@ +{ + "id": "Z", + "domainName": "example.com", + "lastDomainRecordList": [ + { + "id": "R01", + "domainId": "A", + "name": "_acme-challenge.example.com", + "value": "txt", + "type": "TXT" + } + ], + "soaTtl": 300 +} diff --git a/providers/dns/clouddns/internal/fixtures/domain_search-request.json b/providers/dns/clouddns/internal/fixtures/domain_search-request.json new file mode 100644 index 000000000..89043dc3a --- /dev/null +++ b/providers/dns/clouddns/internal/fixtures/domain_search-request.json @@ -0,0 +1,14 @@ +{ + "search": [ + { + "name": "clientId", + "operator": "eq", + "value": "clientID" + }, + { + "name": "domainName", + "operator": "eq", + "value": "example.com" + } + ] +} diff --git a/providers/dns/clouddns/internal/fixtures/domain_search.json b/providers/dns/clouddns/internal/fixtures/domain_search.json new file mode 100644 index 000000000..4ee454732 --- /dev/null +++ b/providers/dns/clouddns/internal/fixtures/domain_search.json @@ -0,0 +1,8 @@ +{ + "items": [ + { + "id": "A", + "domainName": "example.com" + } + ] +} diff --git a/providers/dns/clouddns/internal/fixtures/login-request.json b/providers/dns/clouddns/internal/fixtures/login-request.json new file mode 100644 index 000000000..132577e6b --- /dev/null +++ b/providers/dns/clouddns/internal/fixtures/login-request.json @@ -0,0 +1,4 @@ +{ + "email": "email@example.com", + "password": "secret" +} diff --git a/providers/dns/clouddns/internal/fixtures/login.json b/providers/dns/clouddns/internal/fixtures/login.json new file mode 100644 index 000000000..e72ffb19b --- /dev/null +++ b/providers/dns/clouddns/internal/fixtures/login.json @@ -0,0 +1,5 @@ +{ + "auth": { + "accessToken": "at" + } +} diff --git a/providers/dns/clouddns/internal/fixtures/publish-request.json b/providers/dns/clouddns/internal/fixtures/publish-request.json new file mode 100644 index 000000000..383e26958 --- /dev/null +++ b/providers/dns/clouddns/internal/fixtures/publish-request.json @@ -0,0 +1,3 @@ +{ + "soaTtl": 300 +} diff --git a/providers/dns/clouddns/internal/fixtures/record_txt-request.json b/providers/dns/clouddns/internal/fixtures/record_txt-request.json new file mode 100644 index 000000000..cbc2a32a0 --- /dev/null +++ b/providers/dns/clouddns/internal/fixtures/record_txt-request.json @@ -0,0 +1,6 @@ +{ + "domainId": "A", + "name": "_acme-challenge.example.com", + "value": "txt", + "type": "TXT" +} diff --git a/providers/dns/clouddns/internal/identity_test.go b/providers/dns/clouddns/internal/identity_test.go index a3f3f55ea..df5e20eb8 100644 --- a/providers/dns/clouddns/internal/identity_test.go +++ b/providers/dns/clouddns/internal/identity_test.go @@ -1,38 +1,20 @@ package internal import ( - "encoding/json" - "net/http" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestClient_CreateAuthenticatedContext(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/login", func(rw http.ResponseWriter, req *http.Request) { - response := AuthResponse{ - Auth: Auth{ - AccessToken: "at", - RefreshToken: "", - }, - } - - err := json.NewEncoder(rw).Encode(response) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - mux.HandleFunc("/api/record/xxx", func(rw http.ResponseWriter, req *http.Request) { - authorization := req.Header.Get(authorizationHeader) - if authorization != "Bearer at" { - http.Error(rw, "invalid credential: "+authorization, http.StatusUnauthorized) - return - } - }) + client := mockBuilder(). + Route("POST /login", + servermock.ResponseFromFixture("login.json"), + servermock.CheckRequestJSONBodyFromFile("login-request.json")). + Route("DELETE /api/record/xxx", nil). + Build(t) ctx, err := client.CreateAuthenticatedContext(t.Context()) require.NoError(t, err) diff --git a/providers/dns/cloudns/internal/client_test.go b/providers/dns/cloudns/internal/client_test.go index e5d10b089..dbfa32aee 100644 --- a/providers/dns/cloudns/internal/client_test.go +++ b/providers/dns/cloudns/internal/client_test.go @@ -1,43 +1,25 @@ package internal import ( - "fmt" - "net/http" "net/http/httptest" "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, subAuthID string, handler http.HandlerFunc) *Client { - t.Helper() - - server := httptest.NewServer(handler) - t.Cleanup(server.Close) - - client, err := NewClient("myAuthID", subAuthID, "myAuthPassword") - require.NoError(t, err) - - client.BaseURL, _ = url.Parse(server.URL) - client.HTTPClient = server.Client() - - return client -} - -func handlerMock(method string, jsonData []byte) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, "Incorrect method used", http.StatusBadRequest) - return - } - - _, err := rw.Write(jsonData) +func setupClient(subAuthID string) func(server *httptest.Server) (*Client, error) { + return func(server *httptest.Server) (*Client, error) { + client, err := NewClient("myAuthID", subAuthID, "myAuthPassword") if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return + return nil, err } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + return client, nil } } @@ -131,7 +113,15 @@ func TestClient_GetZone(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client := setupTest(t, "", handlerMock(http.MethodGet, []byte(test.apiResponse))) + client := servermock.NewBuilder[*Client](setupClient("")). + Route("GET /get-zone-info.json", + servermock.RawStringResponse(test.apiResponse), + servermock.CheckQueryParameter().Strict(). + With("auth-id", "myAuthID"). + With("auth-password", "myAuthPassword"). + With("domain-name", "foo.com"), + ). + Build(t) zone, err := client.GetZone(t.Context(), test.authFQDN) @@ -238,7 +228,17 @@ func TestClient_FindTxtRecord(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client := setupTest(t, "", handlerMock(http.MethodGet, []byte(test.apiResponse))) + client := servermock.NewBuilder[*Client](setupClient("")). + Route("GET /records.json", + servermock.RawStringResponse(test.apiResponse), + servermock.CheckQueryParameter().Strict(). + With("auth-id", "myAuthID"). + With("auth-password", "myAuthPassword"). + With("type", "TXT"). + With("host", "_acme-challenge"). + With("domain-name", test.zoneName), + ). + Build(t) txtRecord, err := client.FindTxtRecord(t.Context(), test.zoneName, test.authFQDN) @@ -347,7 +347,17 @@ func TestClient_ListTxtRecord(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client := setupTest(t, "", handlerMock(http.MethodGet, []byte(test.apiResponse))) + client := servermock.NewBuilder[*Client](setupClient("")). + Route("GET /records.json", + servermock.RawStringResponse(test.apiResponse), + servermock.CheckQueryParameter().Strict(). + With("auth-id", "myAuthID"). + With("auth-password", "myAuthPassword"). + With("type", "TXT"). + With("host", "_acme-challenge"). + With("domain-name", test.zoneName), + ). + Build(t) txtRecords, err := client.ListTxtRecords(t.Context(), test.zoneName, test.authFQDN) @@ -363,7 +373,7 @@ func TestClient_ListTxtRecord(t *testing.T) { func TestClient_AddTxtRecord(t *testing.T) { type expected struct { - query string + query url.Values errorMsg string } @@ -387,7 +397,15 @@ func TestClient_AddTxtRecord(t *testing.T) { ttl: 60, apiResponse: `{"status":"Success","statusDescription":"The record was added successfully."}`, expected: expected{ - query: `auth-id=myAuthID&auth-password=myAuthPassword&domain-name=example.com&host=_acme-challenge.foo&record=txtTXTtxtTXTtxtTXTtxtTXT&record-type=TXT&ttl=60`, + query: url.Values{ + "auth-id": {"myAuthID"}, + "auth-password": {"myAuthPassword"}, + "domain-name": {"example.com"}, + "host": {"_acme-challenge.foo"}, + "record": {"txtTXTtxtTXTtxtTXTtxtTXT"}, + "record-type": {"TXT"}, + "ttl": {"60"}, + }, }, }, { @@ -399,7 +417,15 @@ func TestClient_AddTxtRecord(t *testing.T) { ttl: 60, apiResponse: `{"status":"Success","statusDescription":"The record was added successfully."}`, expected: expected{ - query: `auth-id=myAuthID&auth-password=myAuthPassword&domain-name=example.com&host=_acme-challenge&record=TXTtxtTXTtxtTXTtxtTXTtxt&record-type=TXT&ttl=60`, + query: url.Values{ + "auth-id": {"myAuthID"}, + "auth-password": {"myAuthPassword"}, + "domain-name": {"example.com"}, + "host": {"_acme-challenge"}, + "record": {"TXTtxtTXTtxtTXTtxtTXTtxt"}, + "record-type": {"TXT"}, + "ttl": {"60"}, + }, }, }, { @@ -411,7 +437,15 @@ func TestClient_AddTxtRecord(t *testing.T) { ttl: 60, apiResponse: `{"status":"Success","statusDescription":"The record was added successfully."}`, expected: expected{ - query: `auth-password=myAuthPassword&domain-name=example.com&host=_acme-challenge&record=TXTtxtTXTtxtTXTtxtTXTtxt&record-type=TXT&sub-auth-id=mySubAuthID&ttl=60`, + query: url.Values{ + "auth-password": {"myAuthPassword"}, + "domain-name": {"example.com"}, + "host": {"_acme-challenge"}, + "record": {"TXTtxtTXTtxtTXTtxtTXTtxt"}, + "record-type": {"TXT"}, + "sub-auth-id": {"mySubAuthID"}, + "ttl": {"60"}, + }, }, }, { @@ -423,7 +457,15 @@ func TestClient_AddTxtRecord(t *testing.T) { ttl: 120, apiResponse: `{"status":"Failed","statusDescription":"Invalid TTL. Choose from the list of the values we support."}`, expected: expected{ - query: `auth-id=myAuthID&auth-password=myAuthPassword&domain-name=example.com&host=_acme-challenge&record=TXTtxtTXTtxtTXTtxtTXTtxt&record-type=TXT&ttl=300`, + query: url.Values{ + "auth-id": {"myAuthID"}, + "auth-password": {"myAuthPassword"}, + "domain-name": {"example.com"}, + "host": {"_acme-challenge"}, + "record": {"TXTtxtTXTtxtTXTtxtTXTtxt"}, + "record-type": {"TXT"}, + "ttl": {"300"}, + }, errorMsg: "failed to add TXT record: Failed Invalid TTL. Choose from the list of the values we support.", }, }, @@ -436,7 +478,15 @@ func TestClient_AddTxtRecord(t *testing.T) { ttl: 120, apiResponse: `[{}]`, expected: expected{ - query: `auth-id=myAuthID&auth-password=myAuthPassword&domain-name=example.com&host=_acme-challenge&record=TXTtxtTXTtxtTXTtxtTXTtxt&record-type=TXT&ttl=300`, + query: url.Values{ + "auth-id": {"myAuthID"}, + "auth-password": {"myAuthPassword"}, + "domain-name": {"example.com"}, + "host": {"_acme-challenge"}, + "record": {"TXTtxtTXTtxtTXTtxtTXTtxt"}, + "record-type": {"TXT"}, + "ttl": {"300"}, + }, errorMsg: "unable to unmarshal response: [status code: 200] body: [{}] error: json: cannot unmarshal array into Go value of type internal.apiResponse", }, }, @@ -444,15 +494,13 @@ func TestClient_AddTxtRecord(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client := setupTest(t, test.subAuthID, func(rw http.ResponseWriter, req *http.Request) { - if test.expected.query != req.URL.RawQuery { - msg := fmt.Sprintf("got: %s, want: %s", test.expected.query, req.URL.RawQuery) - http.Error(rw, msg, http.StatusBadRequest) - return - } - - handlerMock(http.MethodPost, []byte(test.apiResponse))(rw, req) - }) + client := servermock.NewBuilder[*Client](setupClient(test.subAuthID)). + Route("POST /add-record.json", + servermock.RawStringResponse(test.apiResponse), + servermock.CheckQueryParameter().Strict(). + WithValues(test.expected.query), + ). + Build(t) err := client.AddTxtRecord(t.Context(), test.zoneName, test.authFQDN, test.value, test.ttl) @@ -467,7 +515,7 @@ func TestClient_AddTxtRecord(t *testing.T) { func TestClient_RemoveTxtRecord(t *testing.T) { type expected struct { - query string + query url.Values errorMsg string } @@ -484,7 +532,12 @@ func TestClient_RemoveTxtRecord(t *testing.T) { zoneName: "foo.com", apiResponse: `{ "status": "Success", "statusDescription": "The record was deleted successfully." }`, expected: expected{ - query: `auth-id=myAuthID&auth-password=myAuthPassword&domain-name=foo.com&record-id=5769228`, + query: url.Values{ + "auth-id": {"myAuthID"}, + "auth-password": {"myAuthPassword"}, + "domain-name": {"foo.com"}, + "record-id": {"5769228"}, + }, }, }, { @@ -493,7 +546,12 @@ func TestClient_RemoveTxtRecord(t *testing.T) { zoneName: "foo.com", apiResponse: `{ "status": "Failed", "statusDescription": "Invalid record-id param." }`, expected: expected{ - query: `auth-id=myAuthID&auth-password=myAuthPassword&domain-name=foo.com&record-id=5769000`, + query: url.Values{ + "auth-id": {"myAuthID"}, + "auth-password": {"myAuthPassword"}, + "domain-name": {"foo.com"}, + "record-id": {"5769000"}, + }, errorMsg: "failed to remove TXT record: Failed Invalid record-id param.", }, }, @@ -503,7 +561,12 @@ func TestClient_RemoveTxtRecord(t *testing.T) { zoneName: "foo-plus.com", apiResponse: `[{}]`, expected: expected{ - query: `auth-id=myAuthID&auth-password=myAuthPassword&domain-name=foo-plus.com&record-id=44`, + query: url.Values{ + "auth-id": {"myAuthID"}, + "auth-password": {"myAuthPassword"}, + "domain-name": {"foo-plus.com"}, + "record-id": {"44"}, + }, errorMsg: "unable to unmarshal response: [status code: 200] body: [{}] error: json: cannot unmarshal array into Go value of type internal.apiResponse", }, }, @@ -511,23 +574,15 @@ func TestClient_RemoveTxtRecord(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - if test.expected.query != req.URL.RawQuery { - msg := fmt.Sprintf("got: %s, want: %s", test.expected.query, req.URL.RawQuery) - http.Error(rw, msg, http.StatusBadRequest) - return - } + client := servermock.NewBuilder[*Client](setupClient("")). + Route("POST /delete-record.json", + servermock.RawStringResponse(test.apiResponse), + servermock.CheckQueryParameter().Strict(). + WithValues(test.expected.query), + ). + Build(t) - handlerMock(http.MethodPost, []byte(test.apiResponse))(rw, req) - })) - t.Cleanup(server.Close) - - client, err := NewClient("myAuthID", "", "myAuthPassword") - require.NoError(t, err) - - client.BaseURL, _ = url.Parse(server.URL) - - err = client.RemoveTxtRecord(t.Context(), test.id, test.zoneName) + err := client.RemoveTxtRecord(t.Context(), test.id, test.zoneName) if test.expected.errorMsg != "" { require.EqualError(t, err, test.expected.errorMsg) @@ -589,13 +644,15 @@ func TestClient_GetUpdateStatus(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - server := httptest.NewServer(handlerMock(http.MethodGet, []byte(test.apiResponse))) - t.Cleanup(server.Close) - - client, err := NewClient("myAuthID", "", "myAuthPassword") - require.NoError(t, err) - - client.BaseURL, _ = url.Parse(server.URL) + client := servermock.NewBuilder[*Client](setupClient("")). + Route("GET /update-status.json", + servermock.RawStringResponse(test.apiResponse), + servermock.CheckQueryParameter().Strict(). + With("auth-id", "myAuthID"). + With("auth-password", "myAuthPassword"). + With("domain-name", test.zoneName), + ). + Build(t) syncProgress, err := client.GetUpdateStatus(t.Context(), test.zoneName) diff --git a/providers/dns/cloudru/internal/client_test.go b/providers/dns/cloudru/internal/client_test.go index 21e227f76..3b087d617 100644 --- a/providers/dns/cloudru/internal/client_test.go +++ b/providers/dns/cloudru/internal/client_test.go @@ -1,62 +1,40 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" "time" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, handler http.HandlerFunc) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret") + client.HTTPClient = server.Client() + client.APIEndpoint, _ = url.Parse(server.URL) + client.token = &Token{ + AccessToken: "secret", + ExpiresIn: 60, + TokenType: "Bearer", + Deadline: time.Now().Add(1 * time.Minute), + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(pattern, handler) - - client := NewClient("user", "secret") - client.HTTPClient = server.Client() - client.APIEndpoint, _ = url.Parse(server.URL) - client.token = &Token{ - AccessToken: "secret", - ExpiresIn: 60, - TokenType: "Bearer", - Deadline: time.Now().Add(1 * time.Minute), - } - - return client -} - -func writeFixtureHandler(method, filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, _ = io.Copy(rw, file) - } + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Bearer xxx")) } func TestClient_GetZones(t *testing.T) { - client := setupTest(t, "/zones", writeFixtureHandler(http.MethodGet, "zones.json")) + client := mockBuilder(). + Route("GET /zones", + servermock.ResponseFromFixture("zones.json")). + Build(t) ctx := mockContext(t) @@ -78,7 +56,10 @@ func TestClient_GetZones(t *testing.T) { } func TestClient_GetRecords(t *testing.T) { - client := setupTest(t, "/zones/zzz/records", writeFixtureHandler(http.MethodGet, "records.json")) + client := mockBuilder(). + Route("GET /zones/zzz/records", + servermock.ResponseFromFixture("records.json")). + Build(t) ctx := mockContext(t) @@ -122,7 +103,11 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_CreateRecord(t *testing.T) { - client := setupTest(t, "/zones/zzz/records", writeFixtureHandler(http.MethodPost, "record.json")) + client := mockBuilder(). + Route("POST /zones/zzz/records", + servermock.ResponseFromFixture("record.json"), + servermock.CheckRequestJSONBody(`{"name":"www.example.com.","type":"TXT","values":["text"],"ttl":"3600"}`)). + Build(t) ctx := mockContext(t) @@ -150,7 +135,10 @@ func TestClient_CreateRecord(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, "/zones/zzz/records/example.com/TXT", writeFixtureHandler(http.MethodDelete, "record.json")) + client := mockBuilder(). + Route("DELETE /zones/zzz/records/example.com/TXT", + servermock.ResponseFromFixture("record.json")). + Build(t) ctx := mockContext(t) diff --git a/providers/dns/cloudru/internal/identity_test.go b/providers/dns/cloudru/internal/identity_test.go index 68dbd90cd..c1097c015 100644 --- a/providers/dns/cloudru/internal/identity_test.go +++ b/providers/dns/cloudru/internal/identity_test.go @@ -2,13 +2,11 @@ package internal import ( "context" - "encoding/json" - "fmt" - "net/http" "net/http/httptest" "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -19,47 +17,33 @@ func mockContext(t *testing.T) context.Context { return context.WithValue(t.Context(), tokenKey, &Token{AccessToken: "xxx"}) } -func tokenHandler(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("invalid method, got %s want %s", req.Method, http.MethodPost), http.StatusMethodNotAllowed) - return - } - - err := req.ParseForm() - if err != nil { - http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } - - grantType := req.Form.Get("grant_type") - clientID := req.Form.Get("client_id") - clientSecret := req.Form.Get("client_secret") - - if clientID != "user" || clientSecret != "secret" || grantType != "access_key" { - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - _ = json.NewEncoder(rw).Encode(Token{ - AccessToken: "xxx", - TokenID: "yyy", - ExpiresIn: 666, - TokenType: "Bearer", - Scope: "openid profile email roles", - }) -} - -func TestClient_obtainToken(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/", tokenHandler) - +func setupIdentityClient(server *httptest.Server) (*Client, error) { client := NewClient("user", "secret") client.HTTPClient = server.Client() client.AuthEndpoint, _ = url.Parse(server.URL) + return client, nil +} + +func TestClient_obtainToken(t *testing.T) { + client := servermock.NewBuilder[*Client](setupIdentityClient, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded(), + ). + Route("POST /", servermock.JSONEncode(Token{ + AccessToken: "xxx", + TokenID: "yyy", + ExpiresIn: 666, + TokenType: "Bearer", + Scope: "openid profile email roles", + }), + servermock.CheckForm().Strict(). + With("client_id", "user"). + With("client_secret", "secret"). + With("grant_type", "access_key"), + ). + Build(t) + assert.Nil(t, client.token) tok, err := client.obtainToken(t.Context()) @@ -71,15 +55,23 @@ func TestClient_obtainToken(t *testing.T) { } func TestClient_CreateAuthenticatedContext(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/", tokenHandler) - - client := NewClient("user", "secret") - client.HTTPClient = server.Client() - client.AuthEndpoint, _ = url.Parse(server.URL) + client := servermock.NewBuilder[*Client](setupIdentityClient, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded(), + ). + Route("POST /", servermock.JSONEncode(Token{ + AccessToken: "xxx", + TokenID: "yyy", + ExpiresIn: 666, + TokenType: "Bearer", + Scope: "openid profile email roles", + }), + servermock.CheckForm().Strict(). + With("client_id", "user"). + With("client_secret", "secret"). + With("grant_type", "access_key"), + ). + Build(t) assert.Nil(t, client.token) diff --git a/providers/dns/conoha/internal/client_test.go b/providers/dns/conoha/internal/client_test.go index 0cabb30dd..0b9242c08 100644 --- a/providers/dns/conoha/internal/client_test.go +++ b/providers/dns/conoha/internal/client_test.go @@ -11,60 +11,26 @@ import ( "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("tyo1", "secret") + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - client, err := NewClient("tyo1", "secret") - require.NoError(t, err) - - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client, mux -} - -func writeFixtureHandler(method, filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - writeFixture(rw, filename) - } -} - -func writeBodyHandler(method, content string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - _, err := fmt.Fprint(rw, content) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } -} - -func writeFixture(rw http.ResponseWriter, filename string) { - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, _ = io.Copy(rw, file) + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + With("X-Auth-Token", "secret")) } func TestClient_GetDomainID(t *testing.T) { @@ -76,34 +42,34 @@ func TestClient_GetDomainID(t *testing.T) { testCases := []struct { desc string domainName string - handler http.HandlerFunc + response string expected expected }{ { desc: "success", domainName: "domain1.com.", - handler: writeFixtureHandler(http.MethodGet, "domains_GET.json"), + response: "domains_GET.json", expected: expected{domainID: "09494b72-b65b-4297-9efb-187f65a0553e"}, }, { desc: "non existing domain", domainName: "domain1.com.", - handler: writeBodyHandler(http.MethodGet, "{}"), + response: "empty.json", expected: expected{error: true}, }, { desc: "marshaling error", domainName: "domain1.com.", - handler: writeBodyHandler(http.MethodGet, "[]"), + response: "empty.json", expected: expected{error: true}, }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client, mux := setupTest(t) - - mux.Handle("/v1/domains", test.handler) + client := mockBuilder(). + Route("GET /v1/domains", servermock.ResponseFromFixture(test.response)). + Build(t) domainID, err := client.GetDomainID(t.Context(), test.domainName) @@ -126,11 +92,6 @@ func TestClient_CreateRecord(t *testing.T) { { desc: "success", handler: func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - raw, err := io.ReadAll(req.Body) if err != nil { http.Error(rw, err.Error(), http.StatusBadRequest) @@ -143,18 +104,20 @@ func TestClient_CreateRecord(t *testing.T) { return } - writeFixture(rw, "domains-records_POST.json") + file, err := os.Open(filepath.Join("fixtures", "domains-records_POST.json")) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + defer func() { _ = file.Close() }() + + _, _ = io.Copy(rw, file) }, assert: require.NoError, }, { desc: "bad request", handler: func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - http.Error(rw, "OOPS", http.StatusBadRequest) }, assert: require.Error, @@ -163,9 +126,9 @@ func TestClient_CreateRecord(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client, mux := setupTest(t) - - mux.Handle("/v1/domains/lego/records", test.handler) + client := mockBuilder(). + Route("POST /v1/domains/lego/records", test.handler). + Build(t) domainID := "lego" @@ -183,10 +146,10 @@ func TestClient_CreateRecord(t *testing.T) { } func TestClient_GetRecordID(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records", - writeFixtureHandler(http.MethodGet, "domains-records_GET.json")) + client := mockBuilder(). + Route("GET /v1/domains/89acac79-38e7-497d-807c-a011e1310438/records", + servermock.ResponseFromFixture("domains-records_GET.json")). + Build(t) recordID, err := client.GetRecordID(t.Context(), "89acac79-38e7-497d-807c-a011e1310438", "www.example.com.", "A", "15.185.172.153") require.NoError(t, err) @@ -195,16 +158,10 @@ func TestClient_GetRecordID(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records/2e32e609-3a4f-45ba-bdef-e50eacd345ad", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - rw.WriteHeader(http.StatusOK) - }) + client := mockBuilder(). + Route("DELETE /v1/domains/89acac79-38e7-497d-807c-a011e1310438/records/2e32e609-3a4f-45ba-bdef-e50eacd345ad", + servermock.ResponseFromFixture("domains-records_GET.json")). + Build(t) err := client.DeleteRecord(t.Context(), "89acac79-38e7-497d-807c-a011e1310438", "2e32e609-3a4f-45ba-bdef-e50eacd345ad") require.NoError(t, err) diff --git a/providers/dns/conoha/internal/fixtures/empty.json b/providers/dns/conoha/internal/fixtures/empty.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/providers/dns/conoha/internal/fixtures/empty.json @@ -0,0 +1 @@ +{} diff --git a/providers/dns/conoha/internal/identity_test.go b/providers/dns/conoha/internal/identity_test.go index 77db51f09..0bd4c936a 100644 --- a/providers/dns/conoha/internal/identity_test.go +++ b/providers/dns/conoha/internal/identity_test.go @@ -1,27 +1,33 @@ package internal import ( - "net/http" "net/http/httptest" "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestNewClient(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - +func setupIdentifier(server *httptest.Server) (*Identifier, error) { identifier, err := NewIdentifier("tyo1") - require.NoError(t, err) + if err != nil { + return nil, err + } identifier.HTTPClient = server.Client() identifier.baseURL, _ = url.Parse(server.URL) - mux.HandleFunc("/v2.0/tokens", writeFixtureHandler(http.MethodPost, "tokens_POST.json")) + return identifier, nil +} + +func TestNewClient(t *testing.T) { + identifier := servermock.NewBuilder[*Identifier](setupIdentifier, + servermock.CheckHeader().WithJSONHeaders(), + ). + Route("POST /v2.0/tokens", servermock.ResponseFromFixture("tokens_POST.json")). + Build(t) auth := Auth{ TenantID: "487727e3921d44e3bfe7ebb337bf085e", diff --git a/providers/dns/conohav3/internal/client_test.go b/providers/dns/conohav3/internal/client_test.go index 9600b2f06..babdadf7e 100644 --- a/providers/dns/conohav3/internal/client_test.go +++ b/providers/dns/conohav3/internal/client_test.go @@ -11,60 +11,27 @@ import ( "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("c3j1", "secret") + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - client, err := NewClient("c3j1", "secret") - require.NoError(t, err) - - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client, mux -} - -func writeFixtureHandler(method, filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - writeFixture(rw, filename) - } -} - -func writeBodyHandler(method, content string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - _, err := fmt.Fprint(rw, content) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } -} - -func writeFixture(rw http.ResponseWriter, filename string) { - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, _ = io.Copy(rw, file) + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With("X-Auth-Token", "secret")) } func TestClient_GetDomainID(t *testing.T) { @@ -76,34 +43,34 @@ func TestClient_GetDomainID(t *testing.T) { testCases := []struct { desc string domainName string - handler http.HandlerFunc + response string expected expected }{ { desc: "success", domainName: "domain1.com.", - handler: writeFixtureHandler(http.MethodGet, "domains_GET.json"), + response: "domains_GET.json", expected: expected{domainID: "09494b72-b65b-4297-9efb-187f65a0553e"}, }, { desc: "non existing domain", domainName: "domain1.com.", - handler: writeBodyHandler(http.MethodGet, "{}"), + response: "empty.json", expected: expected{error: true}, }, { desc: "marshaling error", domainName: "domain1.com.", - handler: writeBodyHandler(http.MethodGet, "[]"), + response: "empty.json", expected: expected{error: true}, }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client, mux := setupTest(t) - - mux.Handle("/v1/domains", test.handler) + client := mockBuilder(). + Route("GET /v1/domains", servermock.ResponseFromFixture(test.response)). + Build(t) domainID, err := client.GetDomainID(t.Context(), test.domainName) @@ -126,11 +93,6 @@ func TestClient_CreateRecord(t *testing.T) { { desc: "success", handler: func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - raw, err := io.ReadAll(req.Body) if err != nil { http.Error(rw, err.Error(), http.StatusBadRequest) @@ -143,18 +105,20 @@ func TestClient_CreateRecord(t *testing.T) { return } - writeFixture(rw, "domains-records_POST.json") + file, err := os.Open(filepath.Join("fixtures", "domains-records_POST.json")) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + defer func() { _ = file.Close() }() + + _, _ = io.Copy(rw, file) }, assert: require.NoError, }, { desc: "bad request", handler: func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - http.Error(rw, "OOPS", http.StatusBadRequest) }, assert: require.Error, @@ -163,9 +127,9 @@ func TestClient_CreateRecord(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client, mux := setupTest(t) - - mux.Handle("/v1/domains/lego/records", test.handler) + client := mockBuilder(). + Route("POST /v1/domains/lego/records", test.handler). + Build(t) domainID := "lego" @@ -183,10 +147,10 @@ func TestClient_CreateRecord(t *testing.T) { } func TestClient_GetRecordID(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records", - writeFixtureHandler(http.MethodGet, "domains-records_GET.json")) + client := mockBuilder(). + Route("GET /v1/domains/89acac79-38e7-497d-807c-a011e1310438/records", + servermock.ResponseFromFixture("domains-records_GET.json")). + Build(t) recordID, err := client.GetRecordID(t.Context(), "89acac79-38e7-497d-807c-a011e1310438", "www.example.com.", "A", "15.185.172.153") require.NoError(t, err) @@ -195,16 +159,10 @@ func TestClient_GetRecordID(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records/2e32e609-3a4f-45ba-bdef-e50eacd345ad", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - rw.WriteHeader(http.StatusOK) - }) + client := mockBuilder(). + Route("DELETE /v1/domains/89acac79-38e7-497d-807c-a011e1310438/records/2e32e609-3a4f-45ba-bdef-e50eacd345ad", + servermock.ResponseFromFixture("domains-records_GET.json")). + Build(t) err := client.DeleteRecord(t.Context(), "89acac79-38e7-497d-807c-a011e1310438", "2e32e609-3a4f-45ba-bdef-e50eacd345ad") require.NoError(t, err) diff --git a/providers/dns/conohav3/internal/fixtures/empty.json b/providers/dns/conohav3/internal/fixtures/empty.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/providers/dns/conohav3/internal/fixtures/empty.json @@ -0,0 +1 @@ +{} diff --git a/providers/dns/conohav3/internal/identity_test.go b/providers/dns/conohav3/internal/identity_test.go index d5222c05d..d479a18d9 100644 --- a/providers/dns/conohav3/internal/identity_test.go +++ b/providers/dns/conohav3/internal/identity_test.go @@ -6,26 +6,32 @@ import ( "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestGetToken_HeaderToken(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - +func setupIdentifier(server *httptest.Server) (*Identifier, error) { identifier, err := NewIdentifier("c3j1") - require.NoError(t, err) + if err != nil { + return nil, err + } identifier.HTTPClient = server.Client() identifier.baseURL, _ = url.Parse(server.URL) - mux.HandleFunc("/v3/auth/tokens", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("x-subject-token", "sample-header-token-123") - w.WriteHeader(http.StatusCreated) - _, _ = w.Write([]byte(`{}`)) - }) + return identifier, nil +} + +func TestGetToken_HeaderToken(t *testing.T) { + identifier := servermock.NewBuilder[*Identifier](setupIdentifier, + servermock.CheckHeader().WithJSONHeaders(), + ). + Route("POST /v3/auth/tokens", + servermock.ResponseFromFixture("empty.json"). + WithStatusCode(http.StatusCreated). + WithHeader("x-subject-token", "sample-header-token-123")). + Build(t) auth := Auth{ Identity: Identity{ diff --git a/providers/dns/constellix/internal/domains_test.go b/providers/dns/constellix/internal/domains_test.go index f6ade9d31..2d92fb8f3 100644 --- a/providers/dns/constellix/internal/domains_test.go +++ b/providers/dns/constellix/internal/domains_test.go @@ -1,51 +1,30 @@ package internal import ( - "io" - "net/http" "net/http/httptest" - "os" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(server.Client()) + client.BaseURL = server.URL - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient(server.Client()) - client.BaseURL = server.URL - - return client, mux + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(), + ) } func TestDomainService_GetAll(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - file, err := os.Open("./fixtures/domains-GetAll.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("GET /v1/domains", servermock.ResponseFromFixture("domains-GetAll.json")). + Build(t) data, err := client.Domains.GetAll(t.Context(), nil) require.NoError(t, err) @@ -61,27 +40,12 @@ func TestDomainService_GetAll(t *testing.T) { } func TestDomainService_Search(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/search", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - file, err := os.Open("./fixtures/domains-Search.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("GET /v1/domains/search", + servermock.ResponseFromFixture("domains-Search.json"), + servermock.CheckQueryParameter().Strict(). + With("exact", "lego.wtf")). + Build(t) data, err := client.Domains.Search(t.Context(), Exact, "lego.wtf") require.NoError(t, err) diff --git a/providers/dns/constellix/internal/txtrecords_test.go b/providers/dns/constellix/internal/txtrecords_test.go index ee4d20bf2..54d10dc38 100644 --- a/providers/dns/constellix/internal/txtrecords_test.go +++ b/providers/dns/constellix/internal/txtrecords_test.go @@ -2,37 +2,19 @@ package internal import ( "encoding/json" - "io" - "net/http" "os" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestTxtRecordService_Create(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/12345/records/txt", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - file, err := os.Open("./fixtures/records-Create.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("POST /v1/domains/12345/records/txt", servermock.ResponseFromFixture("records-Create.json"), + servermock.CheckRequestJSONBody(`{"name":""}`)). + Build(t) records, err := client.TxtRecords.Create(t.Context(), 12345, RecordRequest{}) require.NoError(t, err) @@ -47,27 +29,9 @@ func TestTxtRecordService_Create(t *testing.T) { } func TestTxtRecordService_GetAll(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/12345/records/txt", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - file, err := os.Open("./fixtures/records-GetAll.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("GET /v1/domains/12345/records/txt", servermock.ResponseFromFixture("records-GetAll.json")). + Build(t) records, err := client.TxtRecords.GetAll(t.Context(), 12345) require.NoError(t, err) @@ -82,27 +46,9 @@ func TestTxtRecordService_GetAll(t *testing.T) { } func TestTxtRecordService_Get(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/12345/records/txt/6789", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - file, err := os.Open("./fixtures/records-Get.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("GET /v1/domains/12345/records/txt/6789", servermock.ResponseFromFixture("records-Get.json")). + Build(t) record, err := client.TxtRecords.Get(t.Context(), 12345, 6789) require.NoError(t, err) @@ -130,20 +76,10 @@ func TestTxtRecordService_Get(t *testing.T) { } func TestTxtRecordService_Update(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/12345/records/txt/6789", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPut { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - _, err := rw.Write([]byte(`{"success":"Record updated successfully"}`)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("PUT /v1/domains/12345/records/txt/6789", + servermock.RawStringResponse(`{"success":"Record updated successfully"}`)). + Build(t) msg, err := client.TxtRecords.Update(t.Context(), 12345, 6789, RecordRequest{}) require.NoError(t, err) @@ -153,20 +89,10 @@ func TestTxtRecordService_Update(t *testing.T) { } func TestTxtRecordService_Delete(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/12345/records/txt/6789", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - _, err := rw.Write([]byte(`{"success":"Record deleted successfully"}`)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("DELETE /v1/domains/12345/records/txt/6789", + servermock.RawStringResponse(`{"success":"Record deleted successfully"}`)). + Build(t) msg, err := client.TxtRecords.Delete(t.Context(), 12345, 6789) require.NoError(t, err) @@ -176,27 +102,9 @@ func TestTxtRecordService_Delete(t *testing.T) { } func TestTxtRecordService_Search(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/12345/records/txt/search", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - file, err := os.Open("./fixtures/records-Search.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("GET /v1/domains/12345/records/txt/search", servermock.ResponseFromFixture("records-Search.json")). + Build(t) records, err := client.TxtRecords.Search(t.Context(), 12345, Exact, "test") require.NoError(t, err) diff --git a/providers/dns/corenetworks/internal/client_test.go b/providers/dns/corenetworks/internal/client_test.go index ec6de452e..ca5c81a65 100644 --- a/providers/dns/corenetworks/internal/client_test.go +++ b/providers/dns/corenetworks/internal/client_test.go @@ -1,112 +1,34 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret") + client.baseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient("user", "secret") - client.baseURL, _ = url.Parse(server.URL) - client.HTTPClient = server.Client() - - return client, mux -} - -func testHandler(method string, statusCode int, filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf(`unsupported method: %s`, req.Method), http.StatusMethodNotAllowed) - return - } - - rw.WriteHeader(statusCode) - - if statusCode == http.StatusNoContent { - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, fmt.Sprintf(`message %v`, err), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, fmt.Sprintf(`message %v`, err), http.StatusInternalServerError) - return - } - } -} - -func testHandlerAuth(method string, statusCode int, filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf(`{"message":"unsupported method: %s"}`, req.Method), http.StatusMethodNotAllowed) - return - } - - rw.WriteHeader(statusCode) - - if statusCode == http.StatusNoContent { - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, fmt.Sprintf(`{"message":"%v"}`, err), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, fmt.Sprintf(`{"message":"%v"}`, err), http.StatusInternalServerError) - return - } - } -} - -func TestClient_CreateAuthenticationToken(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/auth/token", testHandlerAuth(http.MethodPost, http.StatusOK, "auth.json")) - - ctx := t.Context() - - token, err := client.CreateAuthenticationToken(ctx) - require.NoError(t, err) - - expected := &Token{ - Token: "authsecret", - Expires: 123, - } - assert.Equal(t, expected, token) + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(), + ) } func TestClient_ListZone(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/dnszones/", testHandler(http.MethodGet, http.StatusOK, "ListZone.json")) + client := mockBuilder(). + Route("GET /dnszones/", + servermock.ResponseFromFixture("ListZone.json")). + Build(t) ctx := t.Context() @@ -122,13 +44,12 @@ func TestClient_ListZone(t *testing.T) { } func TestClient_GetZoneDetails(t *testing.T) { - client, mux := setupTest(t) + client := mockBuilder(). + Route("GET /dnszones/example.com", + servermock.ResponseFromFixture("GetZoneDetails.json")). + Build(t) - mux.HandleFunc("/dnszones/example.com", testHandler(http.MethodGet, http.StatusOK, "GetZoneDetails.json")) - - ctx := t.Context() - - zone, err := client.GetZoneDetails(ctx, "example.com") + zone, err := client.GetZoneDetails(t.Context(), "example.com") require.NoError(t, err) expected := &ZoneDetails{ @@ -142,13 +63,12 @@ func TestClient_GetZoneDetails(t *testing.T) { } func TestClient_ListRecords(t *testing.T) { - client, mux := setupTest(t) + client := mockBuilder(). + Route("GET /dnszones/example.com/records/", + servermock.ResponseFromFixture("ListRecords.json")). + Build(t) - mux.HandleFunc("/dnszones/example.com/records/", testHandler(http.MethodGet, http.StatusOK, "ListRecords.json")) - - ctx := t.Context() - - records, err := client.ListRecords(ctx, "example.com") + records, err := client.ListRecords(t.Context(), "example.com") require.NoError(t, err) expected := []Record{ @@ -176,38 +96,35 @@ func TestClient_ListRecords(t *testing.T) { } func TestClient_AddRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/dnszones/example.com/records/", testHandler(http.MethodPost, http.StatusNoContent, "")) - - ctx := t.Context() + client := mockBuilder(). + Route("POST /dnszones/example.com/records/", + servermock.Noop().WithStatusCode(http.StatusNoContent)). + Build(t) record := Record{Name: "www", TTL: 3600, Type: "A", Data: "127.0.0.1"} - err := client.AddRecord(ctx, "example.com", record) + err := client.AddRecord(t.Context(), "example.com", record) require.NoError(t, err) } func TestClient_DeleteRecords(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/dnszones/example.com/records/delete", testHandler(http.MethodPost, http.StatusNoContent, "")) - - ctx := t.Context() + client := mockBuilder(). + Route("POST /dnszones/example.com/records/delete", + servermock.Noop().WithStatusCode(http.StatusNoContent)). + Build(t) record := Record{Name: "www", Type: "A", Data: "127.0.0.1"} - err := client.DeleteRecords(ctx, "example.com", record) + err := client.DeleteRecords(t.Context(), "example.com", record) require.NoError(t, err) } func TestClient_CommitRecords(t *testing.T) { - client, mux := setupTest(t) + client := mockBuilder(). + Route("POST /dnszones/example.com/records/commit", + servermock.Noop().WithStatusCode(http.StatusNoContent)). + Build(t) - mux.HandleFunc("/dnszones/example.com/records/commit", testHandler(http.MethodPost, http.StatusNoContent, "")) - - ctx := t.Context() - - err := client.CommitRecords(ctx, "example.com") + err := client.CommitRecords(t.Context(), "example.com") require.NoError(t, err) } diff --git a/providers/dns/corenetworks/internal/identity_test.go b/providers/dns/corenetworks/internal/identity_test.go new file mode 100644 index 000000000..b5e05ed3f --- /dev/null +++ b/providers/dns/corenetworks/internal/identity_test.go @@ -0,0 +1,24 @@ +package internal + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestClient_CreateAuthenticationToken(t *testing.T) { + client := mockBuilder(). + Route("POST /auth/token", servermock.ResponseFromFixture("auth.json")). + Build(t) + + token, err := client.CreateAuthenticationToken(t.Context()) + require.NoError(t, err) + + expected := &Token{ + Token: "authsecret", + Expires: 123, + } + assert.Equal(t, expected, token) +} diff --git a/providers/dns/cpanel/internal/cpanel/client_test.go b/providers/dns/cpanel/internal/cpanel/client_test.go index 78c45e82d..533d1130d 100644 --- a/providers/dns/cpanel/internal/cpanel/client_test.go +++ b/providers/dns/cpanel/internal/cpanel/client_test.go @@ -1,58 +1,38 @@ package cpanel import ( - "fmt" - "io" - "net/http" "net/http/httptest" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/go-acme/lego/v4/providers/dns/cpanel/internal/shared" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern, filename string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(server.URL, "user", "secret") + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client.HTTPClient = server.Client() - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - open, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(http.StatusOK) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client, err := NewClient(server.URL, "user", "secret") - require.NoError(t, err) - - client.HTTPClient = server.Client() - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("cpanel user:secret")) } func TestClient_FetchZoneInformation(t *testing.T) { - client := setupTest(t, "/execute/DNS/parse_zone", "zone-info.json") + client := mockBuilder(). + Route("GET /execute/DNS/parse_zone", + servermock.ResponseFromFixture("zone-info.json"), + servermock.CheckQueryParameter().Strict(). + With("zone", "example.com")). + Build(t) zoneInfo, err := client.FetchZoneInformation(t.Context(), "example.com") require.NoError(t, err) @@ -70,16 +50,27 @@ func TestClient_FetchZoneInformation(t *testing.T) { } func TestClient_FetchZoneInformation_error(t *testing.T) { - client := setupTest(t, "/execute/DNS/parse_zone", "zone-info_error.json") + client := mockBuilder(). + Route("GET /execute/DNS/parse_zone", + servermock.ResponseFromFixture("zone-info_error.json")). + Build(t) zoneInfo, err := client.FetchZoneInformation(t.Context(), "example.com") - require.Error(t, err) + require.EqualError(t, err, "error(0): You do not control a DNS zone named example.com.: a, b, c") assert.Nil(t, zoneInfo) } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, "/execute/DNS/mass_edit_zone", "update-zone.json") + client := mockBuilder(). + Route("GET /execute/DNS/mass_edit_zone", + servermock.ResponseFromFixture("update-zone.json"), + servermock.CheckQueryParameter().Strict(). + With("zone", "example.com"). + With("add", `{"dname":"example","ttl":14400,"record_type":"TXT","data":["string1","string2"]}`). + With("serial", "123456"). + With("zone", "example.com")). + Build(t) record := shared.Record{ DName: "example", @@ -97,7 +88,10 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, "/execute/DNS/mass_edit_zone", "update-zone_error.json") + client := mockBuilder(). + Route("GET /execute/DNS/mass_edit_zone", + servermock.ResponseFromFixture("update-zone_error.json")). + Build(t) record := shared.Record{ DName: "example", @@ -113,7 +107,14 @@ func TestClient_AddRecord_error(t *testing.T) { } func TestClient_EditRecord(t *testing.T) { - client := setupTest(t, "/execute/DNS/mass_edit_zone", "update-zone.json") + client := mockBuilder(). + Route("GET /execute/DNS/mass_edit_zone", + servermock.ResponseFromFixture("update-zone.json"), + servermock.CheckQueryParameter().Strict(). + With("edit", `{"dname":"example","ttl":14400,"record_type":"TXT","data":["string1","string2"],"line_index":9}`). + With("serial", "123456"). + With("zone", "example.com")). + Build(t) record := shared.Record{ LineIndex: 9, @@ -132,7 +133,10 @@ func TestClient_EditRecord(t *testing.T) { } func TestClient_EditRecord_error(t *testing.T) { - client := setupTest(t, "/execute/DNS/mass_edit_zone", "update-zone_error.json") + client := mockBuilder(). + Route("GET /execute/DNS/mass_edit_zone", + servermock.ResponseFromFixture("update-zone_error.json")). + Build(t) record := shared.Record{ LineIndex: 9, @@ -149,7 +153,14 @@ func TestClient_EditRecord_error(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, "/execute/DNS/mass_edit_zone", "update-zone.json") + client := mockBuilder(). + Route("GET /execute/DNS/mass_edit_zone", + servermock.ResponseFromFixture("update-zone.json"), + servermock.CheckQueryParameter().Strict(). + With("remove", "0"). + With("serial", "123456"). + With("zone", "example.com")). + Build(t) zoneSerial, err := client.DeleteRecord(t.Context(), 123456, "example.com", 0) require.NoError(t, err) @@ -160,7 +171,10 @@ func TestClient_DeleteRecord(t *testing.T) { } func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, "/execute/DNS/mass_edit_zone", "update-zone_error.json") + client := mockBuilder(). + Route("GET /execute/DNS/mass_edit_zone", + servermock.ResponseFromFixture("update-zone_error.json")). + Build(t) zoneSerial, err := client.DeleteRecord(t.Context(), 123456, "example.com", 0) require.Error(t, err) diff --git a/providers/dns/cpanel/internal/whm/client_test.go b/providers/dns/cpanel/internal/whm/client_test.go index 536417666..47686bf09 100644 --- a/providers/dns/cpanel/internal/whm/client_test.go +++ b/providers/dns/cpanel/internal/whm/client_test.go @@ -1,58 +1,39 @@ package whm import ( - "fmt" - "io" - "net/http" "net/http/httptest" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/go-acme/lego/v4/providers/dns/cpanel/internal/shared" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern, filename string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(server.URL, "user", "secret") + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client.HTTPClient = server.Client() - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - open, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(http.StatusOK) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client, err := NewClient(server.URL, "user", "secret") - require.NoError(t, err) - - client.HTTPClient = server.Client() - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("whm user:secret")) } func TestClient_FetchZoneInformation(t *testing.T) { - client := setupTest(t, "/json-api/parse_dns_zone", "zone-info.json") + client := mockBuilder(). + Route("GET /json-api/parse_dns_zone", + servermock.ResponseFromFixture("zone-info.json"), + servermock.CheckQueryParameter().Strict(). + With("api.version", "1"). + With("zone", "example.com")). + Build(t) zoneInfo, err := client.FetchZoneInformation(t.Context(), "example.com") require.NoError(t, err) @@ -70,7 +51,10 @@ func TestClient_FetchZoneInformation(t *testing.T) { } func TestClient_FetchZoneInformation_error(t *testing.T) { - client := setupTest(t, "/json-api/parse_dns_zone", "zone-info_error.json") + client := mockBuilder(). + Route("GET /json-api/parse_dns_zone", + servermock.ResponseFromFixture("zone-info_error.json")). + Build(t) zoneInfo, err := client.FetchZoneInformation(t.Context(), "example.com") require.Error(t, err) @@ -79,7 +63,15 @@ func TestClient_FetchZoneInformation_error(t *testing.T) { } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, "/json-api/mass_edit_dns_zone", "update-zone.json") + client := mockBuilder(). + Route("GET /json-api/mass_edit_dns_zone", + servermock.ResponseFromFixture("update-zone.json"), + servermock.CheckQueryParameter().Strict(). + With("add", `{"dname":"example","ttl":14400,"record_type":"TXT","data":["string1","string2"]}`). + With("api.version", "1"). + With("serial", "123456"). + With("zone", "example.com")). + Build(t) record := shared.Record{ DName: "example", @@ -97,7 +89,10 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, "/json-api/mass_edit_dns_zone", "update-zone_error.json") + client := mockBuilder(). + Route("GET /json-api/mass_edit_dns_zone", + servermock.ResponseFromFixture("update-zone_error.json")). + Build(t) record := shared.Record{ DName: "example", @@ -113,7 +108,15 @@ func TestClient_AddRecord_error(t *testing.T) { } func TestClient_EditRecord(t *testing.T) { - client := setupTest(t, "/json-api/mass_edit_dns_zone", "update-zone.json") + client := mockBuilder(). + Route("GET /json-api/mass_edit_dns_zone", + servermock.ResponseFromFixture("update-zone.json"), + servermock.CheckQueryParameter().Strict(). + With("edit", `{"dname":"example","ttl":14400,"record_type":"TXT","data":["string1","string2"],"line_index":9}`). + With("api.version", "1"). + With("serial", "123456"). + With("zone", "example.com")). + Build(t) record := shared.Record{ LineIndex: 9, @@ -132,7 +135,10 @@ func TestClient_EditRecord(t *testing.T) { } func TestClient_EditRecord_error(t *testing.T) { - client := setupTest(t, "/json-api/mass_edit_dns_zone", "update-zone_error.json") + client := mockBuilder(). + Route("GET /json-api/mass_edit_dns_zone", + servermock.ResponseFromFixture("update-zone_error.json")). + Build(t) record := shared.Record{ LineIndex: 9, @@ -149,7 +155,15 @@ func TestClient_EditRecord_error(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, "/json-api/mass_edit_dns_zone", "update-zone.json") + client := mockBuilder(). + Route("GET /json-api/mass_edit_dns_zone", + servermock.ResponseFromFixture("update-zone.json"), + servermock.CheckQueryParameter().Strict(). + With("remove", "0"). + With("api.version", "1"). + With("serial", "123456"). + With("zone", "example.com")). + Build(t) zoneSerial, err := client.DeleteRecord(t.Context(), 123456, "example.com", 0) require.NoError(t, err) @@ -160,7 +174,10 @@ func TestClient_DeleteRecord(t *testing.T) { } func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, "/json-api/mass_edit_dns_zone", "update-zone_error.json") + client := mockBuilder(). + Route("GET /json-api/mass_edit_dns_zone", + servermock.ResponseFromFixture("update-zone_error.json")). + Build(t) zoneSerial, err := client.DeleteRecord(t.Context(), 123456, "example.com", 0) require.Error(t, err) diff --git a/providers/dns/derak/internal/client_test.go b/providers/dns/derak/internal/client_test.go index 20dea0015..322a7f48c 100644 --- a/providers/dns/derak/internal/client_test.go +++ b/providers/dns/derak/internal/client_test.go @@ -1,80 +1,37 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" "time" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - +func setupClient(server *httptest.Server) (*Client, error) { client := NewClient("secret") client.baseURL, _ = url.Parse(server.URL) client.zoneEndpoint = server.URL client.HTTPClient = server.Client() - return client, mux + return client, nil } -func testHandler(method string, statusCode int, filename string) func(rw http.ResponseWriter, req *http.Request) { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - username, password, ok := req.BasicAuth() - if !ok { - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - if username != "api" { - http.Error(rw, fmt.Sprintf("username: want %s got %s", username, "user"), http.StatusUnauthorized) - return - } - - if password != "secret" { - http.Error(rw, fmt.Sprintf("password: want %s got %s", password, "secret"), http.StatusUnauthorized) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - rw.WriteHeader(statusCode) - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader().WithJSONHeaders(). + WithBasicAuth("api", "secret")) } func TestGetRecords(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords", - testHandler(http.MethodGet, http.StatusOK, "records-GET.json")) + client := mockBuilder(). + Route("GET /zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords", + servermock.ResponseFromFixture("records-GET.json")). + Build(t) records, err := client.GetRecords(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", &GetRecordsParameters{DNSType: "TXT", Content: `"test"'`}) require.NoError(t, err) @@ -134,20 +91,21 @@ func TestGetRecords(t *testing.T) { } func TestGetRecords_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords", - testHandler(http.MethodGet, http.StatusUnauthorized, "error.json")) + client := mockBuilder(). + Route("GET /zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.GetRecords(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", &GetRecordsParameters{DNSType: "TXT", Content: `"test"'`}) require.Error(t, err) } func TestGetRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/812bee17a0b440b0bd5ee099a78b839c", - testHandler(http.MethodGet, http.StatusOK, "record-GET.json")) + client := mockBuilder(). + Route("GET /zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/812bee17a0b440b0bd5ee099a78b839c", + servermock.ResponseFromFixture("record-GET.json")). + Build(t) record, err := client.GetRecord(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", "812bee17a0b440b0bd5ee099a78b839c") require.NoError(t, err) @@ -163,20 +121,22 @@ func TestGetRecord(t *testing.T) { } func TestGetRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/812bee17a0b440b0bd5ee099a78b839c", - testHandler(http.MethodGet, http.StatusUnauthorized, "error.json")) + client := mockBuilder(). + Route("GET /zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.GetRecord(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", "812bee17a0b440b0bd5ee099a78b839c") require.Error(t, err) } func TestCreateRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords", - testHandler(http.MethodPut, http.StatusCreated, "record-PUT.json")) + client := mockBuilder(). + Route("PUT /zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords", + servermock.ResponseFromFixture("record-PUT.json"). + WithStatusCode(http.StatusCreated)). + Build(t) r := Record{ Type: "TXT", @@ -199,10 +159,11 @@ func TestCreateRecord(t *testing.T) { } func TestCreateRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords", - testHandler(http.MethodPut, http.StatusUnauthorized, "error.json")) + client := mockBuilder(). + Route("PUT /zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) r := Record{ Type: "TXT", @@ -216,10 +177,10 @@ func TestCreateRecord_error(t *testing.T) { } func TestEditRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/eebc813de2f94d67b09d91e10e2d65c2", - testHandler(http.MethodPatch, http.StatusOK, "record-PATCH.json")) + client := mockBuilder(). + Route("PATCH /zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/eebc813de2f94d67b09d91e10e2d65c2", + servermock.ResponseFromFixture("record-PATCH.json")). + Build(t) record, err := client.EditRecord(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", "eebc813de2f94d67b09d91e10e2d65c2", Record{ Content: "foo", @@ -237,10 +198,11 @@ func TestEditRecord(t *testing.T) { } func TestEditRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/eebc813de2f94d67b09d91e10e2d65c2", - testHandler(http.MethodPatch, http.StatusUnauthorized, "error.json")) + client := mockBuilder(). + Route("PATCH /zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/eebc813de2f94d67b09d91e10e2d65c2", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.EditRecord(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", "eebc813de2f94d67b09d91e10e2d65c2", Record{ Content: "foo", @@ -249,29 +211,33 @@ func TestEditRecord_error(t *testing.T) { } func TestDeleteRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/653464211b7447a1bee6b8fcb9fb86df", - testHandler(http.MethodDelete, http.StatusOK, "record-DELETE.json")) + client := mockBuilder(). + Route("DELETE /zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/653464211b7447a1bee6b8fcb9fb86df", + servermock.ResponseFromFixture("record-DELETE.json")). + Build(t) err := client.DeleteRecord(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", "653464211b7447a1bee6b8fcb9fb86df") require.NoError(t, err) } func TestDeleteRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/653464211b7447a1bee6b8fcb9fb86df", - testHandler(http.MethodDelete, http.StatusUnauthorized, "error.json")) + client := mockBuilder(). + Route("DELETE /zones/47c0ecf6c91243308c649ad1d2d618dd/dnsrecords/653464211b7447a1bee6b8fcb9fb86df", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) err := client.DeleteRecord(t.Context(), "47c0ecf6c91243308c649ad1d2d618dd", "653464211b7447a1bee6b8fcb9fb86df") require.Error(t, err) } func TestGetZones(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/", testHandler(http.MethodGet, http.StatusOK, "service-cdn-zones.json")) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader(). + WithBasicAuth("api", "secret"), + ). + Route("GET /", servermock.ResponseFromFixture("service-cdn-zones.json")). + Build(t) zones, err := client.GetZones(t.Context()) require.NoError(t, err) @@ -302,9 +268,10 @@ func TestGetZones(t *testing.T) { } func TestGetZones_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/", testHandler(http.MethodGet, http.StatusUnauthorized, "error.json")) + client := mockBuilder(). + Route("GET /", servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.GetZones(t.Context()) require.Error(t, err) diff --git a/providers/dns/digitalocean/digitalocean_test.go b/providers/dns/digitalocean/digitalocean_test.go index bfd2d68c0..a01906812 100644 --- a/providers/dns/digitalocean/digitalocean_test.go +++ b/providers/dns/digitalocean/digitalocean_test.go @@ -1,36 +1,30 @@ package digitalocean import ( - "bytes" - "fmt" - "io" "net/http" "net/http/httptest" "testing" "github.com/go-acme/lego/v4/platform/tester" - "github.com/stretchr/testify/assert" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) var envTest = tester.NewEnvTest(EnvAuthToken) -func setupTest(t *testing.T) (*DNSProvider, *http.ServeMux) { - t.Helper() +func mockProvider() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.AuthToken = "asdf1234" + config.BaseURL = server.URL + config.HTTPClient = server.Client() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - config := NewDefaultConfig() - config.AuthToken = "asdf1234" - config.BaseURL = server.URL - config.HTTPClient = server.Client() - - provider, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - return provider, mux + return NewDNSProviderConfig(config) + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With("Authorization", "Bearer asdf1234")) } func TestNewDNSProvider(t *testing.T) { @@ -111,26 +105,9 @@ func TestNewDNSProviderConfig(t *testing.T) { } func TestDNSProvider_Present(t *testing.T) { - provider, mux := setupTest(t) - - mux.HandleFunc("/v2/domains/example.com/records", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodPost, r.Method, "method") - - assert.Equal(t, "application/json", r.Header.Get("Accept"), "Accept") - assert.Equal(t, "application/json", r.Header.Get("Content-Type"), "Content-Type") - assert.Equal(t, "Bearer asdf1234", r.Header.Get("Authorization"), "Authorization") - - reqBody, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - expectedReqBody := `{"type":"TXT","name":"_acme-challenge.example.com.","data":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI","ttl":30}` - assert.Equal(t, expectedReqBody, string(bytes.TrimSpace(reqBody))) - - w.WriteHeader(http.StatusCreated) - _, err = fmt.Fprintf(w, `{ + provider := mockProvider(). + Route("POST /v2/domains/example.com/records", + servermock.RawStringResponse(`{ "domain_record": { "id": 1234567, "type": "TXT", @@ -140,31 +117,21 @@ func TestDNSProvider_Present(t *testing.T) { "port": null, "weight": null } - }`) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + }`). + WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBody(`{"type":"TXT","name":"_acme-challenge.example.com.","data":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI","ttl":30}`)). + Build(t) err := provider.Present("example.com", "", "foobar") require.NoError(t, err) } func TestDNSProvider_CleanUp(t *testing.T) { - provider, mux := setupTest(t) - - mux.HandleFunc("/v2/domains/example.com/records/1234567", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodDelete, r.Method, "method") - - assert.Equal(t, "/v2/domains/example.com/records/1234567", r.URL.Path, "Path") - - assert.Equal(t, "application/json", r.Header.Get("Accept"), "Accept") - assert.Equal(t, "application/json", r.Header.Get("Content-Type"), "Content-Type") - assert.Equal(t, "Bearer asdf1234", r.Header.Get("Authorization"), "Authorization") - - w.WriteHeader(http.StatusNoContent) - }) + provider := mockProvider(). + Route("DELETE /v2/domains/example.com/records/1234567", + servermock.Noop(). + WithStatusCode(http.StatusNoContent)). + Build(t) provider.recordIDsMu.Lock() provider.recordIDs["token"] = 1234567 diff --git a/providers/dns/digitalocean/internal/client_test.go b/providers/dns/digitalocean/internal/client_test.go index 171601438..65ce5dfaa 100644 --- a/providers/dns/digitalocean/internal/client_test.go +++ b/providers/dns/digitalocean/internal/client_test.go @@ -1,94 +1,35 @@ package internal import ( - "bytes" - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, handler http.HandlerFunc) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(OAuthStaticAccessToken(server.Client(), "secret")) + client.BaseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient(OAuthStaticAccessToken(server.Client(), "secret")) - client.BaseURL, _ = url.Parse(server.URL) - - mux.HandleFunc(pattern, handler) - - return client -} - -func checkHeader(req *http.Request, name, value string) error { - val := req.Header.Get(name) - if val != value { - return fmt.Errorf("invalid header value, got: %s want %s", val, value) - } - return nil -} - -func writeFixture(rw http.ResponseWriter, filename string) { - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, _ = io.Copy(rw, file) + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Bearer secret")) } func TestClient_AddTxtRecord(t *testing.T) { - client := setupTest(t, "/v2/domains/example.com/records", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - err := checkHeader(req, "Accept", "application/json") - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - err = checkHeader(req, "Content-Type", "application/json") - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - err = checkHeader(req, "Authorization", "Bearer secret") - if err != nil { - http.Error(rw, err.Error(), http.StatusUnauthorized) - return - } - - reqBody, err := io.ReadAll(req.Body) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - expectedReqBody := `{"type":"TXT","name":"_acme-challenge.example.com.","data":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI","ttl":30}` - if expectedReqBody != string(bytes.TrimSpace(reqBody)) { - http.Error(rw, fmt.Sprintf("unexpected request body: %s", string(bytes.TrimSpace(reqBody))), http.StatusBadRequest) - return - } - - rw.WriteHeader(http.StatusCreated) - writeFixture(rw, "domains-records_POST.json") - }) + client := mockBuilder(). + Route("POST /v2/domains/example.com/records", + servermock.ResponseFromFixture("domains-records_POST.json"). + WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBody(`{"type":"TXT","name":"_acme-challenge.example.com.","data":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI","ttl":30}`)). + Build(t) record := Record{ Type: "TXT", @@ -112,26 +53,11 @@ func TestClient_AddTxtRecord(t *testing.T) { } func TestClient_RemoveTxtRecord(t *testing.T) { - client := setupTest(t, "/v2/domains/example.com/records/1234567", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - err := checkHeader(req, "Accept", "application/json") - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - err = checkHeader(req, "Authorization", "Bearer secret") - if err != nil { - http.Error(rw, err.Error(), http.StatusUnauthorized) - return - } - - rw.WriteHeader(http.StatusNoContent) - }) + client := mockBuilder(). + Route("DELETE /v2/domains/example.com/records/1234567", + servermock.ResponseFromFixture("domains-records_POST.json"). + WithStatusCode(http.StatusNoContent)). + Build(t) err := client.RemoveTxtRecord(t.Context(), "example.com", 1234567) require.NoError(t, err) diff --git a/providers/dns/directadmin/internal/client_test.go b/providers/dns/directadmin/internal/client_test.go index 6da73da65..759a7fb4e 100644 --- a/providers/dns/directadmin/internal/client_test.go +++ b/providers/dns/directadmin/internal/client_test.go @@ -1,88 +1,48 @@ package internal import ( - "encoding/json" "fmt" - "io" "net/http" "net/http/httptest" - "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, _ := NewClient(server.URL, "user", "secret") + client.HTTPClient = server.Client() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client, _ := NewClient(server.URL, "user", "secret") - client.HTTPClient = server.Client() - - return client, mux + return client, nil + }, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded()) } -func newJSONErrorf(reason string, a ...any) string { - err := APIError{ +func newAPIError(reason string, a ...any) APIError { + return APIError{ Message: "Cannot View Dns Record", Result: fmt.Sprintf(reason, a...), } - - data, _ := json.Marshal(err) - - return string(data) -} - -func testHandler(kv map[string]string) func(rw http.ResponseWriter, req *http.Request) { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - domain := req.URL.Query().Get("domain") - if domain != "example.com" { - http.Error(rw, newJSONErrorf("invalid domain: %s", domain), http.StatusUnauthorized) - return - } - - data, err := io.ReadAll(req.Body) - if err != nil { - http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } - - values, err := url.ParseQuery(string(data)) - if err != nil { - http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return - } - - for k, v := range kv { - actual := values.Get(k) - if v != actual { - http.Error(rw, newJSONErrorf("invalid %q: %s", k, actual), http.StatusBadRequest) - return - } - } - } } func TestClient_SetRecord(t *testing.T) { - client, mux := setupTest(t) - - kv := map[string]string{ - "action": "add", - "name": "foo", - "type": "TXT", - "value": "txtTXTtxt", - "ttl": "123", - } - - mux.HandleFunc("/CMD_API_DNS_CONTROL", testHandler(kv)) + client := mockBuilder(). + Route("POST /CMD_API_DNS_CONTROL", nil, + servermock.CheckQueryParameter().Strict(). + With("domain", "example.com"). + With("json", "yes"), + servermock.CheckForm().UsePostForm().Strict(). + With("action", "add"). + With("name", "foo"). + With("type", "TXT"). + With("value", "txtTXTtxt"). + With("ttl", "123"), + ). + Build(t) record := Record{ Name: "foo", @@ -96,11 +56,11 @@ func TestClient_SetRecord(t *testing.T) { } func TestClient_SetRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/CMD_API_DNS_CONTROL", func(rw http.ResponseWriter, req *http.Request) { - http.Error(rw, newJSONErrorf("OOPS"), http.StatusInternalServerError) - }) + client := mockBuilder(). + Route("POST /CMD_API_DNS_CONTROL", + servermock.JSONEncode(newAPIError("OOPS")). + WithStatusCode(http.StatusInternalServerError)). + Build(t) record := Record{ Name: "foo", @@ -114,17 +74,18 @@ func TestClient_SetRecord_error(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client, mux := setupTest(t) - - kv := map[string]string{ - "action": "delete", - "name": "foo", - "type": "TXT", - "value": "txtTXTtxt", - "ttl": "", - } - - mux.HandleFunc("/CMD_API_DNS_CONTROL", testHandler(kv)) + client := mockBuilder(). + Route("POST /CMD_API_DNS_CONTROL", nil, + servermock.CheckQueryParameter().Strict(). + With("domain", "example.com"). + With("json", "yes"), + servermock.CheckForm().UsePostForm().Strict(). + With("action", "delete"). + With("name", "foo"). + With("type", "TXT"). + With("value", "txtTXTtxt"), + ). + Build(t) record := Record{ Name: "foo", @@ -137,11 +98,11 @@ func TestClient_DeleteRecord(t *testing.T) { } func TestClient_DeleteRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/CMD_API_DNS_CONTROL", func(rw http.ResponseWriter, req *http.Request) { - http.Error(rw, newJSONErrorf("OOPS"), http.StatusInternalServerError) - }) + client := mockBuilder(). + Route("POST /CMD_API_DNS_CONTROL", + servermock.JSONEncode(newAPIError("OOPS")). + WithStatusCode(http.StatusInternalServerError)). + Build(t) record := Record{ Name: "foo", diff --git a/providers/dns/dnshomede/internal/client_test.go b/providers/dns/dnshomede/internal/client_test.go index 710e2c72e..6e1593fe7 100644 --- a/providers/dns/dnshomede/internal/client_test.go +++ b/providers/dns/dnshomede/internal/client_test.go @@ -2,33 +2,32 @@ package internal import ( "fmt" - "net/http" "net/http/httptest" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, credentials map[string]string, handler http.HandlerFunc) *Client { - t.Helper() +func setupClient(credentials map[string]string) func(server *httptest.Server) (*Client, error) { + return func(server *httptest.Server) (*Client, error) { + client := NewClient(credentials) + client.HTTPClient = server.Client() + client.baseURL = server.URL - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/", handler) - - client := NewClient(credentials) - client.HTTPClient = server.Client() - client.baseURL = server.URL - - return client + return client, nil + } } func TestClient_Add(t *testing.T) { txtValue := "123456789012" - client := setupTest(t, map[string]string{"example.org": "secret"}, handlerMock(addAction, txtValue)) + client := servermock.NewBuilder[*Client](setupClient(map[string]string{"example.org": "secret"})). + Route("POST /", + servermock.RawStringResponse(fmt.Sprintf("%s %s", successCode, txtValue)), + servermock.CheckQueryParameter().Strict(). + With("acme", addAction).With("txt", txtValue)). + Build(t) err := client.Add(t.Context(), "example.org", txtValue) require.NoError(t, err) @@ -37,16 +36,27 @@ func TestClient_Add(t *testing.T) { func TestClient_Add_error(t *testing.T) { txtValue := "123456789012" - client := setupTest(t, map[string]string{"example.com": "secret"}, handlerMock(addAction, txtValue)) + client := servermock.NewBuilder[*Client](setupClient(map[string]string{"example.com": "secret"})). + Route("POST /", + servermock.RawStringResponse(fmt.Sprintf("%s %s", successCode, txtValue)), + servermock.CheckQueryParameter().Strict(). + With("acme", addAction).With("txt", txtValue)). + Build(t) err := client.Add(t.Context(), "example.org", txtValue) - require.Error(t, err) + + require.EqualError(t, err, "domain example.org not found in credentials, check your credentials map") } func TestClient_Remove(t *testing.T) { txtValue := "ABCDEFGHIJKL" - client := setupTest(t, map[string]string{"example.org": "secret"}, handlerMock(removeAction, txtValue)) + client := servermock.NewBuilder[*Client](setupClient(map[string]string{"example.org": "secret"})). + Route("POST /", + servermock.RawStringResponse(fmt.Sprintf("%s %s", successCode, txtValue)), + servermock.CheckQueryParameter().Strict(). + With("acme", removeAction).With("txt", txtValue)). + Build(t) err := client.Remove(t.Context(), "example.org", txtValue) require.NoError(t, err) @@ -55,34 +65,45 @@ func TestClient_Remove(t *testing.T) { func TestClient_Remove_error(t *testing.T) { txtValue := "ABCDEFGHIJKL" - client := setupTest(t, map[string]string{"example.com": "secret"}, handlerMock(removeAction, txtValue)) + testCases := []struct { + desc string + hostname string + response string + expected string + }{ + { + desc: "response error - txt", + hostname: "example.com", + response: "error - no valid acme txt record", + expected: "error - no valid acme txt record", + }, + { + desc: "response error - acme", + hostname: "example.com", + response: "nochg 1234:1234:1234:1234:1234:1234:1234:1234", + expected: "nochg 1234:1234:1234:1234:1234:1234:1234:1234", + }, + { + desc: "credential error", + hostname: "example.org", + response: fmt.Sprintf("%s %s", successCode, txtValue), + expected: "domain example.org not found in credentials, check your credentials map", + }, + } - err := client.Remove(t.Context(), "example.org", txtValue) - require.Error(t, err) -} + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() -func handlerMock(action, value string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - rw.WriteHeader(http.StatusOK) + client := servermock.NewBuilder[*Client](setupClient(map[string]string{"example.com": "secret"})). + Route("POST /", + servermock.RawStringResponse(test.response), + servermock.CheckQueryParameter().Strict(). + With("acme", removeAction).With("txt", txtValue)). + Build(t) - query := req.URL.Query() - - if query.Get("acme") != action { - _, _ = rw.Write([]byte("nochg 1234:1234:1234:1234:1234:1234:1234:1234")) - return - } - - txtValue := query.Get("txt") - if len(txtValue) < 12 { - _, _ = rw.Write([]byte("error - no valid acme txt record")) - return - } - - if txtValue != value { - http.Error(rw, fmt.Sprintf("got: %q, expected: %q", txtValue, value), http.StatusBadRequest) - return - } - - _, _ = fmt.Fprintf(rw, "%s %s", successCode, txtValue) + err := client.Remove(t.Context(), test.hostname, txtValue) + require.EqualError(t, err, test.expected) + }) } } diff --git a/providers/dns/dnshomede/internal/readme.md b/providers/dns/dnshomede/internal/readme.md index 014b062a1..622c4354d 100644 --- a/providers/dns/dnshomede/internal/readme.md +++ b/providers/dns/dnshomede/internal/readme.md @@ -16,7 +16,7 @@ Always returns StatusOK (200) If the API call works the first word of the response body is `successfully`. -If an error encoured the response body is `error - `. +If an error occurs the response body is `error - `. Can be a POST or a GET. @@ -35,6 +35,6 @@ Always returns StatusOK (200) If the API call works the first word of the response body is `successfully`. -If an error encoured the response body is `error - `. +If an error occurs the response body is `error - `. Can be a POST or a GET. diff --git a/providers/dns/dnsmadeeasy/internal/client.go b/providers/dns/dnsmadeeasy/internal/client.go index 491d5fd98..cb6f9d2cb 100644 --- a/providers/dns/dnsmadeeasy/internal/client.go +++ b/providers/dns/dnsmadeeasy/internal/client.go @@ -15,6 +15,7 @@ import ( "strconv" "time" + "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) @@ -57,10 +58,8 @@ func NewClient(apiKey, apiSecret string) (*Client, error) { func (c *Client) GetDomain(ctx context.Context, authZone string) (*Domain, error) { endpoint := c.BaseURL.JoinPath("dns", "managed", "name") - domainName := authZone[0 : len(authZone)-1] - query := endpoint.Query() - query.Set("domainname", domainName) + query.Set("domainname", dns01.UnFqdn(authZone)) endpoint.RawQuery = query.Encode() req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) diff --git a/providers/dns/dnsmadeeasy/internal/client_test.go b/providers/dns/dnsmadeeasy/internal/client_test.go index 721214693..f302c8d9b 100644 --- a/providers/dns/dnsmadeeasy/internal/client_test.go +++ b/providers/dns/dnsmadeeasy/internal/client_test.go @@ -2,14 +2,132 @@ package internal import ( "net/http" + "net/http/httptest" + "net/url" "testing" "time" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func Test_sign(t *testing.T) { +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("key", "secret") + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + With("x-dnsme-apiKey", "key"). + WithRegexp("x-dnsme-requestDate", `\w+, \d+ \w+ \d+ \d+:\d+:\d+ UTC`). + WithRegexp("x-dnsme-hmac", `[a-z0-9]+`), + ) +} + +func TestClient_GetDomain(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/managed/name", + servermock.RawStringResponse(`{"id": 1, "name": "foo"}`), + servermock.CheckQueryParameter().Strict(). + With("domainname", "example.com")). + Build(t) + + domain, err := client.GetDomain(t.Context(), "example.com.") + require.NoError(t, err) + + expected := &Domain{ + ID: 1, + Name: "foo", + } + + assert.Equal(t, expected, domain) +} + +func TestClient_GetRecords(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/managed/1/records", + servermock.ResponseFromFixture("get_records.json"), + servermock.CheckQueryParameter().Strict(). + With("recordName", "foo"). + With("type", "TXT"), + ). + Build(t) + + domain := &Domain{ID: 1, Name: "foo"} + + records, err := client.GetRecords(t.Context(), domain, "foo", "TXT") + require.NoError(t, err) + + expected := []Record{ + { + ID: 1, + Type: "TXT", + Name: "foo", + Value: "aaa", + TTL: 60, + SourceID: 123, + }, + { + ID: 2, + Type: "TXT", + Name: "bar", + Value: "bbb", + TTL: 120, + SourceID: 456, + }, + } + + assert.Equal(t, &expected, records) +} + +func TestClient_CreateRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /dns/managed/1/records", nil, + servermock.CheckRequestJSONBodyFromFile("create_record-request.json")). + Build(t) + + domain := &Domain{ID: 1, Name: "foo"} + + record := &Record{ + ID: 1, + Type: "TXT", + Name: "foo", + Value: "aaa", + TTL: 60, + SourceID: 123, + } + + err := client.CreateRecord(t.Context(), domain, record) + require.NoError(t, err) +} + +func TestClient_DeleteRecord(t *testing.T) { + client := mockBuilder(). + Route("DELETE /dns/managed/123/records/1", nil). + Build(t) + + record := Record{ + ID: 1, + Type: "TXT", + Name: "foo", + Value: "aaa", + TTL: 60, + SourceID: 123, + } + + err := client.DeleteRecord(t.Context(), record) + require.NoError(t, err) +} + +func TestClient_sign(t *testing.T) { apiKey := "key" client := Client{apiKey: apiKey, apiSecret: "secret"} diff --git a/providers/dns/dnsmadeeasy/internal/fixtures/create_record-request.json b/providers/dns/dnsmadeeasy/internal/fixtures/create_record-request.json new file mode 100644 index 000000000..9a08b6544 --- /dev/null +++ b/providers/dns/dnsmadeeasy/internal/fixtures/create_record-request.json @@ -0,0 +1,8 @@ +{ + "id": 1, + "type": "TXT", + "name": "foo", + "value": "aaa", + "ttl": 60, + "sourceId": 123 +} diff --git a/providers/dns/dnsmadeeasy/internal/fixtures/get_records.json b/providers/dns/dnsmadeeasy/internal/fixtures/get_records.json new file mode 100644 index 000000000..5667e5e1d --- /dev/null +++ b/providers/dns/dnsmadeeasy/internal/fixtures/get_records.json @@ -0,0 +1,20 @@ +{ + "data": [ + { + "id": 1, + "type": "TXT", + "name": "foo", + "value": "aaa", + "ttl": 60, + "sourceId": 123 + }, + { + "id": 2, + "type": "TXT", + "name": "bar", + "value": "bbb", + "ttl": 120, + "sourceId": 456 + } + ] +} diff --git a/providers/dns/dode/internal/client_test.go b/providers/dns/dode/internal/client_test.go index 139a0939a..6fbaa8c1d 100644 --- a/providers/dns/dode/internal/client_test.go +++ b/providers/dns/dode/internal/client_test.go @@ -1,91 +1,43 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, method, pattern string, status int, file string) *Client { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusBadRequest) - return - } - - query := req.URL.Query() - if query.Get("token") != "secret" { - http.Error(rw, fmt.Sprintf("invalid credentials: %q", query.Get("token")), http.StatusUnauthorized) - return - } - - if query.Get("domain") != "example.com" { - http.Error(rw, fmt.Sprintf("invalid domain: %q", query.Get("domain")), http.StatusBadRequest) - return - } - - if query.Has("action") { - if query.Get("action") != "delete" { - http.Error(rw, fmt.Sprintf("invalid action: %q", query.Get("action")), http.StatusBadRequest) - return - } - } else { - if query.Get("value") != "value" { - http.Error(rw, fmt.Sprintf("invalid value: %q", query.Get("value")), http.StatusBadRequest) - return - } - } - - if file == "" { - rw.WriteHeader(status) - return - } - - open, err := os.Open(filepath.Join("fixtures", file)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - +func setupClient(server *httptest.Server) (*Client, error) { client := NewClient("secret") client.HTTPClient = server.Client() client.baseURL, _ = url.Parse(server.URL) - return client + return client, nil } func TestClient_UpdateTxtRecord(t *testing.T) { - client := setupTest(t, http.MethodGet, "/letsencrypt", http.StatusOK, "success.json") + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /letsencrypt", servermock.ResponseFromFixture("success.json"), + servermock.CheckQueryParameter().Strict(). + With("domain", "example.com"). + With("token", "secret"). + With("value", "value")). + Build(t) err := client.UpdateTxtRecord(t.Context(), "example.com.", "value", false) require.NoError(t, err) } func TestClient_UpdateTxtRecord_clear(t *testing.T) { - client := setupTest(t, http.MethodGet, "/letsencrypt", http.StatusOK, "success.json") + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /letsencrypt", servermock.ResponseFromFixture("success.json"), + servermock.CheckQueryParameter().Strict(). + With("action", "delete"). + With("domain", "example.com"). + With("token", "secret")). + Build(t) err := client.UpdateTxtRecord(t.Context(), "example.com.", "value", true) require.NoError(t, err) diff --git a/providers/dns/domeneshop/internal/client_test.go b/providers/dns/domeneshop/internal/client_test.go index 1f4265d03..beddc1cb2 100644 --- a/providers/dns/domeneshop/internal/client_test.go +++ b/providers/dns/domeneshop/internal/client_test.go @@ -1,121 +1,56 @@ package internal import ( - "net/http" "net/http/httptest" "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -const authorizationHeader = "Authorization" +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("token", "secret") + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient("token", "secret") - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client, mux + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithBasicAuth("token", "secret"), + ) } func TestClient_CreateTXTRecord(t *testing.T) { - client, mux := setupTest(t) + client := mockBuilder(). + Route("POST /domains/1/dns", + servermock.ResponseFromFixture("create_record.json"), + servermock.CheckRequestJSONBodyFromFile("create_record-request.json")). + Build(t) - mux.HandleFunc("/domains/1/dns", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - auth := req.Header.Get(authorizationHeader) - if auth != "Basic dG9rZW46c2VjcmV0" { - http.Error(rw, "invalid credentials: "+auth, http.StatusUnauthorized) - return - } - - _, _ = rw.Write([]byte(`{"id": 1}`)) - }) - - err := client.CreateTXTRecord(t.Context(), &Domain{ID: 1}, "example", "txtTXTtxt") + err := client.CreateTXTRecord(t.Context(), &Domain{ID: 1}, "example.com", "txtTXTtxt") require.NoError(t, err) } func TestClient_DeleteTXTRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/1/dns", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - auth := req.Header.Get(authorizationHeader) - if auth != "Basic dG9rZW46c2VjcmV0" { - http.Error(rw, "invalid credentials: "+auth, http.StatusUnauthorized) - return - } - - _, _ = rw.Write([]byte(`[ - { - "id": 1, - "host": "example.com", - "ttl": 3600, - "type": "TXT", - "data": "txtTXTtxt" - } -]`)) - }) - - mux.HandleFunc("/domains/1/dns/1", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - auth := req.Header.Get(authorizationHeader) - if auth != "Basic dG9rZW46c2VjcmV0" { - http.Error(rw, "invalid credentials: "+auth, http.StatusUnauthorized) - return - } - }) + client := mockBuilder(). + Route("GET /domains/1/dns", + servermock.ResponseFromFixture("delete_record.json")). + Route("DELETE /domains/1/dns/1", nil). + Build(t) err := client.DeleteTXTRecord(t.Context(), &Domain{ID: 1}, "example.com", "txtTXTtxt") require.NoError(t, err) } func TestClient_getDNSRecordByHostData(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/1/dns", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - auth := req.Header.Get(authorizationHeader) - if auth != "Basic dG9rZW46c2VjcmV0" { - http.Error(rw, "invalid credentials: "+auth, http.StatusUnauthorized) - return - } - - _, _ = rw.Write([]byte(`[ - { - "id": 1, - "host": "example.com", - "ttl": 3600, - "type": "TXT", - "data": "txtTXTtxt" - } -]`)) - }) + client := mockBuilder(). + Route("GET /domains/1/dns", + servermock.ResponseFromFixture("getDnsRecords.json")). + Build(t) record, err := client.getDNSRecordByHostData(t.Context(), Domain{ID: 1}, "example.com", "txtTXTtxt") require.NoError(t, err) @@ -132,43 +67,10 @@ func TestClient_getDNSRecordByHostData(t *testing.T) { } func TestClient_GetDomainByName(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - auth := req.Header.Get(authorizationHeader) - if auth != "Basic dG9rZW46c2VjcmV0" { - http.Error(rw, "invalid credentials: "+auth, http.StatusUnauthorized) - return - } - - _, _ = rw.Write([]byte(`[ - { - "id": 1, - "domain": "example.com", - "expiry_date": "2019-08-24", - "registered_date": "2019-08-24", - "renew": true, - "registrant": "Ola Nordmann", - "status": "active", - "nameservers": [ - "ns1.hyp.net", - "ns2.hyp.net", - "ns3.hyp.net" - ], - "services": { - "registrar": true, - "dns": true, - "email": true, - "webhotel": "none" - } - } -]`)) - }) + client := mockBuilder(). + Route("GET /domains/", + servermock.ResponseFromFixture("getDomains.json")). + Build(t) domain, err := client.GetDomainByName(t.Context(), "example.com") require.NoError(t, err) diff --git a/providers/dns/domeneshop/internal/fixtures/create_record-request.json b/providers/dns/domeneshop/internal/fixtures/create_record-request.json new file mode 100644 index 000000000..6bd3ca4ce --- /dev/null +++ b/providers/dns/domeneshop/internal/fixtures/create_record-request.json @@ -0,0 +1,7 @@ +{ + "data": "txtTXTtxt", + "host": "example.com", + "id": 0, + "ttl": 300, + "type": "TXT" +} diff --git a/providers/dns/domeneshop/internal/fixtures/create_record.json b/providers/dns/domeneshop/internal/fixtures/create_record.json new file mode 100644 index 000000000..2572ae5fe --- /dev/null +++ b/providers/dns/domeneshop/internal/fixtures/create_record.json @@ -0,0 +1,3 @@ +{ + "id": 1 +} diff --git a/providers/dns/domeneshop/internal/fixtures/delete_record.json b/providers/dns/domeneshop/internal/fixtures/delete_record.json new file mode 100644 index 000000000..f3f987eef --- /dev/null +++ b/providers/dns/domeneshop/internal/fixtures/delete_record.json @@ -0,0 +1,9 @@ +[ + { + "id": 1, + "host": "example.com", + "ttl": 3600, + "type": "TXT", + "data": "txtTXTtxt" + } +] diff --git a/providers/dns/domeneshop/internal/fixtures/getDnsRecords.json b/providers/dns/domeneshop/internal/fixtures/getDnsRecords.json new file mode 100644 index 000000000..f3f987eef --- /dev/null +++ b/providers/dns/domeneshop/internal/fixtures/getDnsRecords.json @@ -0,0 +1,9 @@ +[ + { + "id": 1, + "host": "example.com", + "ttl": 3600, + "type": "TXT", + "data": "txtTXTtxt" + } +] diff --git a/providers/dns/domeneshop/internal/fixtures/getDomains.json b/providers/dns/domeneshop/internal/fixtures/getDomains.json new file mode 100644 index 000000000..b491d7f53 --- /dev/null +++ b/providers/dns/domeneshop/internal/fixtures/getDomains.json @@ -0,0 +1,22 @@ +[ + { + "id": 1, + "domain": "example.com", + "expiry_date": "2019-08-24", + "registered_date": "2019-08-24", + "renew": true, + "registrant": "Ola Nordmann", + "status": "active", + "nameservers": [ + "ns1.hyp.net", + "ns2.hyp.net", + "ns3.hyp.net" + ], + "services": { + "registrar": true, + "dns": true, + "email": true, + "webhotel": "none" + } + } +] diff --git a/providers/dns/dreamhost/dreamhost_test.go b/providers/dns/dreamhost/dreamhost_test.go index 0f91ffae2..f85e00da4 100644 --- a/providers/dns/dreamhost/dreamhost_test.go +++ b/providers/dns/dreamhost/dreamhost_test.go @@ -1,13 +1,12 @@ package dreamhost import ( - "fmt" - "net/http" "net/http/httptest" "testing" "time" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -23,22 +22,15 @@ const ( fakeKeyAuth = "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI" ) -func setupTest(t *testing.T) (*DNSProvider, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.APIKey = fakeAPIKey + config.BaseURL = server.URL + config.HTTPClient = server.Client() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - config := NewDefaultConfig() - config.APIKey = fakeAPIKey - config.BaseURL = server.URL - config.HTTPClient = server.Client() - - provider, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - return provider, mux + return NewDNSProviderConfig(config) + }) } func TestNewDNSProvider(t *testing.T) { @@ -115,67 +107,48 @@ func TestNewDNSProviderConfig(t *testing.T) { } func TestDNSProvider_Present(t *testing.T) { - provider, mux := setupTest(t) - - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodGet, r.Method, "method") - - q := r.URL.Query() - assert.Equal(t, fakeAPIKey, q.Get("key")) - assert.Equal(t, "dns-add_record", q.Get("cmd")) - assert.Equal(t, "json", q.Get("format")) - assert.Equal(t, "_acme-challenge.example.com", q.Get("record")) - assert.Equal(t, fakeKeyAuth, q.Get("value")) - assert.Equal(t, "Managed+By+lego", q.Get("comment")) - - _, err := fmt.Fprintf(w, `{"data":"record_added","result":"success"}`) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + provider := mockBuilder(). + Route("GET /", + servermock.RawStringResponse(`{"data":"record_added","result":"success"}`), + servermock.CheckQueryParameter().Strict(). + With("cmd", "dns-add_record"). + With("comment", "Managed+By+lego"). + With("format", "json"). + With("record", "_acme-challenge.example.com"). + With("type", "TXT"). + With("key", fakeAPIKey). + With("value", fakeKeyAuth), + ). + Build(t) err := provider.Present("example.com", "", fakeChallengeToken) require.NoError(t, err) } func TestDNSProvider_PresentFailed(t *testing.T) { - provider, mux := setupTest(t) - - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodGet, r.Method, "method") - - _, err := fmt.Fprintf(w, `{"data":"record_already_exists_remove_first","result":"error"}`) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + provider := mockBuilder(). + Route("GET /", + servermock.RawStringResponse(`{"data":"record_already_exists_remove_first","result":"error"}`)). + Build(t) err := provider.Present("example.com", "", fakeChallengeToken) require.EqualError(t, err, "dreamhost: add TXT record failed: record_already_exists_remove_first") } func TestDNSProvider_Cleanup(t *testing.T) { - provider, mux := setupTest(t) - - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodGet, r.Method, "method") - - q := r.URL.Query() - assert.Equal(t, fakeAPIKey, q.Get("key"), "key mismatch") - assert.Equal(t, "dns-remove_record", q.Get("cmd"), "cmd mismatch") - assert.Equal(t, "json", q.Get("format")) - assert.Equal(t, "_acme-challenge.example.com", q.Get("record")) - assert.Equal(t, fakeKeyAuth, q.Get("value"), "value mismatch") - assert.Equal(t, "Managed+By+lego", q.Get("comment")) - - _, err := fmt.Fprintf(w, `{"data":"record_removed","result":"success"}`) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + provider := mockBuilder(). + Route("GET /", + servermock.RawStringResponse(`{"data":"record_removed","result":"success"}`), + servermock.CheckQueryParameter().Strict(). + With("cmd", "dns-remove_record"). + With("comment", "Managed+By+lego"). + With("format", "json"). + With("record", "_acme-challenge.example.com"). + With("type", "TXT"). + With("key", fakeAPIKey). + With("value", fakeKeyAuth), + ). + Build(t) err := provider.CleanUp("example.com", "", fakeChallengeToken) require.NoError(t, err, "failed to remove TXT record") diff --git a/providers/dns/dreamhost/internal/client_test.go b/providers/dns/dreamhost/internal/client_test.go index eff520df0..a836658f9 100644 --- a/providers/dns/dreamhost/internal/client_test.go +++ b/providers/dns/dreamhost/internal/client_test.go @@ -1,15 +1,59 @@ package internal import ( + "net/http/httptest" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -const fakeAPIKey = "asdf1234" +func setupClient(server *httptest.Server) (*Client, error) { + client := NewClient("secret") + client.BaseURL = server.URL + client.HTTPClient = server.Client() + + return client, nil +} + +func TestClient_AddRecord(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /", servermock.RawStringResponse(`{}`), + servermock.CheckQueryParameter().Strict(). + With("cmd", "dns-add_record"). + With("comment", "Managed+By+lego"). + With("format", "json"). + With("key", "secret"). + With("record", "example.com"). + With("type", "TXT"). + With("value", "aaa")). + Build(t) + + err := client.AddRecord(t.Context(), "example.com", "aaa") + require.NoError(t, err) +} + +func TestClient_RemoveRecord(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /", servermock.RawStringResponse(`{}`), + servermock.CheckQueryParameter().Strict(). + With("cmd", "dns-remove_record"). + With("comment", "Managed+By+lego"). + With("format", "json"). + With("key", "secret"). + With("record", "example.com"). + With("type", "TXT"). + With("value", "aaa")). + Build(t) + + err := client.RemoveRecord(t.Context(), "example.com", "aaa") + require.NoError(t, err) +} func TestClient_buildQuery(t *testing.T) { + const fakeAPIKey = "asdf1234" + testCases := []struct { desc string apiKey string diff --git a/providers/dns/duckdns/internal/client.go b/providers/dns/duckdns/internal/client.go index 0ed1bc864..ae86f64c8 100644 --- a/providers/dns/duckdns/internal/client.go +++ b/providers/dns/duckdns/internal/client.go @@ -21,6 +21,7 @@ const defaultBaseURL = "https://www.duckdns.org/update" type Client struct { token string + baseURL string HTTPClient *http.Client } @@ -28,6 +29,7 @@ type Client struct { func NewClient(token string) *Client { return &Client{ token: token, + baseURL: defaultBaseURL, HTTPClient: &http.Client{Timeout: 5 * time.Second}, } } @@ -44,7 +46,7 @@ func (c Client) RemoveTXTRecord(ctx context.Context, domain string) error { // To update the TXT record we just need to make one simple get request. // In DuckDNS you only have one TXT record shared with the domain and all subdomains. func (c Client) UpdateTxtRecord(ctx context.Context, domain, txt string, clearRecord bool) error { - endpoint, _ := url.Parse(defaultBaseURL) + endpoint, _ := url.Parse(c.baseURL) mainDomain := getMainDomain(domain) if mainDomain == "" { diff --git a/providers/dns/duckdns/internal/client_test.go b/providers/dns/duckdns/internal/client_test.go index 4df17d049..aaa441fad 100644 --- a/providers/dns/duckdns/internal/client_test.go +++ b/providers/dns/duckdns/internal/client_test.go @@ -1,11 +1,50 @@ package internal import ( + "net/http/httptest" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +func setupClient(server *httptest.Server) (*Client, error) { + client := NewClient("secret") + client.baseURL = server.URL + client.HTTPClient = server.Client() + + return client, nil +} + +func TestClient_AddTXTRecord(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /", servermock.RawStringResponse("OK"), + servermock.CheckQueryParameter().Strict(). + With("clear", "false"). + With("domains", "com"). + With("token", "secret"). + With("txt", "value")). + Build(t) + + err := client.AddTXTRecord(t.Context(), "example.com", "value") + require.NoError(t, err) +} + +func TestClient_RemoveTXTRecord(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /", servermock.RawStringResponse("OK"), + servermock.CheckQueryParameter().Strict(). + With("clear", "true"). + With("domains", "com"). + With("token", "secret"). + With("txt", "")). + Build(t) + + err := client.RemoveTXTRecord(t.Context(), "example.com") + require.NoError(t, err) +} + func Test_getMainDomain(t *testing.T) { testCases := []struct { desc string diff --git a/providers/dns/dyn/internal/client_test.go b/providers/dns/dyn/internal/client_test.go index c6cdff9d5..f166e7d8d 100644 --- a/providers/dns/dyn/internal/client_test.go +++ b/providers/dns/dyn/internal/client_test.go @@ -1,120 +1,58 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, handlerFunc http.HandlerFunc) *Client { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(pattern, handlerFunc) - +func setupClient(server *httptest.Server) (*Client, error) { client := NewClient("bob", "user", "secret") client.HTTPClient = server.Client() client.baseURL, _ = url.Parse(server.URL) - return client + return client, nil } -func authenticatedHandler(method string, status int, file string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusBadRequest) - return - } +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("bob", "user", "secret") + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - token := req.Header.Get(authTokenHeader) - if token != "tok" { - http.Error(rw, fmt.Sprintf("invalid credentials: %q", token), http.StatusUnauthorized) - return - } - - if file == "" { - rw.WriteHeader(status) - return - } - - open, err := os.Open(filepath.Join("fixtures", file)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } -} - -func unauthenticatedHandler(method string, status int, file string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusBadRequest) - return - } - - token := req.Header.Get(authTokenHeader) - if token != "" { - http.Error(rw, fmt.Sprintf("invalid credentials: %q", token), http.StatusUnauthorized) - return - } - - if file == "" { - rw.WriteHeader(status) - return - } - - open, err := os.Open(filepath.Join("fixtures", file)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders()) } func TestClient_Publish(t *testing.T) { - client := setupTest(t, "/Zone/example.com", unauthenticatedHandler(http.MethodPut, http.StatusOK, "publish.json")) + client := mockBuilder(). + Route("PUT /Zone/example.com", servermock.ResponseFromFixture("publish.json"), + servermock.CheckRequestJSONBody(`{"publish":true,"notes":"my message"}`)). + Build(t) err := client.Publish(t.Context(), "example.com", "my message") require.NoError(t, err) } func TestClient_AddTXTRecord(t *testing.T) { - client := setupTest(t, "/TXTRecord/example.com/example.com.", unauthenticatedHandler(http.MethodPost, http.StatusCreated, "create-txt-record.json")) + client := mockBuilder(). + Route("POST /TXTRecord/example.com/example.com.", servermock.ResponseFromFixture("create-txt-record.json"), + servermock.CheckRequestJSONBody(`{"rdata":{"txtdata":"txt"},"ttl":"120"}`)). + Build(t) err := client.AddTXTRecord(t.Context(), "example.com", "example.com.", "txt", 120) require.NoError(t, err) } func TestClient_RemoveTXTRecord(t *testing.T) { - client := setupTest(t, "/TXTRecord/example.com/example.com.", unauthenticatedHandler(http.MethodDelete, http.StatusOK, "")) + client := mockBuilder(). + Route("DELETE /TXTRecord/example.com/example.com.", nil). + Build(t) err := client.RemoveTXTRecord(t.Context(), "example.com", "example.com.") require.NoError(t, err) diff --git a/providers/dns/dyn/internal/session_test.go b/providers/dns/dyn/internal/session_test.go index 5a939f40c..349b1b190 100644 --- a/providers/dns/dyn/internal/session_test.go +++ b/providers/dns/dyn/internal/session_test.go @@ -2,9 +2,9 @@ package internal import ( "context" - "net/http" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -16,7 +16,10 @@ func mockContext(t *testing.T) context.Context { } func TestClient_login(t *testing.T) { - client := setupTest(t, "/Session", unauthenticatedHandler(http.MethodPost, http.StatusOK, "login.json")) + client := mockBuilder(). + Route("POST /Session", servermock.ResponseFromFixture("login.json"), + servermock.CheckRequestJSONBody(`{"customer_name":"bob","user_name":"user","password":"secret"}`)). + Build(t) sess, err := client.login(t.Context()) require.NoError(t, err) @@ -27,14 +30,22 @@ func TestClient_login(t *testing.T) { } func TestClient_Logout(t *testing.T) { - client := setupTest(t, "/Session", authenticatedHandler(http.MethodDelete, http.StatusOK, "")) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader().WithJSONHeaders(). + With(authTokenHeader, "tok"), + ). + Route("DELETE /Session", nil). + Build(t) err := client.Logout(mockContext(t)) require.NoError(t, err) } func TestClient_CreateAuthenticatedContext(t *testing.T) { - client := setupTest(t, "/Session", unauthenticatedHandler(http.MethodPost, http.StatusOK, "login.json")) + client := mockBuilder(). + Route("POST /Session", servermock.ResponseFromFixture("login.json"), + servermock.CheckRequestJSONBody(`{"customer_name":"bob","user_name":"user","password":"secret"}`)). + Build(t) ctx, err := client.CreateAuthenticatedContext(t.Context()) require.NoError(t, err) diff --git a/providers/dns/dyndnsfree/internal/client_test.go b/providers/dns/dyndnsfree/internal/client_test.go index 206022d5c..d6f1d276b 100644 --- a/providers/dns/dyndnsfree/internal/client_test.go +++ b/providers/dns/dyndnsfree/internal/client_test.go @@ -1,56 +1,44 @@ package internal import ( - "net/http" "net/http/httptest" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, message string) *Client { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - +func setupClient(server *httptest.Server) (*Client, error) { client, err := NewClient("user", "secret") - require.NoError(t, err) + if err != nil { + return nil, err + } - client.HTTPClient = server.Client() client.baseURL = server.URL + client.HTTPClient = server.Client() - mux.HandleFunc("GET /", func(rw http.ResponseWriter, req *http.Request) { - query := req.URL.Query() - - username := query.Get("username") - if username != "user" { - http.Error(rw, "invalid username: "+username, http.StatusUnauthorized) - return - } - - password := query.Get("password") - if password != "secret" { - http.Error(rw, "invalid password: "+password, http.StatusUnauthorized) - return - } - - _, _ = rw.Write([]byte(message)) - }) - - return client + return client, nil } func TestAddTXTRecord(t *testing.T) { - client := setupTest(t, "success") + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /", servermock.RawStringResponse("success"), + servermock.CheckQueryParameter().Strict(). + With("add_hostname", "sub.example.com"). + With("hostname", "example.com"). + With("password", "secret"). + With("txt", "value"). + With("username", "user")). + Build(t) err := client.AddTXTRecord(t.Context(), "example.com", "sub.example.com", "value") require.NoError(t, err) } func TestAddTXTRecord_error(t *testing.T) { - client := setupTest(t, "error: authentification failed") + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /", servermock.RawStringResponse("error: authentification failed")). + Build(t) err := client.AddTXTRecord(t.Context(), "example.com", "sub.example.com", "value") require.EqualError(t, err, "error: authentification failed") diff --git a/providers/dns/dynu/internal/client_test.go b/providers/dns/dynu/internal/client_test.go index 4f3a16be9..7dc94eca2 100644 --- a/providers/dns/dynu/internal/client_test.go +++ b/providers/dns/dynu/internal/client_test.go @@ -1,52 +1,27 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, method, pattern string, status int, file string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient() + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - open, err := os.Open(file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client := NewClient() - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(), + ) } func TestGetRootDomain(t *testing.T) { @@ -64,9 +39,9 @@ func TestGetRootDomain(t *testing.T) { }{ { desc: "success", - pattern: "/dns/getroot/test.lego.freeddns.org", + pattern: "GET /dns/getroot/test.lego.freeddns.org", status: http.StatusOK, - file: "./fixtures/get_root_domain.json", + file: "get_root_domain.json", expected: expected{ domain: &DNSHostname{ APIException: &APIException{ @@ -81,9 +56,9 @@ func TestGetRootDomain(t *testing.T) { }, { desc: "invalid", - pattern: "/dns/getroot/test.lego.freeddns.org", + pattern: "GET /dns/getroot/test.lego.freeddns.org", status: http.StatusNotImplemented, - file: "./fixtures/get_root_domain_invalid.json", + file: "get_root_domain_invalid.json", expected: expected{ error: "API error: 501: Argument Exception: Invalid.", }, @@ -94,7 +69,9 @@ func TestGetRootDomain(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - client := setupTest(t, http.MethodGet, test.pattern, test.status, test.file) + client := mockBuilder(). + Route(test.pattern, servermock.ResponseFromFixture(test.file).WithStatusCode(test.status)). + Build(t) domain, err := client.GetRootDomain(t.Context(), "test.lego.freeddns.org") @@ -126,9 +103,9 @@ func TestGetRecords(t *testing.T) { }{ { desc: "success", - pattern: "/dns/record/_acme-challenge.lego.freeddns.org", + pattern: "GET /dns/record/_acme-challenge.lego.freeddns.org", status: http.StatusOK, - file: "./fixtures/get_records.json", + file: "get_records.json", expected: expected{ records: []DNSRecord{ { @@ -160,18 +137,18 @@ func TestGetRecords(t *testing.T) { }, { desc: "empty", - pattern: "/dns/record/_acme-challenge.lego.freeddns.org", + pattern: "GET /dns/record/_acme-challenge.lego.freeddns.org", status: http.StatusOK, - file: "./fixtures/get_records_empty.json", + file: "get_records_empty.json", expected: expected{ records: []DNSRecord{}, }, }, { desc: "invalid", - pattern: "/dns/record/_acme-challenge.lego.freeddns.org", + pattern: "GET /dns/record/_acme-challenge.lego.freeddns.org", status: http.StatusNotImplemented, - file: "./fixtures/get_records_invalid.json", + file: "get_records_invalid.json", expected: expected{ error: "API error: 501: Argument Exception: Invalid.", }, @@ -182,7 +159,11 @@ func TestGetRecords(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - client := setupTest(t, http.MethodGet, test.pattern, test.status, test.file) + client := mockBuilder(). + Route(test.pattern, servermock.ResponseFromFixture(test.file).WithStatusCode(test.status), + servermock.CheckQueryParameter().Strict(). + With("recordType", "TXT")). + Build(t) records, err := client.GetRecords(t.Context(), "_acme-challenge.lego.freeddns.org", "TXT") @@ -213,15 +194,15 @@ func TestAddNewRecord(t *testing.T) { }{ { desc: "success", - pattern: "/dns/9007481/record", + pattern: "POST /dns/9007481/record", status: http.StatusOK, - file: "./fixtures/add_new_record.json", + file: "add_new_record.json", }, { desc: "invalid", - pattern: "/dns/9007481/record", + pattern: "POST /dns/9007481/record", status: http.StatusNotImplemented, - file: "./fixtures/add_new_record_invalid.json", + file: "add_new_record_invalid.json", expected: expected{ error: "API error: 501: Argument Exception: Invalid.", }, @@ -232,7 +213,10 @@ func TestAddNewRecord(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - client := setupTest(t, http.MethodPost, test.pattern, test.status, test.file) + client := mockBuilder(). + Route(test.pattern, servermock.ResponseFromFixture(test.file).WithStatusCode(test.status), + servermock.CheckRequestJSONBodyFromFile("add_new_record-request.json")). + Build(t) record := DNSRecord{ Type: "TXT", @@ -270,15 +254,15 @@ func TestDeleteRecord(t *testing.T) { }{ { desc: "success", - pattern: "/", + pattern: "DELETE /", status: http.StatusOK, - file: "./fixtures/delete_record.json", + file: "delete_record.json", }, { desc: "invalid", - pattern: "/", + pattern: "DELETE /", status: http.StatusNotImplemented, - file: "./fixtures/delete_record_invalid.json", + file: "delete_record_invalid.json", expected: expected{ error: "API error: 501: Argument Exception: Invalid.", }, @@ -289,7 +273,9 @@ func TestDeleteRecord(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - client := setupTest(t, http.MethodDelete, test.pattern, test.status, test.file) + client := mockBuilder(). + Route(test.pattern, servermock.ResponseFromFixture(test.file).WithStatusCode(test.status)). + Build(t) err := client.DeleteRecord(t.Context(), 9007481, 6041418) diff --git a/providers/dns/dynu/internal/fixtures/add_new_record-request.json b/providers/dns/dynu/internal/fixtures/add_new_record-request.json new file mode 100644 index 000000000..f3c75ca36 --- /dev/null +++ b/providers/dns/dynu/internal/fixtures/add_new_record-request.json @@ -0,0 +1,9 @@ +{ + "recordType": "TXT", + "domainName": "lego.freeddns.org", + "nodeName": "_acme-challenge", + "hostname": "_acme-challenge.lego.freeddns.org", + "state": true, + "textData": "txt_txt_txt_txt_txt_txt_txt_2", + "ttl": 300 +} diff --git a/providers/dns/easydns/easydns_test.go b/providers/dns/easydns/easydns_test.go index 972ff8cda..9a11ef6cc 100644 --- a/providers/dns/easydns/easydns_test.go +++ b/providers/dns/easydns/easydns_test.go @@ -2,7 +2,6 @@ package easydns import ( "fmt" - "io" "net/http" "net/http/httptest" "net/url" @@ -10,12 +9,10 @@ import ( "time" "github.com/go-acme/lego/v4/platform/tester" - "github.com/stretchr/testify/assert" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -const authorizationHeader = "Authorization" - const envDomain = envNamespace + "DOMAIN" var envTest = tester.NewEnvTest( @@ -24,26 +21,27 @@ var envTest = tester.NewEnvTest( EnvKey). WithDomain(envDomain) -func setupTest(t *testing.T) (*DNSProvider, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + endpoint, err := url.Parse(server.URL) + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + config := NewDefaultConfig() + config.Token = "TOKEN" + config.Key = "SECRET" + config.Endpoint = endpoint + config.HTTPClient = server.Client() - endpoint, err := url.Parse(server.URL) - require.NoError(t, err) - - config := NewDefaultConfig() - config.Token = "TOKEN" - config.Key = "SECRET" - config.Endpoint = endpoint - config.HTTPClient = server.Client() - - provider, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - return provider, mux + return NewDNSProviderConfig(config) + }, + servermock.CheckHeader(). + WithJSONHeaders(). + WithAuthorization("Basic VE9LRU46U0VDUkVU"), + servermock.CheckQueryParameter().Strict(). + With("format", "json")) } func TestNewDNSProvider(t *testing.T) { @@ -145,78 +143,50 @@ func TestNewDNSProviderConfig(t *testing.T) { } func TestDNSProvider_Present(t *testing.T) { - provider, mux := setupTest(t) - - mux.HandleFunc("/zones/records/all/example.com", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodGet, r.Method, "method") - assert.Equal(t, "format=json", r.URL.RawQuery, "query") - assert.Equal(t, "Basic VE9LRU46U0VDUkVU", r.Header.Get(authorizationHeader), authorizationHeader) - - w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, `{ - "msg": "string", - "status": 200, - "tm": 0, - "data": [{ - "id": "60898922", - "domain": "example.com", - "host": "hosta", - "ttl": "300", - "prio": "0", - "geozone_id": "0", - "type": "A", - "rdata": "1.2.3.4", - "last_mod": "2019-08-28 19:09:50" - }], - "count": 0, - "total": 0, - "start": 0, - "max": 0 -} -`) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + provider := mockBuilder(). + Route("GET /zones/records/all/example.com", + servermock.RawStringResponse(`{ + "msg": "string", + "status": 200, + "tm": 0, + "data": [{ + "id": "60898922", + "domain": "example.com", + "host": "hosta", + "ttl": "300", + "prio": "0", + "geozone_id": "0", + "type": "A", + "rdata": "1.2.3.4", + "last_mod": "2019-08-28 19:09:50" + }], + "count": 0, + "total": 0, + "start": 0, + "max": 0 } - }) - - mux.HandleFunc("/zones/records/add/example.com/TXT", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodPut, r.Method, "method") - assert.Equal(t, "format=json", r.URL.RawQuery, "query") - assert.Equal(t, "application/json", r.Header.Get("Content-Type"), "Content-Type") - assert.Equal(t, "Basic VE9LRU46U0VDUkVU", r.Header.Get(authorizationHeader), authorizationHeader) - - reqBody, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - expectedReqBody := `{"domain":"example.com","host":"_acme-challenge","ttl":"120","prio":"0","type":"TXT","rdata":"pW9ZKG0xz_PCriK-nCMOjADy9eJcgGWIzkkj2fN4uZM"} -` - assert.Equal(t, expectedReqBody, string(reqBody)) - - w.WriteHeader(http.StatusCreated) - _, err = fmt.Fprintf(w, `{ - "msg": "OK", - "tm": 1554681934, - "data": { - "host": "_acme-challenge", - "geozone_id": 0, - "ttl": "120", - "prio": "0", - "rdata": "pW9ZKG0xz_PCriK-nCMOjADy9eJcgGWIzkkj2fN4uZM", - "revoked": 0, - "id": "123456789", - "new_host": "_acme-challenge.example.com" - }, - "status": 201 - }`) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + `), + servermock.CheckQueryParameter().Strict(). + With("format", "json")). + Route("PUT /zones/records/add/example.com/TXT", + servermock.RawStringResponse(`{ + "msg": "OK", + "tm": 1554681934, + "data": { + "host": "_acme-challenge", + "geozone_id": 0, + "ttl": "120", + "prio": "0", + "rdata": "pW9ZKG0xz_PCriK-nCMOjADy9eJcgGWIzkkj2fN4uZM", + "revoked": 0, + "id": "123456789", + "new_host": "_acme-challenge.example.com" + }, + "status": 201 + }`), + servermock.CheckRequestJSONBody(`{"domain":"example.com","host":"_acme-challenge","ttl":"120","prio":"0","type":"TXT","rdata":"pW9ZKG0xz_PCriK-nCMOjADy9eJcgGWIzkkj2fN4uZM"} +`)). + Build(t) err := provider.Present("example.com", "token", "keyAuth") require.NoError(t, err) @@ -224,163 +194,116 @@ func TestDNSProvider_Present(t *testing.T) { } func TestDNSProvider_Cleanup_WhenRecordIdNotSet_NoOp(t *testing.T) { - provider, mux := setupTest(t) - - mux.HandleFunc("/zones/records/all/example.com", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodGet, r.Method, "method") - assert.Equal(t, "format=json", r.URL.RawQuery, "query") - assert.Equal(t, "Basic VE9LRU46U0VDUkVU", r.Header.Get(authorizationHeader), authorizationHeader) - - w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, `{ - "msg": "string", - "status": 200, - "tm": 0, - "data": [{ - "id": "60898922", - "domain": "example.com", - "host": "hosta", - "ttl": "300", - "prio": "0", - "geozone_id": "0", - "type": "A", - "rdata": "1.2.3.4", - "last_mod": "2019-08-28 19:09:50" - }], - "count": 0, - "total": 0, - "start": 0, - "max": 0 -} -`) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + provider := mockBuilder(). + Route("GET /zones/records/all/_acme-challenge.example.com", + servermock.RawStringResponse(`{ + "msg": "string", + "status": 200, + "tm": 0, + "data": [{ + "id": "60898922", + "domain": "example.com", + "host": "hosta", + "ttl": "300", + "prio": "0", + "geozone_id": "0", + "type": "A", + "rdata": "1.2.3.4", + "last_mod": "2019-08-28 19:09:50" + }], + "count": 0, + "total": 0, + "start": 0, + "max": 0 + } + `)). + Build(t) err := provider.CleanUp("example.com", "token", "keyAuth") require.NoError(t, err) } func TestDNSProvider_Cleanup_WhenRecordIdSet_DeletesTxtRecord(t *testing.T) { - provider, mux := setupTest(t) - - mux.HandleFunc("/zones/records/all/example.com", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodGet, r.Method, "method") - assert.Equal(t, "format=json", r.URL.RawQuery, "query") - assert.Equal(t, "Basic VE9LRU46U0VDUkVU", r.Header.Get(authorizationHeader), authorizationHeader) - - w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, `{ - "msg": "string", - "status": 200, - "tm": 0, - "data": [{ - "id": "60898922", - "domain": "example.com", - "host": "hosta", - "ttl": "300", - "prio": "0", - "geozone_id": "0", - "type": "A", - "rdata": "1.2.3.4", - "last_mod": "2019-08-28 19:09:50" - }], - "count": 0, - "total": 0, - "start": 0, - "max": 0 -} -`) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - mux.HandleFunc("/zones/records/example.com/123456", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodDelete, r.Method, "method") - assert.Equal(t, "format=json", r.URL.RawQuery, "query") - assert.Equal(t, "Basic VE9LRU46U0VDUkVU", r.Header.Get(authorizationHeader), authorizationHeader) - - w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, `{ - "msg": "OK", - "data": { - "domain": "example.com", - "id": "123456" - }, - "status": 200 - }`) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + provider := mockBuilder(). + Route("GET /zones/records/all/_acme-challenge.example.com", + servermock.RawStringResponse(`{ + "msg": "string", + "status": 200, + "tm": 0, + "data": [{ + "id": "60898922", + "domain": "example.com", + "host": "hosta", + "ttl": "300", + "prio": "0", + "geozone_id": "0", + "type": "A", + "rdata": "1.2.3.4", + "last_mod": "2019-08-28 19:09:50" + }], + "count": 0, + "total": 0, + "start": 0, + "max": 0 + } + `)). + Route("DELETE /zones/records/_acme-challenge.example.com/123456", + servermock.RawStringResponse(`{ + "msg": "OK", + "data": { + "domain": "example.com", + "id": "123456" + }, + "status": 200 + }`)). + Build(t) provider.recordIDs["_acme-challenge.example.com.|pW9ZKG0xz_PCriK-nCMOjADy9eJcgGWIzkkj2fN4uZM"] = "123456" + err := provider.CleanUp("example.com", "token", "keyAuth") require.NoError(t, err) } func TestDNSProvider_Cleanup_WhenHttpError_ReturnsError(t *testing.T) { - provider, mux := setupTest(t) - - mux.HandleFunc("/zones/records/all/example.com", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodGet, r.Method, "method") - assert.Equal(t, "format=json", r.URL.RawQuery, "query") - assert.Equal(t, "Basic VE9LRU46U0VDUkVU", r.Header.Get(authorizationHeader), authorizationHeader) - - w.WriteHeader(http.StatusOK) - _, err := fmt.Fprintf(w, `{ - "msg": "string", - "status": 200, - "tm": 0, - "data": [{ - "id": "60898922", - "domain": "example.com", - "host": "hosta", - "ttl": "300", - "prio": "0", - "geozone_id": "0", - "type": "A", - "rdata": "1.2.3.4", - "last_mod": "2019-08-28 19:09:50" - }], - "count": 0, - "total": 0, - "start": 0, - "max": 0 -} -`) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - errorMessage := `{ "error": { "code": 406, "message": "Provided id is invalid or you do not have permission to access it." } }` - mux.HandleFunc("/zones/records/example.com/123456", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodDelete, r.Method, "method") - assert.Equal(t, "format=json", r.URL.RawQuery, "query") - assert.Equal(t, "Basic VE9LRU46U0VDUkVU", r.Header.Get(authorizationHeader), authorizationHeader) - w.WriteHeader(http.StatusNotAcceptable) - _, err := fmt.Fprint(w, errorMessage) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + provider := mockBuilder(). + Route("GET /zones/records/all/example.com", + servermock.RawStringResponse(`{ + "msg": "string", + "status": 200, + "tm": 0, + "data": [{ + "id": "60898922", + "domain": "example.com", + "host": "hosta", + "ttl": "300", + "prio": "0", + "geozone_id": "0", + "type": "A", + "rdata": "1.2.3.4", + "last_mod": "2019-08-28 19:09:50" + }], + "count": 0, + "total": 0, + "start": 0, + "max": 0 +} +`)). + Route("DELETE /zones/records/example.com/123456", + servermock.RawStringResponse(errorMessage). + WithStatusCode(http.StatusNotAcceptable)). + Build(t) provider.recordIDs["_acme-challenge.example.com.|pW9ZKG0xz_PCriK-nCMOjADy9eJcgGWIzkkj2fN4uZM"] = "123456" + err := provider.CleanUp("example.com", "token", "keyAuth") + expectedError := fmt.Sprintf("easydns: unexpected status code: [status code: 406] body: %v", errorMessage) require.EqualError(t, err, expectedError) } diff --git a/providers/dns/easydns/internal/client_test.go b/providers/dns/easydns/internal/client_test.go index 02d46a5a7..bf4e1e45b 100644 --- a/providers/dns/easydns/internal/client_test.go +++ b/providers/dns/easydns/internal/client_test.go @@ -1,73 +1,34 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, method, pattern string, status int, file string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("tok", "k") + client.HTTPClient = server.Client() + client.BaseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusBadRequest) - return - } - - token, key, ok := req.BasicAuth() - if token != "tok" || key != "k" || !ok { - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - if req.URL.Query().Get("format") != "json" { - http.Error(rw, fmt.Sprintf("invalid format: %s", req.URL.Query().Get("format")), http.StatusBadRequest) - return - } - - if file == "" { - rw.WriteHeader(status) - return - } - - open, err := os.Open(filepath.Join("fixtures", file)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client := NewClient("tok", "k") - client.HTTPClient = server.Client() - client.BaseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithBasicAuth("tok", "k"), + ) } func TestClient_ListZones(t *testing.T) { - client := setupTest(t, http.MethodGet, "/zones/records/all/example.com", http.StatusOK, "list-zone.json") + client := mockBuilder(). + Route("GET /zones/records/all/example.com", servermock.ResponseFromFixture("list-zone.json")). + Build(t) zones, err := client.ListZones(t.Context(), "example.com") require.NoError(t, err) @@ -87,14 +48,20 @@ func TestClient_ListZones(t *testing.T) { } func TestClient_ListZones_error(t *testing.T) { - client := setupTest(t, http.MethodGet, "/zones/records/all/example.com", http.StatusOK, "error1.json") + client := mockBuilder(). + Route("GET /zones/records/all/example.com", servermock.ResponseFromFixture("error1.json")). + Build(t) _, err := client.ListZones(t.Context(), "example.com") require.EqualError(t, err, "code 420: Enhance Your Calm. Rate limit exceeded (too many requests) OR you did NOT provide any credentials with your request!") } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, http.MethodPut, "/zones/records/add/example.com/TXT", http.StatusCreated, "add-record.json") + client := mockBuilder(). + Route("PUT /zones/records/add/example.com/TXT", + servermock.ResponseFromFixture("add-record.json").WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBody(`{"domain":"example.com","host":"test631","ttl":"300","prio":"0","type":"TXT","rdata":"txt"}`)). + Build(t) record := ZoneRecord{ Domain: "example.com", @@ -112,7 +79,10 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, http.MethodPut, "/zones/records/add/example.com/TXT", http.StatusCreated, "error1.json") + client := mockBuilder(). + Route("PUT /zones/records/add/example.com/TXT", + servermock.ResponseFromFixture("error1.json").WithStatusCode(http.StatusCreated)). + Build(t) record := ZoneRecord{ Domain: "example.com", @@ -128,7 +98,9 @@ func TestClient_AddRecord_error(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, http.MethodDelete, "/zones/records/example.com/xxx", http.StatusOK, "") + client := mockBuilder(). + Route("DELETE /zones/records/example.com/xxx", nil). + Build(t) err := client.DeleteRecord(t.Context(), "example.com", "xxx") require.NoError(t, err) diff --git a/providers/dns/efficientip/internal/client_test.go b/providers/dns/efficientip/internal/client_test.go index 137f2628c..5d68b7d7f 100644 --- a/providers/dns/efficientip/internal/client_test.go +++ b/providers/dns/efficientip/internal/client_test.go @@ -1,79 +1,38 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, method, pattern string, status int, file string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + srvURL, _ := url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client := NewClient(srvURL.Host, "user", "secret") + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - username, password, ok := req.BasicAuth() - if !ok { - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - if username != "user" { - http.Error(rw, fmt.Sprintf("username: want %s got %s", username, "user"), http.StatusUnauthorized) - return - } - - if password != "secret" { - http.Error(rw, fmt.Sprintf("password: want %s got %s", password, "secret"), http.StatusUnauthorized) - return - } - - open, err := os.Open(filepath.Join("fixtures", file)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - srvURL, _ := url.Parse(server.URL) - - client := NewClient(srvURL.Host, "user", "secret") - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithBasicAuth("user", "secret"), + ) } func TestListRecords(t *testing.T) { - client := setupTest(t, http.MethodGet, "/dns_rr_list", http.StatusOK, "dns_rr_list.json") + client := mockBuilder(). + Route("GET /dns_rr_list", servermock.ResponseFromFixture("dns_rr_list.json")). + Build(t) - ctx := t.Context() - - records, err := client.ListRecords(ctx) + records, err := client.ListRecords(t.Context()) require.NoError(t, err) expected := []ResourceRecord{ @@ -336,11 +295,13 @@ func TestListRecords(t *testing.T) { } func TestGetRecord(t *testing.T) { - client := setupTest(t, http.MethodGet, "/dns_rr_info", http.StatusOK, "dns_rr_info.json") + client := mockBuilder(). + Route("GET /dns_rr_info", servermock.ResponseFromFixture("dns_rr_info.json"), + servermock.CheckQueryParameter().Strict(). + With("rr_id", "239")). + Build(t) - ctx := t.Context() - - record, err := client.GetRecord(ctx, "239") + record, err := client.GetRecord(t.Context(), "239") require.NoError(t, err) expected := &ResourceRecord{ @@ -383,9 +344,11 @@ func TestGetRecord(t *testing.T) { } func TestAddRecord(t *testing.T) { - client := setupTest(t, http.MethodPost, "/dns_rr_add", http.StatusCreated, "dns_rr_add.json") - - ctx := t.Context() + client := mockBuilder(). + Route("POST /dns_rr_add", + servermock.ResponseFromFixture("dns_rr_add.json").WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBody(`{"dns_name":"dns.smart","dnsview_name":"external","rr_name":"test.example.com","rr_type":"TXT","value1":"test"}`)). + Build(t) r := ResourceRecord{ RRName: "test.example.com", @@ -395,7 +358,7 @@ func TestAddRecord(t *testing.T) { DNSViewName: "external", } - resp, err := client.AddRecord(ctx, r) + resp, err := client.AddRecord(t.Context(), r) require.NoError(t, err) expected := &BaseOutput{RetOID: "239"} @@ -404,11 +367,13 @@ func TestAddRecord(t *testing.T) { } func TestDeleteRecord(t *testing.T) { - client := setupTest(t, http.MethodDelete, "/dns_rr_delete", http.StatusOK, "dns_rr_delete.json") + client := mockBuilder(). + Route("DELETE /dns_rr_delete", servermock.ResponseFromFixture("dns_rr_delete.json"), + servermock.CheckQueryParameter().Strict(). + With("rr_id", "251")). + Build(t) - ctx := t.Context() - - resp, err := client.DeleteRecord(ctx, DeleteInputParameters{RRID: "251"}) + resp, err := client.DeleteRecord(t.Context(), DeleteInputParameters{RRID: "251"}) require.NoError(t, err) expected := &BaseOutput{RetOID: "251"} @@ -417,10 +382,11 @@ func TestDeleteRecord(t *testing.T) { } func TestDeleteRecord_error(t *testing.T) { - client := setupTest(t, http.MethodDelete, "/dns_rr_delete", http.StatusBadRequest, "dns_rr_delete-error.json") + client := mockBuilder(). + Route("DELETE /dns_rr_delete", + servermock.ResponseFromFixture("dns_rr_delete-error.json").WithStatusCode(http.StatusBadRequest)). + Build(t) - ctx := t.Context() - - _, err := client.DeleteRecord(ctx, DeleteInputParameters{RRID: "251"}) + _, err := client.DeleteRecord(t.Context(), DeleteInputParameters{RRID: "251"}) require.ErrorAs(t, err, &APIError{}) } diff --git a/providers/dns/epik/internal/client_test.go b/providers/dns/epik/internal/client_test.go index b23862207..b7c6f97df 100644 --- a/providers/dns/epik/internal/client_test.go +++ b/providers/dns/epik/internal/client_test.go @@ -1,37 +1,36 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("secret") + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient("secret") - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client, mux + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(), + ) } func TestClient_GetDNSRecords(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodGet, http.StatusOK, "getDnsRecord.json")) + client := mockBuilder(). + Route("GET /domains/example.com/records", + servermock.ResponseFromFixture("getDnsRecord.json"), + servermock.CheckQueryParameter().Strict(). + With("SIGNATURE", "secret")). + Build(t) records, err := client.GetDNSRecords(t.Context(), "example.com") require.NoError(t, err) @@ -88,18 +87,25 @@ func TestClient_GetDNSRecords(t *testing.T) { } func TestClient_GetDNSRecords_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodGet, http.StatusUnauthorized, "error.json")) + client := mockBuilder(). + Route("GET /domains/example.com/records", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized), + servermock.CheckQueryParameter().Strict(). + With("SIGNATURE", "secret")). + Build(t) _, err := client.GetDNSRecords(t.Context(), "example.com") require.Error(t, err) } func TestClient_CreateHostRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodPost, http.StatusOK, "createHostRecord.json")) + client := mockBuilder(). + Route("POST /domains/example.com/records", + servermock.ResponseFromFixture("createHostRecord.json"), + servermock.CheckQueryParameter().Strict(). + With("SIGNATURE", "secret")). + Build(t) record := RecordRequest{ Host: "www2", @@ -121,9 +127,13 @@ func TestClient_CreateHostRecord(t *testing.T) { } func TestClient_CreateHostRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodPost, http.StatusUnauthorized, "error.json")) + client := mockBuilder(). + Route("POST /domains/example.com/records", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized), + servermock.CheckQueryParameter().Strict(). + With("SIGNATURE", "secret")). + Build(t) record := RecordRequest{ Host: "www2", @@ -138,9 +148,13 @@ func TestClient_CreateHostRecord_error(t *testing.T) { } func TestClient_RemoveHostRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodDelete, http.StatusOK, "removeHostRecord.json")) + client := mockBuilder(). + Route("DELETE /domains/example.com/records", + servermock.ResponseFromFixture("removeHostRecord.json"), + servermock.CheckQueryParameter().Strict(). + With("ID", "abc123"). + With("SIGNATURE", "secret")). + Build(t) data, err := client.RemoveHostRecord(t.Context(), "example.com", "abc123") require.NoError(t, err) @@ -154,45 +168,12 @@ func TestClient_RemoveHostRecord(t *testing.T) { } func TestClient_RemoveHostRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/example.com/records", testHandler(http.MethodDelete, http.StatusUnauthorized, "error.json")) + client := mockBuilder(). + Route("DELETE /domains/example.com/records", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.RemoveHostRecord(t.Context(), "example.com", "abc123") require.Error(t, err) } - -func testHandler(method string, statusCode int, filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf(`{"message":"unsupported method: %s"}`, req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.URL.Query().Get("SIGNATURE") - if auth != "secret" { - http.Error(rw, fmt.Sprintf("invalid API key: %s", auth), http.StatusUnauthorized) - return - } - - rw.WriteHeader(statusCode) - - if statusCode == http.StatusNoContent { - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, fmt.Sprintf(`{"message":"%v"}`, err), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, fmt.Sprintf(`{"message":"%v"}`, err), http.StatusInternalServerError) - return - } - } -} diff --git a/providers/dns/f5xc/internal/client_test.go b/providers/dns/f5xc/internal/client_test.go index 7b53a3bce..0357abb16 100644 --- a/providers/dns/f5xc/internal/client_test.go +++ b/providers/dns/f5xc/internal/client_test.go @@ -1,58 +1,39 @@ package internal import ( - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, status int, filename string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("secret", "shortname") + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if filename == "" { - rw.WriteHeader(status) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client, err := NewClient("secret", "shortname") - require.NoError(t, err) - - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("APIToken secret")) } func TestClient_Create(t *testing.T) { - client := setupTest(t, "POST /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA", http.StatusOK, "create.json") + client := mockBuilder(). + Route("POST /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA", + servermock.ResponseFromFixture("create.json"), + servermock.CheckRequestJSONBody(`{"dns_zone_name":"example.com","group_name":"groupA","rrset":{"description":"lego","ttl":60,"txt_record":{"name":"wwww","values":["txt"]}}}`)). + Build(t) rrSet := RRSet{ Description: "lego", @@ -82,7 +63,10 @@ func TestClient_Create(t *testing.T) { } func TestClient_Create_error(t *testing.T) { - client := setupTest(t, "POST /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA", http.StatusBadRequest, "") + client := mockBuilder(). + Route("POST /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA", + servermock.Noop().WithStatusCode(http.StatusBadRequest)). + Build(t) rrSet := RRSet{ Description: "lego", @@ -98,7 +82,10 @@ func TestClient_Create_error(t *testing.T) { } func TestClient_Get(t *testing.T) { - client := setupTest(t, "GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusOK, "get.json") + client := mockBuilder(). + Route("GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", + servermock.ResponseFromFixture("get.json")). + Build(t) result, err := client.GetRRSet(t.Context(), "example.com", "groupA", "www", "TXT") require.NoError(t, err) @@ -122,7 +109,10 @@ func TestClient_Get(t *testing.T) { } func TestClient_Get_not_found(t *testing.T) { - client := setupTest(t, "GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusNotFound, "error_404.json") + client := mockBuilder(). + Route("GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", + servermock.ResponseFromFixture("error_404.json").WithStatusCode(http.StatusNotFound)). + Build(t) result, err := client.GetRRSet(t.Context(), "example.com", "groupA", "www", "TXT") require.NoError(t, err) @@ -131,14 +121,20 @@ func TestClient_Get_not_found(t *testing.T) { } func TestClient_Get_error(t *testing.T) { - client := setupTest(t, "GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusBadRequest, "") + client := mockBuilder(). + Route("GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", + servermock.Noop().WithStatusCode(http.StatusBadRequest)). + Build(t) _, err := client.GetRRSet(t.Context(), "example.com", "groupA", "www", "TXT") require.Error(t, err) } func TestClient_Delete(t *testing.T) { - client := setupTest(t, "DELETE /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusOK, "get.json") + client := mockBuilder(). + Route("DELETE /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", + servermock.ResponseFromFixture("get.json")). + Build(t) result, err := client.DeleteRRSet(t.Context(), "example.com", "groupA", "www", "TXT") require.NoError(t, err) @@ -162,14 +158,21 @@ func TestClient_Delete(t *testing.T) { } func TestClient_Delete_error(t *testing.T) { - client := setupTest(t, "DELETE /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusBadRequest, "") + client := mockBuilder(). + Route("DELETE /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", + servermock.Noop().WithStatusCode(http.StatusBadRequest)). + Build(t) _, err := client.DeleteRRSet(t.Context(), "example.com", "groupA", "www", "TXT") require.Error(t, err) } func TestClient_Replace(t *testing.T) { - client := setupTest(t, "PUT /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusOK, "get.json") + client := mockBuilder(). + Route("PUT /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", + servermock.ResponseFromFixture("get.json"), + servermock.CheckRequestJSONBody(`{"dns_zone_name":"example.com","group_name":"groupA","type":"TXT","rrset":{"description":"lego","ttl":60,"txt_record":{"name":"wwww","values":["txt"]}}}`)). + Build(t) rrSet := RRSet{ Description: "lego", @@ -202,7 +205,10 @@ func TestClient_Replace(t *testing.T) { } func TestClient_Replace_error(t *testing.T) { - client := setupTest(t, "PUT /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", http.StatusBadRequest, "") + client := mockBuilder(). + Route("PUT /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", + servermock.Noop().WithStatusCode(http.StatusBadRequest)). + Build(t) rrSet := RRSet{ Description: "lego", diff --git a/providers/dns/gandi/gandi_test.go b/providers/dns/gandi/gandi_test.go index 36bc4ccd2..4c37fb00e 100644 --- a/providers/dns/gandi/gandi_test.go +++ b/providers/dns/gandi/gandi_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) @@ -119,38 +120,41 @@ func TestDNSProvider(t *testing.T) { cleanupDeleteZoneRequestMock: cleanupDeleteZoneResponseMock, } - fakeKeyAuth := "XXXX" - regexpDate := regexp.MustCompile(`\[ACME Challenge [^\]:]*:[^\]]*\]`) - // start fake RPC server - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "text/xml", r.Header.Get("Content-Type"), "invalid content type") + provider := servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.BaseURL = server.URL + "/" + config.APIKey = "123412341234123412341234" - req, errS := io.ReadAll(r.Body) - require.NoError(t, errS) + return NewDNSProviderConfig(config) + }, + servermock.CheckHeader().WithContentType("text/xml"), + ). + Route("POST /", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + require.Equal(t, "text/xml", req.Header.Get("Content-Type"), "invalid content type") - req = regexpDate.ReplaceAllLiteral(req, []byte(`[ACME Challenge 01 Jan 16 00:00 +0000]`)) - resp, ok := serverResponses[string(req)] - require.Truef(t, ok, "Server response for request not found: %s", string(req)) + body, errS := io.ReadAll(req.Body) + require.NoError(t, errS) - _, errS = io.Copy(w, strings.NewReader(resp)) - require.NoError(t, errS) - })) - t.Cleanup(server.Close) + body = regexpDate.ReplaceAllLiteral(body, []byte(`[ACME Challenge 01 Jan 16 00:00 +0000]`)) + resp, ok := serverResponses[string(body)] + require.Truef(t, ok, "Server response for request not found: %s", string(body)) + + _, errS = io.Copy(rw, strings.NewReader(resp)) + require.NoError(t, errS) + })). + Route("/", servermock.DumpRequest()). + Build(t) + + fakeKeyAuth := "XXXX" // define function to override findZoneByFqdn with fakeFindZoneByFqdn := func(fqdn string) (string, error) { return "example.com.", nil } - config := NewDefaultConfig() - config.BaseURL = server.URL + "/" - config.APIKey = "123412341234123412341234" - - provider, err := NewDNSProviderConfig(config) - require.NoError(t, err) - // override findZoneByFqdn function savedFindZoneByFqdn := provider.findZoneByFqdn t.Cleanup(func() { @@ -159,7 +163,7 @@ func TestDNSProvider(t *testing.T) { provider.findZoneByFqdn = fakeFindZoneByFqdn // run Present - err = provider.Present("abc.def.example.com", "", fakeKeyAuth) + err := provider.Present("abc.def.example.com", "", fakeKeyAuth) require.NoError(t, err) // run CleanUp diff --git a/providers/dns/gandi/internal/client_test.go b/providers/dns/gandi/internal/client_test.go new file mode 100644 index 000000000..573f812fa --- /dev/null +++ b/providers/dns/gandi/internal/client_test.go @@ -0,0 +1,99 @@ +package internal + +import ( + "net/http/httptest" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("secret") + client.BaseURL = server.URL + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader().WithContentType("text/xml"), + ) +} + +func TestClient_GetZoneID(t *testing.T) { + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("get_zone_id.xml"), + servermock.CheckRequestBodyFromFile("get_zone_id-request.xml").IgnoreWhitespace()). + Build(t) + + zoneID, err := client.GetZoneID(t.Context(), "example.com") + require.NoError(t, err) + + assert.Equal(t, 1, zoneID) +} + +func TestClient_CloneZone(t *testing.T) { + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("clone_zone.xml"), + servermock.CheckRequestBodyFromFile("clone_zone-request.xml").IgnoreWhitespace()). + Build(t) + + zoneID, err := client.CloneZone(t.Context(), 6, "foo") + require.NoError(t, err) + + assert.Equal(t, 1, zoneID) +} + +func TestClient_NewZoneVersion(t *testing.T) { + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("new_zone_version.xml"), + servermock.CheckRequestBodyFromFile("new_zone_version-request.xml").IgnoreWhitespace()). + Build(t) + + zoneID, err := client.NewZoneVersion(t.Context(), 6) + require.NoError(t, err) + + assert.Equal(t, 1, zoneID) +} + +func TestClient_AddTXTRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("empty.xml"), + servermock.CheckRequestBodyFromFile("add_txt_record-request.xml").IgnoreWhitespace()). + Build(t) + + err := client.AddTXTRecord(t.Context(), 1, 123, "foo", "content", 120) + require.NoError(t, err) +} + +func TestClient_SetZoneVersion(t *testing.T) { + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("set_zone_version.xml"), + servermock.CheckRequestBodyFromFile("set_zone_version-request.xml").IgnoreWhitespace()). + Build(t) + + err := client.SetZoneVersion(t.Context(), 1, 123) + require.NoError(t, err) +} + +func TestClient_SetZone(t *testing.T) { + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("set_zone.xml"), + servermock.CheckRequestBodyFromFile("set_zone-request.xml").IgnoreWhitespace()). + Build(t) + + err := client.SetZone(t.Context(), "example.com", 1) + require.NoError(t, err) +} + +func TestClient_DeleteZone(t *testing.T) { + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("delete_zone.xml"), + servermock.CheckRequestBodyFromFile("delete_zone-request.xml").IgnoreWhitespace()). + Build(t) + + err := client.DeleteZone(t.Context(), 1) + require.NoError(t, err) +} diff --git a/providers/dns/gandi/internal/fixtures/add_txt_record-request.xml b/providers/dns/gandi/internal/fixtures/add_txt_record-request.xml new file mode 100644 index 000000000..001ee7a33 --- /dev/null +++ b/providers/dns/gandi/internal/fixtures/add_txt_record-request.xml @@ -0,0 +1,49 @@ + + + domain.zone.record.add + + + secret + + + + + 1 + + + + + 123 + + + + + + + type + + TXT + + + + name + + foo + + + + value + + content + + + + ttl + + 120 + + + + + + diff --git a/providers/dns/gandi/internal/fixtures/clone_zone-request.xml b/providers/dns/gandi/internal/fixtures/clone_zone-request.xml new file mode 100644 index 000000000..40ee87c7e --- /dev/null +++ b/providers/dns/gandi/internal/fixtures/clone_zone-request.xml @@ -0,0 +1,31 @@ + + + domain.zone.clone + + + secret + + + + + 6 + + + + + 0 + + + + + + + name + + foo + + + + + + diff --git a/providers/dns/gandi/internal/fixtures/clone_zone.xml b/providers/dns/gandi/internal/fixtures/clone_zone.xml new file mode 100644 index 000000000..2af93526e --- /dev/null +++ b/providers/dns/gandi/internal/fixtures/clone_zone.xml @@ -0,0 +1,22 @@ + + + + + + + id + + 1 + + + + foo + + 2 + + + + + + + diff --git a/providers/dns/gandi/internal/fixtures/delete_zone-request.xml b/providers/dns/gandi/internal/fixtures/delete_zone-request.xml new file mode 100644 index 000000000..0ba9cb766 --- /dev/null +++ b/providers/dns/gandi/internal/fixtures/delete_zone-request.xml @@ -0,0 +1,14 @@ + + + domain.zone.delete + + + secret + + + + + 1 + + + diff --git a/providers/dns/gandi/internal/fixtures/delete_zone.xml b/providers/dns/gandi/internal/fixtures/delete_zone.xml new file mode 100644 index 000000000..28ba00dc5 --- /dev/null +++ b/providers/dns/gandi/internal/fixtures/delete_zone.xml @@ -0,0 +1,9 @@ + + + + + true + + + + diff --git a/providers/dns/gandi/internal/fixtures/empty.xml b/providers/dns/gandi/internal/fixtures/empty.xml new file mode 100644 index 000000000..7843fd723 --- /dev/null +++ b/providers/dns/gandi/internal/fixtures/empty.xml @@ -0,0 +1,2 @@ + + diff --git a/providers/dns/gandi/internal/fixtures/get_zone_id-request.xml b/providers/dns/gandi/internal/fixtures/get_zone_id-request.xml new file mode 100644 index 000000000..173a725d8 --- /dev/null +++ b/providers/dns/gandi/internal/fixtures/get_zone_id-request.xml @@ -0,0 +1,14 @@ + + + domain.info + + + secret + + + + + example.com + + + diff --git a/providers/dns/gandi/internal/fixtures/get_zone_id.xml b/providers/dns/gandi/internal/fixtures/get_zone_id.xml new file mode 100644 index 000000000..2a11e0dff --- /dev/null +++ b/providers/dns/gandi/internal/fixtures/get_zone_id.xml @@ -0,0 +1,22 @@ + + + + + + + zone_id + + 1 + + + + foo + + 2 + + + + + + + diff --git a/providers/dns/gandi/internal/fixtures/new_zone_version-request.xml b/providers/dns/gandi/internal/fixtures/new_zone_version-request.xml new file mode 100644 index 000000000..2fbac82de --- /dev/null +++ b/providers/dns/gandi/internal/fixtures/new_zone_version-request.xml @@ -0,0 +1,14 @@ + + + domain.zone.version.new + + + secret + + + + + 6 + + + diff --git a/providers/dns/gandi/internal/fixtures/new_zone_version.xml b/providers/dns/gandi/internal/fixtures/new_zone_version.xml new file mode 100644 index 000000000..feb84e486 --- /dev/null +++ b/providers/dns/gandi/internal/fixtures/new_zone_version.xml @@ -0,0 +1,9 @@ + + + + + 1 + + + + diff --git a/providers/dns/gandi/internal/fixtures/set_zone-request.xml b/providers/dns/gandi/internal/fixtures/set_zone-request.xml new file mode 100644 index 000000000..71ac843fd --- /dev/null +++ b/providers/dns/gandi/internal/fixtures/set_zone-request.xml @@ -0,0 +1,19 @@ + + + domain.zone.set + + + secret + + + + + example.com + + + + + 1 + + + diff --git a/providers/dns/gandi/internal/fixtures/set_zone.xml b/providers/dns/gandi/internal/fixtures/set_zone.xml new file mode 100644 index 000000000..2a11e0dff --- /dev/null +++ b/providers/dns/gandi/internal/fixtures/set_zone.xml @@ -0,0 +1,22 @@ + + + + + + + zone_id + + 1 + + + + foo + + 2 + + + + + + + diff --git a/providers/dns/gandi/internal/fixtures/set_zone_version-request.xml b/providers/dns/gandi/internal/fixtures/set_zone_version-request.xml new file mode 100644 index 000000000..68a021446 --- /dev/null +++ b/providers/dns/gandi/internal/fixtures/set_zone_version-request.xml @@ -0,0 +1,19 @@ + + + domain.zone.version.set + + + secret + + + + + 1 + + + + + 123 + + + diff --git a/providers/dns/gandi/internal/fixtures/set_zone_version.xml b/providers/dns/gandi/internal/fixtures/set_zone_version.xml new file mode 100644 index 000000000..28ba00dc5 --- /dev/null +++ b/providers/dns/gandi/internal/fixtures/set_zone_version.xml @@ -0,0 +1,9 @@ + + + + + true + + + + diff --git a/providers/dns/gandiv5/gandiv5_test.go b/providers/dns/gandiv5/gandiv5_test.go index 57fed032e..451b1b683 100644 --- a/providers/dns/gandiv5/gandiv5_test.go +++ b/providers/dns/gandiv5/gandiv5_test.go @@ -1,15 +1,11 @@ package gandiv5 import ( - "fmt" - "io" - "net/http" "net/http/httptest" - "regexp" "testing" - "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) @@ -95,81 +91,32 @@ func TestNewDNSProviderConfig(t *testing.T) { // TestDNSProvider runs Present and CleanUp against a fake Gandi RPC // Server, whose responses are predetermined for particular requests. func TestDNSProvider(t *testing.T) { - // serverResponses is the JSON Request->Response map used by the - // fake JSON server. - serverResponses := map[string]map[string]string{ - http.MethodGet: { - ``: `{"rrset_ttl":300,"rrset_values":[],"rrset_name":"_acme-challenge.abc.def","rrset_type":"TXT"}`, + provider := servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.PersonalAccessToken = "123412341234123412341234" + config.BaseURL = server.URL + + return NewDNSProviderConfig(config) }, - http.MethodPut: { - `{"rrset_ttl":300,"rrset_values":["TOKEN"]}`: `{"message": "Zone Record Created"}`, - }, - http.MethodDelete: { - ``: ``, - }, - } + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Bearer 123412341234123412341234"), + ). + Route("GET /domains/example.com/records/_acme-challenge.abc.def/TXT", + servermock.RawStringResponse(`{"rrset_ttl":300,"rrset_values":[],"rrset_name":"_acme-challenge.abc.def","rrset_type":"TXT"}`)). + Route("PUT /domains/example.com/records/_acme-challenge.abc.def/TXT", + servermock.RawStringResponse(`{"message": "Zone Record Created"}`), + servermock.CheckRequestJSONBody(`{"rrset_ttl":300,"rrset_values":["ezRpBPY8wH8djMLYjX2uCKPwiKDkFZ1SFMJ6ZXGlHrQ"]}`)). + Route("DELETE /domains/example.com/records/_acme-challenge.abc.def/TXT", nil). + Build(t) fakeKeyAuth := "XXXX" - regexpToken := regexp.MustCompile(`"rrset_values":\[".+"\]`) - - // start fake RPC server - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/domains/example.com/records/_acme-challenge.abc.def/TXT", func(rw http.ResponseWriter, req *http.Request) { - log.Infof("request: %s %s", req.Method, req.URL) - - if req.Header.Get("Authorization") != "Bearer 123412341234123412341234" { - http.Error(rw, `{"message": "missing or malformed Authorization"}`, http.StatusUnauthorized) - return - } - - if req.Method == http.MethodPost && req.Header.Get("Content-Type") != "application/json" { - http.Error(rw, `{"message": "invalid content type"}`, http.StatusBadRequest) - return - } - - body, errS := io.ReadAll(req.Body) - if errS != nil { - http.Error(rw, fmt.Sprintf(`{"message": "read body error: %v"}`, errS), http.StatusInternalServerError) - return - } - - body = regexpToken.ReplaceAllLiteral(body, []byte(`"rrset_values":["TOKEN"]`)) - - responses, ok := serverResponses[req.Method] - if !ok { - http.Error(rw, fmt.Sprintf(`{"message": "Server response for request not found: %#q"}`, string(body)), http.StatusInternalServerError) - return - } - - resp := responses[string(body)] - - _, errS = rw.Write([]byte(resp)) - if errS != nil { - http.Error(rw, fmt.Sprintf(`{"message": "failed to write response: %v"}`, errS), http.StatusInternalServerError) - return - } - }) - mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { - log.Infof("request: %s %s", req.Method, req.URL) - http.Error(rw, fmt.Sprintf(`{"message": "URL doesn't match: %s"}`, req.URL), http.StatusNotFound) - }) - // define function to override findZoneByFqdn with fakeFindZoneByFqdn := func(fqdn string) (string, error) { return "example.com.", nil } - config := NewDefaultConfig() - config.PersonalAccessToken = "123412341234123412341234" - config.BaseURL = server.URL - - provider, err := NewDNSProviderConfig(config) - require.NoError(t, err) - // override findZoneByFqdn function savedFindZoneByFqdn := provider.findZoneByFqdn defer func() { @@ -178,7 +125,7 @@ func TestDNSProvider(t *testing.T) { provider.findZoneByFqdn = fakeFindZoneByFqdn // run Present - err = provider.Present("abc.def.example.com", "", fakeKeyAuth) + err := provider.Present("abc.def.example.com", "", fakeKeyAuth) require.NoError(t, err) // run CleanUp diff --git a/providers/dns/gandiv5/internal/client_test.go b/providers/dns/gandiv5/internal/client_test.go new file mode 100644 index 000000000..2465566f9 --- /dev/null +++ b/providers/dns/gandiv5/internal/client_test.go @@ -0,0 +1,48 @@ +package internal + +import ( + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("secret", "xxx") + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + With("X-Api-Key", "secret"). + WithAuthorization("Bearer xxx"), + ) +} + +func TestClient_AddTXTRecord(t *testing.T) { + client := mockBuilder(). + Route("GET /domains/example.com/records/foo/TXT", + servermock.ResponseFromFixture("add_txt_record_get.json")). + Route("PUT /domains/example.com/records/foo/TXT", + servermock.ResponseFromFixture("api_response.json"), + servermock.CheckRequestJSONBody(`{"rrset_ttl":120,"rrset_values":["content","value1"]}`)). + Build(t) + + err := client.AddTXTRecord(t.Context(), "example.com", "foo", "content", 120) + require.NoError(t, err) +} + +func TestClient_DeleteTXTRecord(t *testing.T) { + client := mockBuilder(). + Route("DELETE /domains/example.com/records/foo/TXT", + servermock.ResponseFromFixture("api_response.json")). + Build(t) + + err := client.DeleteTXTRecord(t.Context(), "example.com", "foo") + require.NoError(t, err) +} diff --git a/providers/dns/gandiv5/internal/fixtures/add_txt_record_get.json b/providers/dns/gandiv5/internal/fixtures/add_txt_record_get.json new file mode 100644 index 000000000..fead6ab0a --- /dev/null +++ b/providers/dns/gandiv5/internal/fixtures/add_txt_record_get.json @@ -0,0 +1,8 @@ +{ + "rrset_ttl": 120, + "rrset_values": [ + "value1" + ], + "rrset_name": "foo", + "rrset_type": "TXT" +} diff --git a/providers/dns/gandiv5/internal/fixtures/api_response.json b/providers/dns/gandiv5/internal/fixtures/api_response.json new file mode 100644 index 000000000..47f4352ff --- /dev/null +++ b/providers/dns/gandiv5/internal/fixtures/api_response.json @@ -0,0 +1,4 @@ +{ + "message": "test", + "uuid": "123456789" +} diff --git a/providers/dns/gcloud/googlecloud_test.go b/providers/dns/gcloud/googlecloud_test.go index 15c61556c..7fda2f8f6 100644 --- a/providers/dns/gcloud/googlecloud_test.go +++ b/providers/dns/gcloud/googlecloud_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" "golang.org/x/oauth2/google" "google.golang.org/api/dns/v1" @@ -144,245 +145,160 @@ func TestNewDNSProviderConfig(t *testing.T) { } func TestPresentNoExistingRR(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + provider := mockBuilder(). + // getHostedZone + Route("GET /dns/v1/projects/manhattan/managedZones", + servermock.JSONEncode(&dns.ManagedZonesListResponse{ + ManagedZones: []*dns.ManagedZone{ + {Name: "test", Visibility: "public"}, + }, + }), + servermock.CheckQueryParameter().Strict(). + With("dnsName", "lego.wtf."). + With("prettyPrint", "false"). + With("alt", "json")). + // findTxtRecords + Route("GET /dns/v1/projects/manhattan/managedZones/test/rrsets", + servermock.JSONEncode(&dns.ResourceRecordSetsListResponse{ + Rrsets: []*dns.ResourceRecordSet{}, + }), + servermock.CheckQueryParameter().Strict(). + With("name", "_acme-challenge.lego.wtf."). + With("type", "TXT"). + With("prettyPrint", "false"). + With("alt", "json")). + // applyChanges [Create] + Route("POST /dns/v1/projects/manhattan/managedZones/test/changes", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + var chgReq dns.Change + if err := json.NewDecoder(req.Body).Decode(&chgReq); err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } - // getHostedZone: /manhattan/managedZones?alt=json&dnsName=lego.wtf. - mux.HandleFunc("/dns/v1/projects/manhattan/managedZones", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } + chgResp := chgReq + chgResp.Status = changeStatusDone - mzlrs := &dns.ManagedZonesListResponse{ - ManagedZones: []*dns.ManagedZone{ - {Name: "test", Visibility: "public"}, - }, - } - - err := json.NewEncoder(w).Encode(mzlrs) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - // findTxtRecords: /manhattan/managedZones/test/rrsets?alt=json&name=_acme-challenge.lego.wtf.&type=TXT - mux.HandleFunc("/dns/v1/projects/manhattan/managedZones/test/rrsets", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - rrslr := &dns.ResourceRecordSetsListResponse{ - Rrsets: []*dns.ResourceRecordSet{}, - } - - err := json.NewEncoder(w).Encode(rrslr) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - // applyChanges [Create]: /manhattan/managedZones/test/changes?alt=json - mux.HandleFunc("/dns/v1/projects/manhattan/managedZones/test/changes", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - var chgReq dns.Change - if err := json.NewDecoder(r.Body).Decode(&chgReq); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - chgResp := chgReq - chgResp.Status = changeStatusDone - - if err := json.NewEncoder(w).Encode(chgResp); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - config := NewDefaultConfig() - config.HTTPClient = &http.Client{Timeout: 10 * time.Second} - config.Project = "manhattan" - - p, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - p.client.BasePath = server.URL + if err := json.NewEncoder(rw).Encode(chgResp); err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + }), + servermock.CheckQueryParameter().Strict(). + With("prettyPrint", "false"). + With("alt", "json")). + Build(t) domain := "lego.wtf" - err = p.Present(domain, "", "") + err := provider.Present(domain, "", "") require.NoError(t, err) } func TestPresentWithExistingRR(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - // getHostedZone: /manhattan/managedZones?alt=json&dnsName=lego.wtf. - mux.HandleFunc("/dns/v1/projects/manhattan/managedZones", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - mzlrs := &dns.ManagedZonesListResponse{ - ManagedZones: []*dns.ManagedZone{ - {Name: "test", Visibility: "public"}, - }, - } - - err := json.NewEncoder(w).Encode(mzlrs) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - // findTxtRecords: /manhattan/managedZones/test/rrsets?alt=json&name=_acme-challenge.lego.wtf.&type=TXT - mux.HandleFunc("/dns/v1/projects/manhattan/managedZones/test/rrsets", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - rrslr := &dns.ResourceRecordSetsListResponse{ - Rrsets: []*dns.ResourceRecordSet{{ - Name: "_acme-challenge.lego.wtf.", - Rrdatas: []string{`"X7DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"`, `"huji"`}, - Ttl: 120, - Type: "TXT", - }}, - } - - err := json.NewEncoder(w).Encode(rrslr) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - // applyChanges [Create]: /manhattan/managedZones/test/changes?alt=json - mux.HandleFunc("/dns/v1/projects/manhattan/managedZones/test/changes", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - var chgReq dns.Change - if err := json.NewDecoder(r.Body).Decode(&chgReq); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - if len(chgReq.Additions) > 0 { - sort.Strings(chgReq.Additions[0].Rrdatas) - } - - var prevVal string - for _, addition := range chgReq.Additions { - for _, value := range addition.Rrdatas { - if prevVal == value { - http.Error(w, fmt.Sprintf("The resource %s already exists", value), http.StatusConflict) + provider := mockBuilder(). + // getHostedZone + Route("GET /dns/v1/projects/manhattan/managedZones", + servermock.JSONEncode(&dns.ManagedZonesListResponse{ + ManagedZones: []*dns.ManagedZone{ + {Name: "test", Visibility: "public"}, + }, + }), + servermock.CheckQueryParameter().Strict(). + With("dnsName", "lego.wtf."). + With("prettyPrint", "false"). + With("alt", "json")). + // findTxtRecords + Route("GET /dns/v1/projects/manhattan/managedZones/test/rrsets", + servermock.JSONEncode(&dns.ResourceRecordSetsListResponse{ + Rrsets: []*dns.ResourceRecordSet{{ + Name: "_acme-challenge.lego.wtf.", + Rrdatas: []string{`"X7DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"`, `"huji"`}, + Ttl: 120, + Type: "TXT", + }}, + }), + servermock.CheckQueryParameter().Strict(). + With("name", "_acme-challenge.lego.wtf."). + With("type", "TXT"). + With("prettyPrint", "false"). + With("alt", "json")). + // applyChanges [Create] + Route("POST /dns/v1/projects/manhattan/managedZones/test/changes", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + var chgReq dns.Change + if err := json.NewDecoder(req.Body).Decode(&chgReq); err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) return } - prevVal = value - } - } - chgResp := chgReq - chgResp.Status = changeStatusDone + if len(chgReq.Additions) > 0 { + sort.Strings(chgReq.Additions[0].Rrdatas) + } - if err := json.NewEncoder(w).Encode(chgResp); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + var prevVal string + for _, addition := range chgReq.Additions { + for _, value := range addition.Rrdatas { + if prevVal == value { + http.Error(rw, fmt.Sprintf("The resource %s already exists", value), http.StatusConflict) + return + } + prevVal = value + } + } - config := NewDefaultConfig() - config.HTTPClient = &http.Client{Timeout: 10 * time.Second} - config.Project = "manhattan" + chgResp := chgReq + chgResp.Status = changeStatusDone - p, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - p.client.BasePath = server.URL + if err := json.NewEncoder(rw).Encode(chgResp); err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + }), + servermock.CheckQueryParameter().Strict(). + With("prettyPrint", "false"). + With("alt", "json")). + Build(t) domain := "lego.wtf" - err = p.Present(domain, "", "") + err := provider.Present(domain, "", "") require.NoError(t, err) } func TestPresentSkipExistingRR(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - // getHostedZone: /manhattan/managedZones?alt=json&dnsName=lego.wtf. - mux.HandleFunc("/dns/v1/projects/manhattan/managedZones", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - mzlrs := &dns.ManagedZonesListResponse{ - ManagedZones: []*dns.ManagedZone{ - {Name: "test", Visibility: "public"}, - }, - } - - err := json.NewEncoder(w).Encode(mzlrs) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - // findTxtRecords: /manhattan/managedZones/test/rrsets?alt=json&name=_acme-challenge.lego.wtf.&type=TXT - mux.HandleFunc("/dns/v1/projects/manhattan/managedZones/test/rrsets", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - rrslr := &dns.ResourceRecordSetsListResponse{ - Rrsets: []*dns.ResourceRecordSet{{ - Name: "_acme-challenge.lego.wtf.", - Rrdatas: []string{`"47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"`, `"X7DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"`, `"huji"`}, - Ttl: 120, - Type: "TXT", - }}, - } - - err := json.NewEncoder(w).Encode(rrslr) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - config := NewDefaultConfig() - config.HTTPClient = &http.Client{Timeout: 10 * time.Second} - config.Project = "manhattan" - - p, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - p.client.BasePath = server.URL + provider := mockBuilder(). + // getHostedZone + Route("GET /dns/v1/projects/manhattan/managedZones", + servermock.JSONEncode(&dns.ManagedZonesListResponse{ + ManagedZones: []*dns.ManagedZone{ + {Name: "test", Visibility: "public"}, + }, + }), + servermock.CheckQueryParameter().Strict(). + With("dnsName", "lego.wtf."). + With("prettyPrint", "false"). + With("alt", "json")). + // findTxtRecords + Route("GET /dns/v1/projects/manhattan/managedZones/test/rrsets", + servermock.JSONEncode(&dns.ResourceRecordSetsListResponse{ + Rrsets: []*dns.ResourceRecordSet{{ + Name: "_acme-challenge.lego.wtf.", + Rrdatas: []string{`"47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"`, `"X7DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"`, `"huji"`}, + Ttl: 120, + Type: "TXT", + }}, + }), + servermock.CheckQueryParameter().Strict(). + With("name", "_acme-challenge.lego.wtf."). + With("type", "TXT"). + With("prettyPrint", "false"). + With("alt", "json")). + Build(t) domain := "lego.wtf" - err = p.Present(domain, "", "") + err := provider.Present(domain, "", "") require.NoError(t, err) } @@ -432,3 +348,20 @@ func TestLiveCleanUp(t *testing.T) { err = provider.CleanUp(envTest.GetDomain(), "", "123d==") require.NoError(t, err) } + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.HTTPClient = &http.Client{Timeout: 10 * time.Second} + config.Project = "manhattan" + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BasePath = server.URL + + return p, err + }) +} diff --git a/providers/dns/gcore/internal/client_test.go b/providers/dns/gcore/internal/client_test.go index 21bdb8e05..4a0f83311 100644 --- a/providers/dns/gcore/internal/client_test.go +++ b/providers/dns/gcore/internal/client_test.go @@ -1,47 +1,41 @@ package internal import ( - "encoding/json" - "fmt" "net/http" "net/http/httptest" "net/url" - "reflect" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const ( - testToken = "test" - testRecordContent = "acme" - testRecordContent2 = "foo" - testTTL = 10 + testToken = "test" + testRecordContent = "acme" + testTTL = 10 ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(testToken) + client.baseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient(testToken) - client.baseURL, _ = url.Parse(server.URL) - - return client, mux + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders()) } func TestClient_GetZone(t *testing.T) { - client, mux := setupTest(t) - expected := Zone{Name: "example.com"} - mux.Handle("/v2/zones/example.com", validationHandler{ - method: http.MethodGet, - next: handleJSONResponse(expected), - }) + client := mockBuilder(). + Route("GET /v2/zones/example.com", + servermock.JSONEncode(expected)). + Build(t) zone, err := client.GetZone(t.Context(), "example.com") require.NoError(t, err) @@ -50,20 +44,16 @@ func TestClient_GetZone(t *testing.T) { } func TestClient_GetZone_error(t *testing.T) { - client, mux := setupTest(t) - - mux.Handle("/v2/zones/example.com", validationHandler{ - method: http.MethodGet, - next: handleAPIError(), - }) + client := mockBuilder(). + Route("GET /v2/zones/example.com", + servermock.JSONEncode(APIError{Message: "oops"}).WithStatusCode(http.StatusInternalServerError)). + Build(t) _, err := client.GetZone(t.Context(), "example.com") - require.Error(t, err) + require.EqualError(t, err, "get zone example.com: 500: oops") } func TestClient_GetRRSet(t *testing.T) { - client, mux := setupTest(t) - expected := RRSet{ TTL: testTTL, Records: []Records{ @@ -71,10 +61,10 @@ func TestClient_GetRRSet(t *testing.T) { }, } - mux.Handle("/v2/zones/example.com/foo.example.com/TXT", validationHandler{ - method: http.MethodGet, - next: handleJSONResponse(expected), - }) + client := mockBuilder(). + Route("GET /v2/zones/example.com/foo.example.com/TXT", + servermock.JSONEncode(expected)). + Build(t) rrSet, err := client.GetRRSet(t.Context(), "example.com", "foo.example.com") require.NoError(t, err) @@ -83,173 +73,93 @@ func TestClient_GetRRSet(t *testing.T) { } func TestClient_GetRRSet_error(t *testing.T) { - client, mux := setupTest(t) - - mux.Handle("/v2/zones/example.com/foo.example.com/TXT", validationHandler{ - method: http.MethodGet, - next: handleAPIError(), - }) + client := mockBuilder(). + Route("GET /v2/zones/example.com/foo.example.com/TXT", + servermock.JSONEncode(APIError{Message: "oops"}).WithStatusCode(http.StatusInternalServerError)). + Build(t) _, err := client.GetRRSet(t.Context(), "example.com", "foo.example.com") - require.Error(t, err) + require.EqualError(t, err, "get txt records example.com -> foo.example.com: 500: oops") } func TestClient_DeleteRRSet(t *testing.T) { - client, mux := setupTest(t) - - mux.Handle("/v2/zones/test.example.com/my.test.example.com/"+txtRecordType, - validationHandler{method: http.MethodDelete}) + client := mockBuilder(). + Route("DELETE /v2/zones/test.example.com/my.test.example.com/TXT", nil). + Build(t) err := client.DeleteRRSet(t.Context(), "test.example.com", "my.test.example.com.") require.NoError(t, err) } func TestClient_DeleteRRSet_error(t *testing.T) { - client, mux := setupTest(t) - - mux.Handle("/v2/zones/test.example.com/my.test.example.com/"+txtRecordType, validationHandler{ - method: http.MethodDelete, - next: handleAPIError(), - }) + client := mockBuilder(). + Route("DELETE /v2/zones/test.example.com/my.test.example.com/TXT", + servermock.JSONEncode(APIError{Message: "oops"}).WithStatusCode(http.StatusInternalServerError)). + Build(t) err := client.DeleteRRSet(t.Context(), "test.example.com", "my.test.example.com.") require.NoError(t, err) } -func TestClient_AddRRSet(t *testing.T) { - testCases := []struct { - desc string - zone string - recordName string - value string - handledDomain string - handlers map[string]http.Handler - wantErr bool - }{ - { - desc: "success add", - zone: "test.example.com", - recordName: "my.test.example.com", - value: testRecordContent, - handlers: map[string]http.Handler{ - // createRRSet - "/v2/zones/test.example.com/my.test.example.com/" + txtRecordType: validationHandler{ - method: http.MethodPost, - next: handleAddRRSet([]Records{{Content: []string{testRecordContent}}}), - }, - }, - }, - { - desc: "success update", - zone: "test.example.com", - recordName: "my.test.example.com", - value: testRecordContent, - handlers: map[string]http.Handler{ - "/v2/zones/test.example.com/my.test.example.com/" + txtRecordType: http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - switch req.Method { - case http.MethodGet: // GetRRSet - data := RRSet{ - TTL: testTTL, - Records: []Records{{Content: []string{testRecordContent2}}}, - } - handleJSONResponse(data).ServeHTTP(rw, req) - case http.MethodPut: // updateRRSet - expected := []Records{ - {Content: []string{testRecordContent}}, - {Content: []string{testRecordContent2}}, - } - handleAddRRSet(expected).ServeHTTP(rw, req) - default: - http.Error(rw, "wrong method", http.StatusMethodNotAllowed) - } - }), - }, - }, - { - desc: "not in the zone", - zone: "test.example.com", - recordName: "notfound.example.com", - value: testRecordContent, - wantErr: true, - }, - } +func TestClient_AddRRSet_add(t *testing.T) { + client := mockBuilder(). + // GetRRSet + Route("GET /v2/zones/test.example.com/my.test.example.com/TXT", + servermock.JSONEncode(APIError{Message: "not found"}).WithStatusCode(http.StatusBadRequest)). + // createRRSet + Route("POST /v2/zones/test.example.com/my.test.example.com/TXT", + servermock.JSONEncode([]Records{{Content: []string{testRecordContent}}}), + servermock.CheckRequestJSONBody(`{"ttl":10,"resource_records":[{"content":["acme"]}]}`)). + Build(t) - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - cl, mux := setupTest(t) - - for pattern, handler := range test.handlers { - mux.Handle(pattern, handler) - } - - err := cl.AddRRSet(t.Context(), test.zone, test.recordName, test.value, testTTL) - if test.wantErr { - require.Error(t, err) - return - } - - require.NoError(t, err) - }) - } + err := client.AddRRSet(t.Context(), "test.example.com", "my.test.example.com", testRecordContent, testTTL) + require.NoError(t, err) } -type validationHandler struct { - method string - next http.Handler +func TestClient_AddRRSet_add_error(t *testing.T) { + client := mockBuilder(). + // GetRRSet + Route("GET /v2/zones/test.example.com/my.test.example.com/TXT", + servermock.JSONEncode(APIError{Message: "not found"}).WithStatusCode(http.StatusBadRequest)). + // createRRSet + Route("POST /v2/zones/test.example.com/my.test.example.com/TXT", + servermock.JSONEncode(APIError{Message: "oops"}).WithStatusCode(http.StatusBadRequest)). + Build(t) + + err := client.AddRRSet(t.Context(), "test.example.com", "my.test.example.com", testRecordContent, testTTL) + require.EqualError(t, err, "400: oops") } -func (v validationHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - if req.Header.Get(authorizationHeader) != fmt.Sprintf("%s %s", tokenTypeHeader, testToken) { - rw.WriteHeader(http.StatusForbidden) - _ = json.NewEncoder(rw).Encode(APIError{Message: "token up for parsing was not passed through the context"}) - return - } +func TestClient_AddRRSet_update(t *testing.T) { + client := mockBuilder(). + // GetRRSet + Route("GET /v2/zones/test.example.com/my.test.example.com/TXT", + servermock.JSONEncode(RRSet{ + TTL: testTTL, + Records: []Records{{Content: []string{"foo"}}}, + })). + // updateRRSet + Route("PUT /v2/zones/test.example.com/my.test.example.com/TXT", nil, + servermock.CheckRequestJSONBody(`{"ttl":10,"resource_records":[{"content":["acme"]},{"content":["foo"]}]}`)). + Build(t) - if req.Method != v.method { - http.Error(rw, "wrong method", http.StatusMethodNotAllowed) - return - } - - if v.next != nil { - v.next.ServeHTTP(rw, req) - } + err := client.AddRRSet(t.Context(), "test.example.com", "my.test.example.com", testRecordContent, testTTL) + require.NoError(t, err) } -func handleAPIError() http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - rw.WriteHeader(http.StatusInternalServerError) - _ = json.NewEncoder(rw).Encode(APIError{Message: "oops"}) - } -} - -func handleJSONResponse(data any) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - err := json.NewEncoder(rw).Encode(data) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } -} - -func handleAddRRSet(expected []Records) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - body := RRSet{} - - err := json.NewDecoder(req.Body).Decode(&body) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - if body.TTL != testTTL { - http.Error(rw, "wrong ttl", http.StatusInternalServerError) - return - } - - if !reflect.DeepEqual(body.Records, expected) { - http.Error(rw, "wrong resource records", http.StatusInternalServerError) - return - } - } +func TestClient_AddRRSet_update_error(t *testing.T) { + client := mockBuilder(). + // GetRRSet + Route("GET /v2/zones/test.example.com/my.test.example.com/TXT", + servermock.JSONEncode(RRSet{ + TTL: testTTL, + Records: []Records{{Content: []string{"foo"}}}, + })). + // updateRRSet + Route("PUT /v2/zones/test.example.com/my.test.example.com/TXT", + servermock.JSONEncode(APIError{Message: "oops"}).WithStatusCode(http.StatusBadRequest)). + Build(t) + + err := client.AddRRSet(t.Context(), "test.example.com", "my.test.example.com", testRecordContent, testTTL) + require.EqualError(t, err, "400: oops") } diff --git a/providers/dns/glesys/internal/client_test.go b/providers/dns/glesys/internal/client_test.go index ab30f9516..cd71757ff 100644 --- a/providers/dns/glesys/internal/client_test.go +++ b/providers/dns/glesys/internal/client_test.go @@ -1,68 +1,35 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, method, pattern string, status int, file string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret") + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusBadRequest) - return - } - - apiUser, apiKey, ok := req.BasicAuth() - if apiUser != "user" || apiKey != "secret" || !ok { - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - if file == "" { - rw.WriteHeader(status) - return - } - - open, err := os.Open(filepath.Join("fixtures", file)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client := NewClient("user", "secret") - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithBasicAuth("user", "secret"), + ) } func TestClient_AddTXTRecord(t *testing.T) { - client := setupTest(t, http.MethodPost, "/domain/addrecord", http.StatusOK, "add-record.json") + client := mockBuilder(). + Route("POST /domain/addrecord", + servermock.ResponseFromFixture("add-record.json"), + servermock.CheckRequestJSONBody(`{"domainname":"example.com","host":"foo","type":"TXT","data":"txt","ttl":120}`)). + Build(t) recordID, err := client.AddTXTRecord(t.Context(), "example.com", "foo", "txt", 120) require.NoError(t, err) @@ -71,7 +38,11 @@ func TestClient_AddTXTRecord(t *testing.T) { } func TestClient_DeleteTXTRecord(t *testing.T) { - client := setupTest(t, http.MethodPost, "/domain/deleterecord", http.StatusOK, "delete-record.json") + client := mockBuilder(). + Route("POST /domain/deleterecord", + servermock.ResponseFromFixture("delete-record.json"), + servermock.CheckRequestJSONBody(`{"recordid":123}`)). + Build(t) err := client.DeleteTXTRecord(t.Context(), 123) require.NoError(t, err) diff --git a/providers/dns/godaddy/internal/client_test.go b/providers/dns/godaddy/internal/client_test.go index 64bf2b388..741a55f3e 100644 --- a/providers/dns/godaddy/internal/client_test.go +++ b/providers/dns/godaddy/internal/client_test.go @@ -1,37 +1,33 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("key", "secret") + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient("key", "secret") - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client, mux + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("sso-key key:secret")) } func TestClient_GetRecords(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/example.com/records/TXT/", testHandler(http.MethodGet, http.StatusOK, "getrecords.json")) + client := mockBuilder(). + Route("GET /v1/domains/example.com/records/TXT/", servermock.ResponseFromFixture("getrecords.json")). + Build(t) records, err := client.GetRecords(t.Context(), "example.com", "TXT", "") require.NoError(t, err) @@ -49,9 +45,10 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_GetRecords_errors(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/example.com/records/TXT/", testHandler(http.MethodGet, http.StatusUnprocessableEntity, "errors.json")) + client := mockBuilder(). + Route("GET /v1/domains/example.com/records/TXT/", + servermock.ResponseFromFixture("errors.json").WithStatusCode(http.StatusUnprocessableEntity)). + Build(t) records, err := client.GetRecords(t.Context(), "example.com", "TXT", "") require.EqualError(t, err, "[status code: 422] INVALID_BODY: Request body doesn't fulfill schema, see details in `fields`") @@ -59,20 +56,10 @@ func TestClient_GetRecords_errors(t *testing.T) { } func TestClient_UpdateTxtRecords(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/example.com/records/TXT/lego", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPut { - http.Error(rw, fmt.Sprintf(`{"message":"unsupported method: %s"}`, req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get(authorizationHeader) - if auth != "sso-key key:secret" { - http.Error(rw, fmt.Sprintf("invalid API key or secret: %s", auth), http.StatusUnauthorized) - return - } - }) + client := mockBuilder(). + Route("PUT /v1/domains/example.com/records/TXT/lego", nil, + servermock.CheckRequestJSONBodyFromFile("update_records-request.json")). + Build(t) records := []DNSRecord{ {Name: "_acme-challenge", Type: "TXT", Data: " ", TTL: 600}, @@ -88,10 +75,11 @@ func TestClient_UpdateTxtRecords(t *testing.T) { } func TestClient_UpdateTxtRecords_errors(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/example.com/records/TXT/lego", - testHandler(http.MethodPut, http.StatusUnprocessableEntity, "errors.json")) + client := mockBuilder(). + Route("PUT /v1/domains/example.com/records/TXT/lego", + servermock.ResponseFromFixture("errors.json").WithStatusCode(http.StatusUnprocessableEntity), + servermock.CheckRequestJSONBodyFromFile("update_records-request.json")). + Build(t) records := []DNSRecord{ {Name: "_acme-challenge", Type: "TXT", Data: " ", TTL: 600}, @@ -107,54 +95,21 @@ func TestClient_UpdateTxtRecords_errors(t *testing.T) { } func TestClient_DeleteTxtRecords(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/example.com/records/TXT/foo", testHandler(http.MethodDelete, http.StatusNoContent, "")) + client := mockBuilder(). + Route("DELETE /v1/domains/example.com/records/TXT/foo", + servermock.Noop().WithStatusCode(http.StatusNoContent)). + Build(t) err := client.DeleteTxtRecords(t.Context(), "example.com", "foo") require.NoError(t, err) } func TestClient_DeleteTxtRecords_errors(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/domains/example.com/records/TXT/foo", testHandler(http.MethodDelete, http.StatusConflict, "error-extended.json")) + client := mockBuilder(). + Route("DELETE /v1/domains/example.com/records/TXT/foo", + servermock.ResponseFromFixture("error-extended.json").WithStatusCode(http.StatusConflict)). + Build(t) err := client.DeleteTxtRecords(t.Context(), "example.com", "foo") require.EqualError(t, err, "[status code: 409] ACCESS_DENIED: Authenticated user is not allowed access [test: content (path=/foo) (pathRelated=/bar)]") } - -func testHandler(method string, statusCode int, filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf(`{"message":"unsupported method: %s"}`, req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get(authorizationHeader) - if auth != "sso-key key:secret" { - http.Error(rw, fmt.Sprintf("invalid API key or secret: %s", auth), http.StatusUnauthorized) - return - } - - rw.WriteHeader(statusCode) - - if statusCode == http.StatusNoContent { - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, fmt.Sprintf(`{"message":"%v"}`, err), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, fmt.Sprintf(`{"message":"%v"}`, err), http.StatusInternalServerError) - return - } - } -} diff --git a/providers/dns/godaddy/internal/fixtures/update_records-request.json b/providers/dns/godaddy/internal/fixtures/update_records-request.json new file mode 100644 index 000000000..969afb2dc --- /dev/null +++ b/providers/dns/godaddy/internal/fixtures/update_records-request.json @@ -0,0 +1,38 @@ +[ + { + "name": "_acme-challenge", + "type": "TXT", + "data": " ", + "ttl": 600 + }, + { + "name": "_acme-challenge.example", + "type": "TXT", + "data": "6rrai7-jm7l3PxL4WGmGoS6VMeefSHx24r-qCvUIOxU", + "ttl": 600 + }, + { + "name": "_acme-challenge.example", + "type": "TXT", + "data": "8Axt-PXQvjOVD2oi2YXqyyn8U5CDcC8P-BphlCxk3Ek", + "ttl": 600 + }, + { + "name": "_acme-challenge.lego", + "type": "TXT", + "data": " ", + "ttl": 600 + }, + { + "name": "_acme-challenge.lego", + "type": "TXT", + "data": "0Ad60wO_yxxJPFPb1deir6lQ37FPLeA02YCobo7Qm8A", + "ttl": 600 + }, + { + "name": "_acme-challenge.lego", + "type": "TXT", + "data": "acme", + "ttl": 600 + } +] diff --git a/providers/dns/hetzner/internal/client_test.go b/providers/dns/hetzner/internal/client_test.go index fe9f992fb..d301493a9 100644 --- a/providers/dns/hetzner/internal/client_test.go +++ b/providers/dns/hetzner/internal/client_test.go @@ -1,107 +1,60 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" - "os" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, apiKey string) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder(apiKey string) *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(apiKey) + client.baseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient(apiKey) - client.baseURL, _ = url.Parse(server.URL) - client.HTTPClient = server.Client() - - return client, mux + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + With(authHeader, apiKey)) } func TestClient_GetTxtRecord(t *testing.T) { const zoneID = "zoneA" - const apiKey = "myKeyA" - client, mux := setupTest(t, apiKey) - - mux.HandleFunc("/api/v1/records", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get(authHeader) - if auth != apiKey { - http.Error(rw, fmt.Sprintf("invalid API key: %s", auth), http.StatusUnauthorized) - return - } - - zID := req.URL.Query().Get("zone_id") - if zID != zoneID { - http.Error(rw, fmt.Sprintf("invalid zone ID: %s", zID), http.StatusBadRequest) - return - } - - file, err := os.Open("./fixtures/get_txt_record.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder("myKeyA"). + Route("GET /api/v1/records", servermock.ResponseFromFixture("get_txt_record.json"), + servermock.CheckQueryParameter().Strict(). + With("zone_id", zoneID)). + Build(t) record, err := client.GetTxtRecord(t.Context(), "test1", "txttxttxt", zoneID) require.NoError(t, err) - fmt.Println(record) + expected := &DNSRecord{ + ID: "1b", + Name: "test1", + Type: "TXT", + Value: "txttxttxt", + Priority: 0, + TTL: 600, + ZoneID: "zoneA", + } + + assert.Equal(t, expected, record) } func TestClient_CreateRecord(t *testing.T) { const zoneID = "zoneA" - const apiKey = "myKeyB" - client, mux := setupTest(t, apiKey) - - mux.HandleFunc("/api/v1/records", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get(authHeader) - if auth != apiKey { - http.Error(rw, fmt.Sprintf("invalid API key: %s", auth), http.StatusUnauthorized) - return - } - - file, err := os.Open("./fixtures/create_txt_record.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder("myKeyB"). + Route("POST /api/v1/records", servermock.ResponseFromFixture("create_txt_record.json"), + servermock.CheckRequestJSONBodyFromFile("create_txt_record-request.json")). + Build(t) record := DNSRecord{ Name: "test", @@ -116,57 +69,18 @@ func TestClient_CreateRecord(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - const apiKey = "myKeyC" - - client, mux := setupTest(t, apiKey) - - mux.HandleFunc("/api/v1/records/recordID", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get(authHeader) - if auth != apiKey { - http.Error(rw, fmt.Sprintf("invalid API key: %s", auth), http.StatusUnauthorized) - return - } - }) + client := mockBuilder("myKeyC"). + Route("DELETE /api/v1/records/recordID", nil). + Build(t) err := client.DeleteRecord(t.Context(), "recordID") require.NoError(t, err) } func TestClient_GetZoneID(t *testing.T) { - const apiKey = "myKeyD" - - client, mux := setupTest(t, apiKey) - - mux.HandleFunc("/api/v1/zones", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get(authHeader) - if auth != apiKey { - http.Error(rw, fmt.Sprintf("invalid API key: %s", auth), http.StatusUnauthorized) - return - } - - file, err := os.Open("./fixtures/get_zone_id.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder("myKeyD"). + Route("GET /api/v1/zones", servermock.ResponseFromFixture("get_zone_id.json")). + Build(t) zoneID, err := client.GetZoneID(t.Context(), "example.com") require.NoError(t, err) diff --git a/providers/dns/hetzner/internal/fixtures/create_txt_record-request.json b/providers/dns/hetzner/internal/fixtures/create_txt_record-request.json new file mode 100644 index 000000000..894d81886 --- /dev/null +++ b/providers/dns/hetzner/internal/fixtures/create_txt_record-request.json @@ -0,0 +1,7 @@ +{ + "name": "test", + "type": "TXT", + "value": "txttxttxt", + "ttl": 600, + "zone_id": "zoneA" +} diff --git a/providers/dns/hosttech/internal/client_test.go b/providers/dns/hosttech/internal/client_test.go index 3acbaafc5..223a0d9cf 100644 --- a/providers/dns/hosttech/internal/client_test.go +++ b/providers/dns/hosttech/internal/client_test.go @@ -1,23 +1,38 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const testAPIKey = "secret" +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(OAuthStaticAccessToken(server.Client(), testAPIKey)) + client.baseURL, _ = url.Parse(server.URL) + + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Bearer secret")) +} + func TestClient_GetZones(t *testing.T) { - client := setupTest(t, "/user/v1/zones", testHandler(http.MethodGet, http.StatusOK, "zones.json")) + client := mockBuilder(). + Route("GET /user/v1/zones", + servermock.ResponseFromFixture("zones.json"), + servermock.CheckQueryParameter().Strict(). + With("limit", "100"). + With("query", "")). + Build(t) zones, err := client.GetZones(t.Context(), "", 100, 0) require.NoError(t, err) @@ -38,14 +53,21 @@ func TestClient_GetZones(t *testing.T) { } func TestClient_GetZones_error(t *testing.T) { - client := setupTest(t, "/user/v1/zones", testHandler(http.MethodGet, http.StatusUnauthorized, "error.json")) + client := mockBuilder(). + Route("GET /user/v1/zones", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.GetZones(t.Context(), "", 100, 0) require.Error(t, err) } func TestClient_GetZone(t *testing.T) { - client := setupTest(t, "/user/v1/zones/123", testHandler(http.MethodGet, http.StatusOK, "zone.json")) + client := mockBuilder(). + Route("GET /user/v1/zones/123", + servermock.ResponseFromFixture("zone.json")). + Build(t) zone, err := client.GetZone(t.Context(), "123") require.NoError(t, err) @@ -64,14 +86,23 @@ func TestClient_GetZone(t *testing.T) { } func TestClient_GetZone_error(t *testing.T) { - client := setupTest(t, "/user/v1/zones/123", testHandler(http.MethodGet, http.StatusUnauthorized, "error.json")) + client := mockBuilder(). + Route("GET /user/v1/zones/123", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.GetZone(t.Context(), "123") - require.Error(t, err) + require.EqualError(t, err, "401: Unauthenticated.") } func TestClient_GetRecords(t *testing.T) { - client := setupTest(t, "/user/v1/zones/123/records", testHandler(http.MethodGet, http.StatusOK, "records.json")) + client := mockBuilder(). + Route("GET /user/v1/zones/123/records", + servermock.ResponseFromFixture("records.json"), + servermock.CheckQueryParameter().Strict(). + With("type", "TXT")). + Build(t) records, err := client.GetRecords(t.Context(), "123", "TXT") require.NoError(t, err) @@ -151,14 +182,22 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_GetRecords_error(t *testing.T) { - client := setupTest(t, "/user/v1/zones/123/records", testHandler(http.MethodGet, http.StatusUnauthorized, "error.json")) + client := mockBuilder(). + Route("GET /user/v1/zones/123/records", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.GetRecords(t.Context(), "123", "TXT") - require.Error(t, err) + require.EqualError(t, err, "401: Unauthenticated.") } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, "/user/v1/zones/123/records", testHandler(http.MethodPost, http.StatusCreated, "record.json")) + client := mockBuilder(). + Route("POST /user/v1/zones/123/records", + servermock.ResponseFromFixture("record.json"). + WithStatusCode(http.StatusCreated)). + Build(t) record := Record{ Type: "TXT", @@ -184,7 +223,11 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, "/user/v1/zones/123/records", testHandler(http.MethodPost, http.StatusUnauthorized, "error-details.json")) + client := mockBuilder(). + Route("POST /user/v1/zones/123/records", + servermock.ResponseFromFixture("error-details.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) record := Record{ Type: "TXT", @@ -195,68 +238,27 @@ func TestClient_AddRecord_error(t *testing.T) { } _, err := client.AddRecord(t.Context(), "123", record) - require.Error(t, err) + require.EqualError(t, err, "401: The given data was invalid. type: [Darf nicht leer sein.]") } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, "/user/v1/zones/123/records/6", testHandler(http.MethodDelete, http.StatusUnauthorized, "error.json")) + client := mockBuilder(). + Route("DELETE /user/v1/zones/123/records/6", + servermock.Noop().WithStatusCode(http.StatusNoContent). + WithStatusCode(http.StatusCreated)). + Build(t) err := client.DeleteRecord(t.Context(), "123", "6") require.Error(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, "/user/v1/zones/123/records/6", testHandler(http.MethodDelete, http.StatusNoContent, "")) + client := mockBuilder(). + Route("DELETE /user/v1/zones/123/records/6", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) err := client.DeleteRecord(t.Context(), "123", "6") - require.NoError(t, err) -} - -func setupTest(t *testing.T, path string, handler http.Handler) *Client { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.Handle(path, handler) - - client := NewClient(OAuthStaticAccessToken(server.Client(), testAPIKey)) - client.baseURL, _ = url.Parse(server.URL) - - return client -} - -func testHandler(method string, statusCode int, filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf(`{"message":"unsupported method: %s"}`, req.Method), http.StatusMethodNotAllowed) - return - } - - if req.Header.Get("Authorization") != "Bearer "+testAPIKey { - http.Error(rw, `{"message":"Unauthenticated"}`, http.StatusUnauthorized) - return - } - - rw.WriteHeader(statusCode) - - if statusCode == http.StatusNoContent { - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, fmt.Sprintf(`{"message":"%v"}`, err), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, fmt.Sprintf(`{"message":"%v"}`, err), http.StatusInternalServerError) - return - } - } + require.EqualError(t, err, "401: Unauthenticated.") } diff --git a/providers/dns/httpreq/httpreq_test.go b/providers/dns/httpreq/httpreq_test.go index 8dc36ccc6..038b21b1a 100644 --- a/providers/dns/httpreq/httpreq_test.go +++ b/providers/dns/httpreq/httpreq_test.go @@ -1,15 +1,12 @@ package httpreq import ( - "encoding/json" - "fmt" - "net/http" "net/http/httptest" "net/url" - "path" "testing" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) @@ -102,75 +99,60 @@ func TestNewDNSProvider_Present(t *testing.T) { testCases := []struct { desc string - mode string - username string - password string - pathPrefix string - handler http.HandlerFunc + builder *servermock.Builder[*DNSProvider] expectedError string }{ { - desc: "success", - handler: successHandler, + desc: "success", + builder: mockBuilder(""). + Route("/present", + servermock.RawStringResponse("lego"), + servermock.CheckRequestJSONBody(`{"fqdn":"_acme-challenge.domain.","value":"LHDhK3oGRvkiefQnx7OOczTY5Tic_xZ6HcMOc_gmtoM"}`)), }, { - desc: "success with path prefix", - handler: successHandler, - pathPrefix: "/api/acme/", + desc: "success with path prefix", + builder: mockBuilderWithPathPrefix("", "/api/acme/"). + Route("/api/acme/present", + servermock.RawStringResponse("lego"), + servermock.CheckRequestJSONBody(`{"fqdn":"_acme-challenge.domain.","value":"LHDhK3oGRvkiefQnx7OOczTY5Tic_xZ6HcMOc_gmtoM"}`)), }, { desc: "error", - handler: http.NotFound, + builder: mockBuilder(""), expectedError: "httpreq: unexpected status code: [status code: 404] body: 404 page not found", }, { - desc: "success raw mode", - mode: "RAW", - handler: successRawModeHandler, + desc: "success raw mode", + builder: mockBuilder("RAW"). + Route("/present", + servermock.RawStringResponse("lego"), + servermock.CheckRequestBody(`{"domain":"domain","token":"token","keyAuth":"key"}`)), }, { desc: "error raw mode", - mode: "RAW", - handler: http.NotFound, + builder: mockBuilder("RAW"), expectedError: "httpreq: unexpected status code: [status code: 404] body: 404 page not found", }, { - desc: "basic auth", - username: "bar", - password: "foo", - handler: func(rw http.ResponseWriter, req *http.Request) { - username, password, ok := req.BasicAuth() - if username != "bar" || password != "foo" || !ok { - rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, "Please enter your username and password.")) - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - fmt.Fprint(rw, "lego") - }, + desc: "basic auth fail", + builder: mockBuilderWithBasicAuth("nope", "nope"). + Route("/present", servermock.Noop()), + expectedError: `httpreq: unexpected status code: [status code: 400] body: invalid credentials: got [username: "nope", password: "nope"], want [username: "user", password: "secret"]`, + }, + { + desc: "basic auth success", + builder: mockBuilderWithBasicAuth("user", "secret"). + Route("/present", + servermock.RawStringResponse("lego"), + servermock.CheckRequestJSONBody(`{"fqdn":"_acme-challenge.domain.","value":"LHDhK3oGRvkiefQnx7OOczTY5Tic_xZ6HcMOc_gmtoM"}`)), }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - t.Parallel() + p := test.builder.Build(t) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(path.Join("/", test.pathPrefix, "present"), test.handler) - - config := NewDefaultConfig() - config.Endpoint = mustParse(server.URL + test.pathPrefix) - config.Mode = test.mode - config.Username = test.username - config.Password = test.password - - p, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - err = p.Present("domain", "token", "key") + err := p.Present("domain", "token", "key") if test.expectedError == "" { require.NoError(t, err) } else { @@ -185,68 +167,53 @@ func TestNewDNSProvider_Cleanup(t *testing.T) { testCases := []struct { desc string - mode string - username string - password string - handler http.HandlerFunc + builder *servermock.Builder[*DNSProvider] expectedError string }{ { - desc: "success", - handler: successHandler, + desc: "success", + builder: mockBuilder(""). + Route("/cleanup", + servermock.RawStringResponse("lego"), + servermock.CheckRequestJSONBody(`{"fqdn":"_acme-challenge.domain.","value":"LHDhK3oGRvkiefQnx7OOczTY5Tic_xZ6HcMOc_gmtoM"}`)), }, { desc: "error", - handler: http.NotFound, + builder: mockBuilder(""), expectedError: "httpreq: unexpected status code: [status code: 404] body: 404 page not found", }, { - desc: "success raw mode", - mode: "RAW", - handler: successRawModeHandler, + desc: "success raw mode", + builder: mockBuilder("RAW"). + Route("/cleanup", + servermock.RawStringResponse("lego"), + servermock.CheckRequestBody(`{"domain":"domain","token":"token","keyAuth":"key"}`)), }, { desc: "error raw mode", - mode: "RAW", - handler: http.NotFound, + builder: mockBuilder("RAW"), expectedError: "httpreq: unexpected status code: [status code: 404] body: 404 page not found", }, { - desc: "basic auth", - username: "bar", - password: "foo", - handler: func(rw http.ResponseWriter, req *http.Request) { - username, password, ok := req.BasicAuth() - if username != "bar" || password != "foo" || !ok { - rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, "Please enter your username and password.")) - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - fmt.Fprint(rw, "lego") - }, + desc: "basic auth fail", + builder: mockBuilderWithBasicAuth("test", "example"). + Route("/cleanup", servermock.Noop()), + expectedError: `httpreq: unexpected status code: [status code: 400] body: invalid credentials: got [username: "test", password: "example"], want [username: "user", password: "secret"]`, + }, + { + desc: "basic auth success", + builder: mockBuilderWithBasicAuth("user", "secret"). + Route("/cleanup", + servermock.RawStringResponse("lego"), + servermock.CheckRequestJSONBody(`{"fqdn":"_acme-challenge.domain.","value":"LHDhK3oGRvkiefQnx7OOczTY5Tic_xZ6HcMOc_gmtoM"}`)), }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - t.Parallel() + p := test.builder.Build(t) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/cleanup", test.handler) - - config := NewDefaultConfig() - config.Endpoint = mustParse(server.URL) - config.Mode = test.mode - config.Username = test.username - config.Password = test.password - - p, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - err = p.CleanUp("domain", "token", "key") + err := p.CleanUp("domain", "token", "key") if test.expectedError == "" { require.NoError(t, err) } else { @@ -256,36 +223,39 @@ func TestNewDNSProvider_Cleanup(t *testing.T) { } } -func successHandler(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } +func mockBuilder(mode string) *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.Endpoint, _ = url.Parse(server.URL) + config.Mode = mode - msg := &message{} - err := json.NewDecoder(req.Body).Decode(msg) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - fmt.Fprint(rw, "lego") + return NewDNSProviderConfig(config) + }) } -func successRawModeHandler(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } +func mockBuilderWithPathPrefix(mode, prefix string) *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.Endpoint, _ = url.Parse(server.URL + prefix) + config.Mode = mode - msg := &messageRaw{} - err := json.NewDecoder(req.Body).Decode(msg) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } + return NewDNSProviderConfig(config) + }) +} - fmt.Fprint(rw, "lego") +func mockBuilderWithBasicAuth(username, password string) *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.Endpoint, _ = url.Parse(server.URL) + config.Username = username + config.Password = password + + return NewDNSProviderConfig(config) + }, + servermock.CheckHeader().WithBasicAuth("user", "secret")) } func mustParse(rawURL string) *url.URL { diff --git a/providers/dns/hurricane/internal/client_test.go b/providers/dns/hurricane/internal/client_test.go index 2e55c2057..d93f3e0ed 100644 --- a/providers/dns/hurricane/internal/client_test.go +++ b/providers/dns/hurricane/internal/client_test.go @@ -1,14 +1,21 @@ package internal import ( - "fmt" - "net/http" "net/http/httptest" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" ) +func setupClient(server *httptest.Server) (*Client, error) { + client := NewClient(map[string]string{"example.com": "secret"}) + client.baseURL = server.URL + client.HTTPClient = server.Client() + + return client, nil +} + func TestClient_UpdateTxtRecord(t *testing.T) { testCases := []struct { code string @@ -48,31 +55,14 @@ func TestClient_UpdateTxtRecord(t *testing.T) { t.Run(test.code, func(t *testing.T) { t.Parallel() - handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - if err := req.ParseForm(); err != nil { - http.Error(rw, "failed to parse form data", http.StatusBadRequest) - return - } - - if req.PostForm.Encode() != "hostname=_acme-challenge.example.com&password=secret&txt=foo" { - http.Error(rw, "invalid form data", http.StatusBadRequest) - return - } - - _, _ = rw.Write([]byte(test.code)) - }) - - server := httptest.NewServer(handler) - t.Cleanup(server.Close) - - client := NewClient(map[string]string{"example.com": "secret"}) - client.baseURL = server.URL - client.HTTPClient = server.Client() + client := servermock.NewBuilder[*Client](setupClient, servermock.CheckHeader().WithContentTypeFromURLEncoded()). + Route("POST /", + servermock.RawStringResponse(test.code), + servermock.CheckForm().Strict(). + With("hostname", "_acme-challenge.example.com"). + With("password", "secret"). + With("txt", "foo")). + Build(t) err := client.UpdateTxtRecord(t.Context(), "_acme-challenge.example.com", "foo") test.expected(t, err) diff --git a/providers/dns/hyperone/internal/client_test.go b/providers/dns/hyperone/internal/client_test.go index 55c43700c..aa087c4f2 100644 --- a/providers/dns/hyperone/internal/client_test.go +++ b/providers/dns/hyperone/internal/client_test.go @@ -1,16 +1,10 @@ package internal import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" "net/http/httptest" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -21,8 +15,32 @@ func (s signerMock) GetJWT() (string, error) { return "", nil } +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + passport := &Passport{ + SubjectID: "/iam/project/proj123/sa/xxxxxxx", + } + + client, err := NewClient(server.URL, "loc123", passport) + if err != nil { + return nil, err + } + + client.HTTPClient = server.Client() + client.signer = signerMock{} + + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Bearer")) +} + func TestClient_FindRecordset(t *testing.T) { - client := setupTest(t, http.MethodGet, "/dns/loc123/project/proj123/zone/zone321/recordset", respFromFile("recordset.json")) + client := mockBuilder(). + Route("GET /dns/loc123/project/proj123/zone/zone321/recordset", + servermock.ResponseFromFixture("recordset.json")). + Build(t) recordset, err := client.FindRecordset(t.Context(), "zone321", "SOA", "example.com.") require.NoError(t, err) @@ -45,8 +63,11 @@ func TestClient_CreateRecordset(t *testing.T) { Record: &Record{Content: "value"}, } - client := setupTest(t, http.MethodPost, "/dns/loc123/project/proj123/zone/zone123/recordset", - hasReqBody(expectedReqBody), respFromFile("createRecordset.json")) + client := mockBuilder(). + Route("POST /dns/loc123/project/proj123/zone/zone123/recordset", + servermock.ResponseFromFixture("createRecordset.json"), + servermock.CheckRequestJSONBodyFromStruct(expectedReqBody)). + Build(t) rs, err := client.CreateRecordset(t.Context(), "zone123", "TXT", "test.example.com.", "value", 3600) require.NoError(t, err) @@ -56,14 +77,19 @@ func TestClient_CreateRecordset(t *testing.T) { } func TestClient_DeleteRecordset(t *testing.T) { - client := setupTest(t, http.MethodDelete, "/dns/loc123/project/proj123/zone/zone321/recordset/rs322") + client := mockBuilder(). + Route("DELETE /dns/loc123/project/proj123/zone/zone321/recordset/rs322", nil). + Build(t) err := client.DeleteRecordset(t.Context(), "zone321", "rs322") require.NoError(t, err) } func TestClient_GetRecords(t *testing.T) { - client := setupTest(t, http.MethodGet, "/dns/loc123/project/proj123/zone/321/recordset/322/record", respFromFile("record.json")) + client := mockBuilder(). + Route("GET /dns/loc123/project/proj123/zone/321/recordset/322/record", + servermock.ResponseFromFixture("record.json")). + Build(t) records, err := client.GetRecords(t.Context(), "321", "322") require.NoError(t, err) @@ -84,8 +110,11 @@ func TestClient_CreateRecord(t *testing.T) { Content: "value", } - client := setupTest(t, http.MethodPost, "/dns/loc123/project/proj123/zone/z123/recordset/rs325/record", - hasReqBody(expectedReqBody), respFromFile("createRecord.json")) + client := mockBuilder(). + Route("POST /dns/loc123/project/proj123/zone/z123/recordset/rs325/record", + servermock.ResponseFromFixture("createRecord.json"), + servermock.CheckRequestJSONBodyFromStruct(expectedReqBody)). + Build(t) rs, err := client.CreateRecord(t.Context(), "z123", "rs325", "value") require.NoError(t, err) @@ -95,14 +124,20 @@ func TestClient_CreateRecord(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, http.MethodDelete, "/dns/loc123/project/proj123/zone/321/recordset/322/record/323") + client := mockBuilder(). + Route("DELETE /dns/loc123/project/proj123/zone/321/recordset/322/record/323", + servermock.ResponseFromFixture("createRecord.json")). + Build(t) err := client.DeleteRecord(t.Context(), "321", "322", "323") require.NoError(t, err) } func TestClient_FindZone(t *testing.T) { - client := setupTest(t, http.MethodGet, "/dns/loc123/project/proj123/zone", respFromFile("zones.json")) + client := mockBuilder(). + Route("GET /dns/loc123/project/proj123/zone", + servermock.ResponseFromFixture("zones.json")). + Build(t) zone, err := client.FindZone(t.Context(), "example.com") require.NoError(t, err) @@ -119,7 +154,10 @@ func TestClient_FindZone(t *testing.T) { } func TestClient_GetZones(t *testing.T) { - client := setupTest(t, http.MethodGet, "/dns/loc123/project/proj123/zone", respFromFile("zones.json")) + client := mockBuilder(). + Route("GET /dns/loc123/project/proj123/zone", + servermock.ResponseFromFixture("zones.json")). + Build(t) zones, err := client.GetZones(t.Context()) require.NoError(t, err) @@ -143,77 +181,3 @@ func TestClient_GetZones(t *testing.T) { assert.Equal(t, expected, zones) } - -func setupTest(t *testing.T, method, path string, handlers ...assertHandler) *Client { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.Handle(path, http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - if len(handlers) != 0 { - for _, handler := range handlers { - code, err := handler(rw, req) - if err != nil { - http.Error(rw, err.Error(), code) - return - } - } - } - })) - - passport := &Passport{ - SubjectID: "/iam/project/proj123/sa/xxxxxxx", - } - - client, err := NewClient(server.URL, "loc123", passport) - require.NoError(t, err) - - client.signer = signerMock{} - - return client -} - -type assertHandler func(http.ResponseWriter, *http.Request) (int, error) - -func hasReqBody(v any) assertHandler { - return func(rw http.ResponseWriter, req *http.Request) (int, error) { - reqBody, err := io.ReadAll(req.Body) - if err != nil { - return http.StatusBadRequest, err - } - - marshal, err := json.Marshal(v) - if err != nil { - return http.StatusInternalServerError, err - } - - if !bytes.Equal(marshal, bytes.TrimSpace(reqBody)) { - return http.StatusBadRequest, fmt.Errorf("invalid request body, got: %s, expect: %s", string(reqBody), string(marshal)) - } - - return http.StatusOK, nil - } -} - -func respFromFile(fixtureName string) assertHandler { - return func(rw http.ResponseWriter, req *http.Request) (int, error) { - file, err := os.Open(filepath.Join(".", "fixtures", fixtureName)) - if err != nil { - return http.StatusInternalServerError, err - } - - _, err = io.Copy(rw, file) - if err != nil { - return http.StatusInternalServerError, err - } - - return http.StatusOK, nil - } -} diff --git a/providers/dns/infomaniak/internal/client_test.go b/providers/dns/infomaniak/internal/client_test.go index 5c2d93202..566ed9c34 100644 --- a/providers/dns/infomaniak/internal/client_test.go +++ b/providers/dns/infomaniak/internal/client_test.go @@ -1,64 +1,34 @@ package internal import ( - "bytes" - "fmt" - "io" - "net/http" "net/http/httptest" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := New(OAuthStaticAccessToken(server.Client(), "token"), server.URL) + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client, err := New(OAuthStaticAccessToken(server.Client(), "token"), server.URL) - require.NoError(t, err) - - return client, mux + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Bearer token")) } func TestClient_CreateDNSRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/1/domain/666/dns/record", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - if req.Header.Get("Authorization") != "Bearer token" { - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - raw, err := io.ReadAll(req.Body) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - defer func() { _ = req.Body.Close() }() - - if string(bytes.TrimSpace(raw)) != `{"source":"foo","type":"TXT","ttl":60,"target":"txtxtxttxt"}` { - http.Error(rw, fmt.Sprintf("invalid request body: %s", string(raw)), http.StatusBadRequest) - return - } - - response := `{"result":"success","data": "123"}` - - _, err = rw.Write([]byte(response)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("POST /1/domain/666/dns/record", + servermock.RawStringResponse(`{"result":"success","data": "123"}`), + servermock.CheckRequestJSONBodyFromFile("create_dns_record-request.json")). + Build(t) domain := &DNSDomain{ ID: 666, @@ -79,53 +49,13 @@ func TestClient_CreateDNSRecord(t *testing.T) { } func TestClient_GetDomainByName(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/1/product", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - if req.Header.Get("Authorization") != "Bearer token" { - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - serviceName := req.URL.Query().Get("service_name") - if serviceName != "domain" { - http.Error(rw, fmt.Sprintf("invalid service_name: %s", serviceName), http.StatusBadRequest) - return - } - - customerName := req.URL.Query().Get("customer_name") - if customerName == "" { - http.Error(rw, fmt.Sprintf("invalid customer_name: %s", customerName), http.StatusBadRequest) - return - } - - response := ` - { - "result": "success", - "data": [ - { - "id": 123, - "customer_name": "two.three.example.com" - }, - { - "id": 456, - "customer_name": "three.example.com" - } - ] - } - ` - - _, err := rw.Write([]byte(response)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("GET /1/product", + servermock.ResponseFromFixture("get_domain_name.json"), + servermock.CheckQueryParameter().Strict(). + WithRegexp("customer_name", `.+\.example\.com`). + With("service_name", "domain")). + Build(t) domain, err := client.GetDomainByName(t.Context(), "one.two.three.example.com.") require.NoError(t, err) @@ -135,25 +65,10 @@ func TestClient_GetDomainByName(t *testing.T) { } func TestClient_DeleteDNSRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/1/domain/123/dns/record/456", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - if req.Header.Get("Authorization") != "Bearer token" { - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - _, err := rw.Write([]byte((`{"result":"success"}`))) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("DELETE /1/domain/123/dns/record/456", + servermock.RawStringResponse(`{"result":"success"}`)). + Build(t) err := client.DeleteDNSRecord(t.Context(), 123, "456") require.NoError(t, err) diff --git a/providers/dns/infomaniak/internal/fixtures/create_dns_record-request.json b/providers/dns/infomaniak/internal/fixtures/create_dns_record-request.json new file mode 100644 index 000000000..7e00434f1 --- /dev/null +++ b/providers/dns/infomaniak/internal/fixtures/create_dns_record-request.json @@ -0,0 +1,6 @@ +{ + "source": "foo", + "type": "TXT", + "ttl": 60, + "target": "txtxtxttxt" +} diff --git a/providers/dns/infomaniak/internal/fixtures/get_domain_name.json b/providers/dns/infomaniak/internal/fixtures/get_domain_name.json new file mode 100644 index 000000000..d431cc0d7 --- /dev/null +++ b/providers/dns/infomaniak/internal/fixtures/get_domain_name.json @@ -0,0 +1,13 @@ +{ + "result": "success", + "data": [ + { + "id": 123, + "customer_name": "two.three.example.com" + }, + { + "id": 456, + "customer_name": "three.example.com" + } + ] +} diff --git a/providers/dns/internal/active24/client_test.go b/providers/dns/internal/active24/client_test.go index d92ec574d..ad2a8126b 100644 --- a/providers/dns/internal/active24/client_test.go +++ b/providers/dns/internal/active24/client_test.go @@ -1,59 +1,41 @@ package active24 import ( - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" "time" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, status int, filename string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("example.com", "user", "secret") + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if filename == "" { - rw.WriteHeader(status) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client, err := NewClient("example.com", "user", "secret") - require.NoError(t, err) - - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithRegexp("Authorization", `Basic .+`). + WithRegexp("Date", `\d+-\d+-\d+T\d{2}:\d{2}:\d{2}.*`). + With("Accept-Language", "en_us")) } func TestClient_GetServices(t *testing.T) { - client := setupTest(t, "GET /v1/user/self/service", http.StatusOK, "services.json") + client := mockBuilder(). + Route("GET /v1/user/self/service", + servermock.ResponseFromFixture("services.json")). + Build(t) services, err := client.GetServices(t.Context()) require.NoError(t, err) @@ -83,14 +65,21 @@ func TestClient_GetServices(t *testing.T) { } func TestClient_GetServices_errors(t *testing.T) { - client := setupTest(t, "GET /v1/user/self/service", http.StatusUnauthorized, "error_v1.json") + client := mockBuilder(). + Route("GET /v1/user/self/service", + servermock.ResponseFromFixture("error_v1.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.GetServices(t.Context()) require.EqualError(t, err, "401: No username or password.") } func TestClient_GetRecords(t *testing.T) { - client := setupTest(t, "GET /v2/service/aaa/dns/record", http.StatusOK, "records.json") + client := mockBuilder(). + Route("GET /v2/service/aaa/dns/record", + servermock.ResponseFromFixture("records.json")). + Build(t) filter := RecordFilter{ Name: "example.com", @@ -115,7 +104,11 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_GetRecords_errors(t *testing.T) { - client := setupTest(t, "GET /v2/service/aaa/dns/record", http.StatusForbidden, "error_403.json") + client := mockBuilder(). + Route("GET /v2/service/aaa/dns/record", + servermock.ResponseFromFixture("error_403.json"). + WithStatusCode(http.StatusForbidden)). + Build(t) filter := RecordFilter{ Name: "example.com", @@ -128,28 +121,44 @@ func TestClient_GetRecords_errors(t *testing.T) { } func TestClient_CreateRecord(t *testing.T) { - client := setupTest(t, "POST /v2/service/aaa/dns/record", http.StatusNoContent, "") + client := mockBuilder(). + Route("POST /v2/service/aaa/dns/record", + servermock.Noop(). + WithStatusCode(http.StatusNoContent)). + Build(t) err := client.CreateRecord(t.Context(), "aaa", Record{}) require.NoError(t, err) } func TestClient_CreateRecord_errors(t *testing.T) { - client := setupTest(t, "POST /v2/service/aaa/dns/record", http.StatusForbidden, "error_403.json") + client := mockBuilder(). + Route("POST /v2/service/aaa/dns/record", + servermock.ResponseFromFixture("error_403.json"). + WithStatusCode(http.StatusForbidden)). + Build(t) err := client.CreateRecord(t.Context(), "aaa", Record{}) require.EqualError(t, err, "403: /errors/httpException: This action is unauthorized.") } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, "DELETE /v2/service/aaa/dns/record/123", http.StatusNoContent, "") + client := mockBuilder(). + Route("DELETE /v2/service/aaa/dns/record/123", + servermock.Noop(). + WithStatusCode(http.StatusNoContent)). + Build(t) err := client.DeleteRecord(t.Context(), "aaa", "123") require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, "DELETE /v2/service/aaa/dns/record/123", http.StatusForbidden, "error_403.json") + client := mockBuilder(). + Route("DELETE /v2/service/aaa/dns/record/123", + servermock.ResponseFromFixture("error_403.json"). + WithStatusCode(http.StatusForbidden)). + Build(t) err := client.DeleteRecord(t.Context(), "aaa", "123") require.EqualError(t, err, "403: /errors/httpException: This action is unauthorized.") diff --git a/providers/dns/internal/hostingde/client_test.go b/providers/dns/internal/hostingde/client_test.go index c4090ec5c..b735509c0 100644 --- a/providers/dns/internal/hostingde/client_test.go +++ b/providers/dns/internal/hostingde/client_test.go @@ -1,69 +1,30 @@ package hostingde import ( - "bytes" "encoding/json" - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, handler http.HandlerFunc) *Client { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - +func setupClient(server *httptest.Server) (*Client, error) { client := NewClient("secret") client.HTTPClient = server.Client() client.BaseURL, _ = url.Parse(server.URL) - mux.HandleFunc(pattern, handler) - - return client -} - -func writeFixture(rw http.ResponseWriter, filename string) { - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, _ = io.Copy(rw, file) + return client, nil } func TestClient_ListZoneConfigs(t *testing.T) { - client := setupTest(t, "/zoneConfigsFind", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - raw, err := io.ReadAll(req.Body) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - body := string(bytes.TrimSpace(raw)) - if body != `{"authToken":"secret","filter":{"field":"zoneName","value":"example.com"},"limit":1,"page":1}` { - http.Error(rw, fmt.Sprintf("unexpected body: got %s", body), http.StatusBadRequest) - return - } - - writeFixture(rw, "zoneConfigsFind.json") - }) + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /zoneConfigsFind", + servermock.ResponseFromFixture("zoneConfigsFind.json"), + servermock.CheckRequestJSONBodyFromFile("zoneConfigsFind-request.json")). + Build(t) zonesFind := ZoneConfigsFindRequest{ Filter: Filter{Field: "zoneName", Value: "example.com"}, @@ -108,14 +69,10 @@ func TestClient_ListZoneConfigs(t *testing.T) { } func TestClient_ListZoneConfigs_error(t *testing.T) { - client := setupTest(t, "/zoneConfigsFind", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - writeFixture(rw, "zoneConfigsFind_error.json") - }) + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /zoneConfigsFind", + servermock.ResponseFromFixture("zoneConfigsFind_error.json")). + Build(t) zonesFind := ZoneConfigsFindRequest{ Filter: Filter{Field: "zoneName", Value: "example.com"}, @@ -128,26 +85,11 @@ func TestClient_ListZoneConfigs_error(t *testing.T) { } func TestClient_UpdateZone(t *testing.T) { - client := setupTest(t, "/zoneUpdate", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - raw, err := io.ReadAll(req.Body) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - body := string(bytes.TrimSpace(raw)) - if body != `{"authToken":"secret","zoneConfig":{"id":"123","accountId":"456","status":"s","name":"n","nameUnicode":"u","masterIp":"m","type":"t","emailAddress":"e","zoneTransferWhitelist":["a","b"],"lastChangeDate":"l","dnsServerGroupId":"g","dnsSecMode":"m","soaValues":{"refresh":1,"retry":2,"expire":3,"ttl":4,"negativeTtl":5}},"recordsToAdd":null,"recordsToDelete":[{"name":"_acme-challenge.example.com","type":"TXT","content":"\"txt\""}]}` { - http.Error(rw, fmt.Sprintf("unexpected body: got %s", body), http.StatusBadRequest) - return - } - - writeFixture(rw, "zoneUpdate.json") - }) + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /zoneUpdate", + servermock.ResponseFromFixture("zoneUpdate.json"), + servermock.CheckRequestJSONBodyFromFile("zoneUpdate-request.json")). + Build(t) request := ZoneUpdateRequest{ ZoneConfig: ZoneConfig{ @@ -220,14 +162,10 @@ func TestClient_UpdateZone(t *testing.T) { } func TestClient_UpdateZone_error(t *testing.T) { - client := setupTest(t, "/zoneUpdate", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - writeFixture(rw, "zoneUpdate_error.json") - }) + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /zoneUpdate", + servermock.ResponseFromFixture("zoneUpdate_error.json")). + Build(t) request := ZoneUpdateRequest{ ZoneConfig: ZoneConfig{ diff --git a/providers/dns/internal/hostingde/fixtures/zoneConfigsFind-request.json b/providers/dns/internal/hostingde/fixtures/zoneConfigsFind-request.json new file mode 100644 index 000000000..eb552d9eb --- /dev/null +++ b/providers/dns/internal/hostingde/fixtures/zoneConfigsFind-request.json @@ -0,0 +1,9 @@ +{ + "authToken": "secret", + "filter": { + "field": "zoneName", + "value": "example.com" + }, + "limit": 1, + "page": 1 +} diff --git a/providers/dns/internal/hostingde/fixtures/zoneUpdate-request.json b/providers/dns/internal/hostingde/fixtures/zoneUpdate-request.json new file mode 100644 index 000000000..38b1be50d --- /dev/null +++ b/providers/dns/internal/hostingde/fixtures/zoneUpdate-request.json @@ -0,0 +1,35 @@ +{ + "authToken": "secret", + "zoneConfig": { + "id": "123", + "accountId": "456", + "status": "s", + "name": "n", + "nameUnicode": "u", + "masterIp": "m", + "type": "t", + "emailAddress": "e", + "zoneTransferWhitelist": [ + "a", + "b" + ], + "lastChangeDate": "l", + "dnsServerGroupId": "g", + "dnsSecMode": "m", + "soaValues": { + "refresh": 1, + "retry": 2, + "expire": 3, + "ttl": 4, + "negativeTtl": 5 + } + }, + "recordsToAdd": null, + "recordsToDelete": [ + { + "name": "_acme-challenge.example.com", + "type": "TXT", + "content": "\"txt\"" + } + ] +} diff --git a/providers/dns/internal/rimuhosting/client_test.go b/providers/dns/internal/rimuhosting/client_test.go index 90a574b3a..6ee9ea3f7 100644 --- a/providers/dns/internal/rimuhosting/client_test.go +++ b/providers/dns/internal/rimuhosting/client_test.go @@ -2,63 +2,42 @@ package rimuhosting import ( "encoding/xml" - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - +func setupClient(server *httptest.Server) (*Client, error) { client := NewClient("apikeyvaluehere") client.BaseURL = server.URL client.HTTPClient = server.Client() - return client, mux + return client, nil } func TestClient_FindTXTRecords(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { - query := req.URL.Query() - - var fixture string - switch query.Get("name") { - case "example.com": - fixture = "./fixtures/find_records.xml" - case "**.example.com": - fixture = "./fixtures/find_records_pattern.xml" - default: - fixture = "./fixtures/find_records_empty.xml" - } - - err := writeResponse(rw, fixture) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - testCases := []struct { desc string domain string + response string + query url.Values expected []Record }{ { - desc: "simple", - domain: "example.com", + desc: "simple", + domain: "example.com", + response: "find_records.xml", + query: url.Values{ + "name": []string{"example.com"}, + "type": []string{"TXT"}, + "action": []string{"QUERY"}, + "api_key": []string{"apikeyvaluehere"}, + }, expected: []Record{ { Name: "example.org", @@ -70,8 +49,15 @@ func TestClient_FindTXTRecords(t *testing.T) { }, }, { - desc: "pattern", - domain: "**.example.com", + desc: "pattern", + domain: "**.example.com", + response: "find_records_pattern.xml", + query: url.Values{ + "name": []string{"**.example.com"}, + "type": []string{"TXT"}, + "action": []string{"QUERY"}, + "api_key": []string{"apikeyvaluehere"}, + }, expected: []Record{ { Name: "_test.example.org", @@ -92,12 +78,26 @@ func TestClient_FindTXTRecords(t *testing.T) { { desc: "empty", domain: "empty.com", + response: "find_records_empty.xml", + query: url.Values{ + "name": []string{"empty.com"}, + "type": []string{"TXT"}, + "action": []string{"QUERY"}, + "api_key": []string{"apikeyvaluehere"}, + }, expected: nil, }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /", + servermock.ResponseFromFixture(test.response), + servermock.CheckQueryParameter().Strict(). + WithValues(test.query)). + Build(t) + records, err := client.FindTXTRecords(t.Context(), test.domain) require.NoError(t, err) @@ -107,54 +107,42 @@ func TestClient_FindTXTRecords(t *testing.T) { } func TestClient_DoActions(t *testing.T) { - type expected struct { - Query string - Resp *DNSAPIResult - Error string - } - testCases := []struct { desc string actions []ActionParameter - fixture string - expected expected + query url.Values + response string + expected *DNSAPIResult }{ - { - desc: "SET error", - actions: []ActionParameter{ - NewAddRecordAction("example.com", "txttxtx", 0), - }, - fixture: "./fixtures/add_record_error.xml", - expected: expected{ - Query: "action=SET&api_key=apikeyvaluehere&name=example.com&type=TXT&value=txttxtx", - Error: "ERROR: No zone found for example.com", - }, - }, { desc: "SET simple", actions: []ActionParameter{ NewAddRecordAction("example.org", "txttxtx", 0), }, - fixture: "./fixtures/add_record.xml", - expected: expected{ - Query: "action=SET&api_key=apikeyvaluehere&name=example.org&type=TXT&value=txttxtx", - Resp: &DNSAPIResult{ - XMLName: xml.Name{Space: "", Local: "dnsapi_result"}, - IsOk: "OK:", - ResultCounts: ResultCounts{Added: "1", Changed: "0", Unchanged: "0", Deleted: "0"}, - Actions: Actions{ - Action: Action{ - Action: "SET", - Host: "example.org", - Type: "TXT", - Records: []Record{{ - Name: "example.org", - Type: "TXT", - Content: "txttxtx", - TTL: "3600 seconds", - Priority: "0", - }}, - }, + response: "add_record.xml", + query: url.Values{ + "action": []string{"SET"}, + "name": []string{"example.org"}, + "type": []string{"TXT"}, + "value": []string{"txttxtx"}, + "api_key": []string{"apikeyvaluehere"}, + }, + expected: &DNSAPIResult{ + XMLName: xml.Name{Space: "", Local: "dnsapi_result"}, + IsOk: "OK:", + ResultCounts: ResultCounts{Added: "1", Changed: "0", Unchanged: "0", Deleted: "0"}, + Actions: Actions{ + Action: Action{ + Action: "SET", + Host: "example.org", + Type: "TXT", + Records: []Record{{ + Name: "example.org", + Type: "TXT", + Content: "txttxtx", + TTL: "3600 seconds", + Priority: "0", + }}, }, }, }, @@ -165,69 +153,72 @@ func TestClient_DoActions(t *testing.T) { NewAddRecordAction("example.org", "txttxtx", 0), NewAddRecordAction("example.org", "sample", 0), }, - fixture: "./fixtures/add_record_same_domain.xml", - expected: expected{ - Query: "action[0]=SET&action[1]=SET&api_key=apikeyvaluehere&name[0]=example.org&name[1]=example.org&ttl[0]=0&ttl[1]=0&type[0]=TXT&type[1]=TXT&value[0]=txttxtx&value[1]=sample", - Resp: &DNSAPIResult{ - XMLName: xml.Name{Space: "", Local: "dnsapi_result"}, - IsOk: "OK:", - ResultCounts: ResultCounts{Added: "2", Changed: "0", Unchanged: "0", Deleted: "0"}, - Actions: Actions{ - Action: Action{ - Action: "SET", - Host: "example.org", - Type: "TXT", - Records: []Record{ - { - Name: "example.org", - Type: "TXT", - Content: "txttxtx", - TTL: "0 seconds", - Priority: "0", - }, - { - Name: "example.org", - Type: "TXT", - Content: "sample", - TTL: "0 seconds", - Priority: "0", - }, + response: "add_record_same_domain.xml", + query: url.Values{ + "api_key": []string{"apikeyvaluehere"}, + "action[0]": []string{"SET"}, + "name[0]": []string{"example.org"}, + "ttl[0]": []string{"0"}, + "type[0]": []string{"TXT"}, + "value[0]": []string{"txttxtx"}, + "action[1]": []string{"SET"}, + "name[1]": []string{"example.org"}, + "ttl[1]": []string{"0"}, + "type[1]": []string{"TXT"}, + "value[1]": []string{"sample"}, + }, + expected: &DNSAPIResult{ + XMLName: xml.Name{Space: "", Local: "dnsapi_result"}, + IsOk: "OK:", + ResultCounts: ResultCounts{Added: "2", Changed: "0", Unchanged: "0", Deleted: "0"}, + Actions: Actions{ + Action: Action{ + Action: "SET", + Host: "example.org", + Type: "TXT", + Records: []Record{ + { + Name: "example.org", + Type: "TXT", + Content: "txttxtx", + TTL: "0 seconds", + Priority: "0", + }, + { + Name: "example.org", + Type: "TXT", + Content: "sample", + TTL: "0 seconds", + Priority: "0", }, }, }, }, }, }, - { - desc: "DELETE error", - actions: []ActionParameter{ - NewDeleteRecordAction("example.com", "txttxtx"), - }, - fixture: "./fixtures/delete_record_error.xml", - expected: expected{ - Query: "action=DELETE&api_key=apikeyvaluehere&name=example.com&type=TXT&value=txttxtx", - Error: "ERROR: No zone found for example.com", - }, - }, { desc: "DELETE nothing", actions: []ActionParameter{ NewDeleteRecordAction("example.org", "nothing"), }, - fixture: "./fixtures/delete_record_nothing.xml", - expected: expected{ - Query: "action=DELETE&api_key=apikeyvaluehere&name=example.org&type=TXT&value=nothing", - Resp: &DNSAPIResult{ - XMLName: xml.Name{Space: "", Local: "dnsapi_result"}, - IsOk: "OK:", - ResultCounts: ResultCounts{Added: "0", Changed: "0", Unchanged: "0", Deleted: "0"}, - Actions: Actions{ - Action: Action{ - Action: "DELETE", - Host: "example.org", - Type: "TXT", - Records: nil, - }, + response: "delete_record_nothing.xml", + query: url.Values{ + "action": []string{"DELETE"}, + "name": []string{"example.org"}, + "type": []string{"TXT"}, + "value": []string{"nothing"}, + "api_key": []string{"apikeyvaluehere"}, + }, + expected: &DNSAPIResult{ + XMLName: xml.Name{Space: "", Local: "dnsapi_result"}, + IsOk: "OK:", + ResultCounts: ResultCounts{Added: "0", Changed: "0", Unchanged: "0", Deleted: "0"}, + Actions: Actions{ + Action: Action{ + Action: "DELETE", + Host: "example.org", + Type: "TXT", + Records: nil, }, }, }, @@ -237,26 +228,30 @@ func TestClient_DoActions(t *testing.T) { actions: []ActionParameter{ NewDeleteRecordAction("example.org", "txttxtx"), }, - fixture: "./fixtures/delete_record.xml", - expected: expected{ - Query: "action=DELETE&api_key=apikeyvaluehere&name=example.org&type=TXT&value=txttxtx", - Resp: &DNSAPIResult{ - XMLName: xml.Name{Space: "", Local: "dnsapi_result"}, - IsOk: "OK:", - ResultCounts: ResultCounts{Added: "0", Changed: "0", Unchanged: "0", Deleted: "1"}, - Actions: Actions{ - Action: Action{ - Action: "DELETE", - Host: "example.org", - Type: "TXT", - Records: []Record{{ - Name: "example.org", - Type: "TXT", - Content: "txttxtx", - TTL: "3600 seconds", - Priority: "0", - }}, - }, + response: "delete_record.xml", + query: url.Values{ + "action": []string{"DELETE"}, + "name": []string{"example.org"}, + "type": []string{"TXT"}, + "value": []string{"txttxtx"}, + "api_key": []string{"apikeyvaluehere"}, + }, + expected: &DNSAPIResult{ + XMLName: xml.Name{Space: "", Local: "dnsapi_result"}, + IsOk: "OK:", + ResultCounts: ResultCounts{Added: "0", Changed: "0", Unchanged: "0", Deleted: "1"}, + Actions: Actions{ + Action: Action{ + Action: "DELETE", + Host: "example.org", + Type: "TXT", + Records: []Record{{ + Name: "example.org", + Type: "TXT", + Content: "txttxtx", + TTL: "3600 seconds", + Priority: "0", + }}, }, }, }, @@ -265,52 +260,73 @@ func TestClient_DoActions(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { - query, err := url.QueryUnescape(req.URL.RawQuery) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - if test.expected.Query != query { - http.Error(rw, fmt.Sprintf("invalid query: %s", query), http.StatusBadRequest) - return - } - - if test.expected.Error != "" { - rw.WriteHeader(http.StatusInternalServerError) - } - - err = writeResponse(rw, test.fixture) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /", + servermock.ResponseFromFixture(test.response), + servermock.CheckQueryParameter().Strict(). + WithValues(test.query)). + Build(t) resp, err := client.DoActions(t.Context(), test.actions...) - if test.expected.Error != "" { - require.EqualError(t, err, test.expected.Error) - return - } - require.NoError(t, err) - assert.Equal(t, test.expected.Resp, resp) + assert.Equal(t, test.expected, resp) }) } } -func writeResponse(rw io.Writer, filename string) error { - file, err := os.Open(filename) - if err != nil { - return err +func TestClient_DoActions_error(t *testing.T) { + testCases := []struct { + desc string + actions []ActionParameter + query url.Values + response string + expected string + }{ + { + desc: "SET error", + actions: []ActionParameter{ + NewAddRecordAction("example.com", "txttxtx", 0), + }, + response: "add_record_error.xml", + query: url.Values{ + "action": []string{"SET"}, + "name": []string{"example.com"}, + "type": []string{"TXT"}, + "value": []string{"txttxtx"}, + "api_key": []string{"apikeyvaluehere"}, + }, + expected: "ERROR: No zone found for example.com", + }, + { + desc: "DELETE error", + actions: []ActionParameter{ + NewDeleteRecordAction("example.com", "txttxtx"), + }, + response: "delete_record_error.xml", + query: url.Values{ + "action": []string{"DELETE"}, + "name": []string{"example.com"}, + "type": []string{"TXT"}, + "value": []string{"txttxtx"}, + "api_key": []string{"apikeyvaluehere"}, + }, + expected: "ERROR: No zone found for example.com", + }, } - defer func() { _ = file.Close() }() + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /", + servermock.ResponseFromFixture(test.response). + WithStatusCode(http.StatusInternalServerError), + servermock.CheckQueryParameter().Strict(). + WithValues(test.query)). + Build(t) - _, err = io.Copy(rw, file) - return err + _, err := client.DoActions(t.Context(), test.actions...) + require.EqualError(t, err, test.expected) + }) + } } diff --git a/providers/dns/internal/selectel/client_test.go b/providers/dns/internal/selectel/client_test.go index d0a2f8cf0..d67a4b3bf 100644 --- a/providers/dns/internal/selectel/client_test.go +++ b/providers/dns/internal/selectel/client_test.go @@ -1,50 +1,29 @@ package selectel import ( - "encoding/json" - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - +func setupClient(server *httptest.Server) (*Client, error) { client := NewClient("token") client.BaseURL, _ = url.Parse(server.URL) client.HTTPClient = server.Client() - return client, mux + return client, nil } func TestClient_ListRecords(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/123/records/", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - fixture := "./fixtures/list_records.json" - - err := writeResponse(rw, fixture) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader().WithJSONHeaders()). + Route("GET /123/records/", servermock.ResponseFromFixture("list_records.json")). + Build(t) records, err := client.ListRecords(t.Context(), 123) require.NoError(t, err) @@ -59,21 +38,12 @@ func TestClient_ListRecords(t *testing.T) { } func TestClient_ListRecords_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/123/records/", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - rw.WriteHeader(http.StatusUnauthorized) - err := writeResponse(rw, "./fixtures/error.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader().WithJSONHeaders(). + With(tokenHeader, "token")). + Route("GET /123/records/", + servermock.ResponseFromFixture("error.json").WithStatusCode(http.StatusUnauthorized)). + Build(t) records, err := client.ListRecords(t.Context(), 123) @@ -82,40 +52,16 @@ func TestClient_ListRecords_error(t *testing.T) { } func TestClient_GetDomainByName(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/sub.sub.example.org", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - rw.WriteHeader(http.StatusNotFound) - }) - - mux.HandleFunc("/sub.example.org", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - rw.WriteHeader(http.StatusNotFound) - }) - - mux.HandleFunc("/example.org", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - fixture := "./fixtures/domains.json" - - err := writeResponse(rw, fixture) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader().WithJSONHeaders(). + With(tokenHeader, "token")). + Route("GET /sub.sub.example.org", + servermock.Noop().WithStatusCode(http.StatusNotFound)). + Route("GET /sub.example.org", + servermock.Noop().WithStatusCode(http.StatusNotFound)). + Route("GET /example.org", + servermock.ResponseFromFixture("domains.json")). + Build(t) domain, err := client.GetDomainByName(t.Context(), "sub.sub.example.org") require.NoError(t, err) @@ -129,30 +75,13 @@ func TestClient_GetDomainByName(t *testing.T) { } func TestClient_AddRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/123/records/", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - rec := Record{} - - err := json.NewDecoder(req.Body).Decode(&rec) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - rec.ID = 456 - - err = json.NewEncoder(rw).Encode(rec) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader().WithJSONHeaders(). + With(tokenHeader, "token")). + Route("POST /123/records/", + servermock.ResponseFromFixture("add_record.json"), + servermock.CheckRequestJSONBodyFromFile("add_record-request.json")). + Build(t) record, err := client.AddRecord(t.Context(), 123, Record{ Name: "example.org", @@ -177,27 +106,12 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - }) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader().WithJSONHeaders(). + With(tokenHeader, "token")). + Route("DELETE /123/records/456", nil). + Build(t) err := client.DeleteRecord(t.Context(), 123, 456) require.NoError(t, err) } - -func writeResponse(rw io.Writer, filename string) error { - file, err := os.Open(filename) - if err != nil { - return err - } - - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - return err -} diff --git a/providers/dns/internal/selectel/fixtures/add_record-request.json b/providers/dns/internal/selectel/fixtures/add_record-request.json new file mode 100644 index 000000000..c65d3d267 --- /dev/null +++ b/providers/dns/internal/selectel/fixtures/add_record-request.json @@ -0,0 +1,7 @@ +{ + "name": "example.org", + "type": "TXT", + "ttl": 60, + "email": "email@example.org", + "content": "txttxttxttxt" +} diff --git a/providers/dns/internal/selectel/fixtures/add_record.json b/providers/dns/internal/selectel/fixtures/add_record.json new file mode 100644 index 000000000..18a436707 --- /dev/null +++ b/providers/dns/internal/selectel/fixtures/add_record.json @@ -0,0 +1,8 @@ +{ + "id": 456, + "name": "example.org", + "type": "TXT", + "ttl": 60, + "email": "email@example.org", + "content": "txttxttxttxt" +} diff --git a/providers/dns/internetbs/internal/client_test.go b/providers/dns/internetbs/internal/client_test.go index d0b94ad4e..4532426d5 100644 --- a/providers/dns/internetbs/internal/client_test.go +++ b/providers/dns/internetbs/internal/client_test.go @@ -2,14 +2,13 @@ package internal import ( "fmt" - "io" - "net/http" "net/http/httptest" "net/url" "os" "strconv" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -21,8 +20,33 @@ const ( testPassword = "testpass" ) +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(testAPIKey, testPassword) + client.baseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded(), + ) +} + func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, "/Domain/DnsRecord/Add", "./fixtures/Domain_DnsRecord_Add_SUCCESS.json") + client := mockBuilder(). + Route("POST /Domain/DnsRecord/Add", + servermock.ResponseFromFixture("Domain_DnsRecord_Add_SUCCESS.json"), + servermock.CheckForm().Strict(). + With("fullrecordname", "www.example.com"). + With("ttl", "36000"). + With("type", "TXT"). + With("value", "xxx"). + With("password", testPassword). + With("apiKey", testAPIKey). + With("ResponseFormat", "JSON")). + Build(t) query := RecordQuery{ FullRecordName: "www.example.com", @@ -36,7 +60,10 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, "/Domain/DnsRecord/Add", "./fixtures/Domain_DnsRecord_Add_FAILURE.json") + client := mockBuilder(). + Route("POST /Domain/DnsRecord/Add", + servermock.ResponseFromFixture("Domain_DnsRecord_Add_FAILURE.json")). + Build(t) query := RecordQuery{ FullRecordName: "www.example.com.", @@ -81,7 +108,16 @@ func TestClient_AddRecord_integration(t *testing.T) { } func TestClient_RemoveRecord(t *testing.T) { - client := setupTest(t, "/Domain/DnsRecord/Remove", "./fixtures/Domain_DnsRecord_Remove_SUCCESS.json") + client := mockBuilder(). + Route("POST /Domain/DnsRecord/Remove", + servermock.ResponseFromFixture("Domain_DnsRecord_Remove_SUCCESS.json"), + servermock.CheckForm().Strict(). + With("fullrecordname", "www.example.com"). + With("type", "TXT"). + With("password", testPassword). + With("apiKey", testAPIKey). + With("ResponseFormat", "JSON")). + Build(t) query := RecordQuery{ FullRecordName: "www.example.com", @@ -93,7 +129,10 @@ func TestClient_RemoveRecord(t *testing.T) { } func TestClient_RemoveRecord_error(t *testing.T) { - client := setupTest(t, "/Domain/DnsRecord/Remove", "./fixtures/Domain_DnsRecord_Remove_FAILURE.json") + client := mockBuilder(). + Route("POST /Domain/DnsRecord/Remove", + servermock.ResponseFromFixture("Domain_DnsRecord_Remove_FAILURE.json")). + Build(t) query := RecordQuery{ FullRecordName: "www.example.com.", @@ -125,7 +164,15 @@ func TestClient_RemoveRecord_integration(t *testing.T) { } func TestClient_ListRecords(t *testing.T) { - client := setupTest(t, "/Domain/DnsRecord/List", "./fixtures/Domain_DnsRecord_List_SUCCESS.json") + client := mockBuilder(). + Route("POST /Domain/DnsRecord/List", + servermock.ResponseFromFixture("Domain_DnsRecord_List_SUCCESS.json"), + servermock.CheckForm().Strict(). + With("Domain", "example.com"). + With("password", testPassword). + With("apiKey", testAPIKey). + With("ResponseFormat", "JSON")). + Build(t) query := ListRecordQuery{ Domain: "example.com", @@ -177,7 +224,10 @@ func TestClient_ListRecords(t *testing.T) { } func TestClient_ListRecords_error(t *testing.T) { - client := setupTest(t, "/Domain/DnsRecord/List", "./fixtures/Domain_DnsRecord_List_FAILURE.json") + client := mockBuilder(). + Route("POST /Domain/DnsRecord/List", + servermock.ResponseFromFixture("Domain_DnsRecord_List_FAILURE.json")). + Build(t) query := ListRecordQuery{ Domain: "www.example.com", @@ -208,51 +258,3 @@ func TestClient_ListRecords_integration(t *testing.T) { fmt.Println(record) } } - -func setupTest(t *testing.T, path, filename string) *Client { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(path, testHandler(filename)) - - client := NewClient(testAPIKey, testPassword) - client.baseURL, _ = url.Parse(server.URL) - - return client -} - -func testHandler(filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - if req.FormValue("apiKey") != testAPIKey { - http.Error(rw, `{"transactid":"d46d812569acdb8b39c3933ec4351e79","status":"FAILURE","message":"Invalid API key and\/or Password","code":107002}`, http.StatusOK) - return - } - - if req.FormValue("password") != testPassword { - http.Error(rw, `{"transactid":"d46d812569acdb8b39c3933ec4351e79","status":"FAILURE","message":"Invalid API key and\/or Password","code":107002}`, http.StatusOK) - return - } - - file, err := os.Open(filename) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } -} diff --git a/providers/dns/internetbs/internal/fixtures/auth_error.json b/providers/dns/internetbs/internal/fixtures/auth_error.json new file mode 100644 index 000000000..a40a0ef5e --- /dev/null +++ b/providers/dns/internetbs/internal/fixtures/auth_error.json @@ -0,0 +1,6 @@ +{ + "transactid": "d46d812569acdb8b39c3933ec4351e79", + "status": "FAILURE", + "message": "Invalid API key and\/or Password", + "code": 107002 +} diff --git a/providers/dns/ionos/internal/client.go b/providers/dns/ionos/internal/client.go index 8b37d5f1c..b51e003f7 100644 --- a/providers/dns/ionos/internal/client.go +++ b/providers/dns/ionos/internal/client.go @@ -17,6 +17,9 @@ import ( // defaultBaseURL represents the API endpoint to call. const defaultBaseURL = "https://api.hosting.ionos.com/dns" +// APIKeyHeader API key header. +const APIKeyHeader = "X-Api-Key" + // Client Ionos API client. type Client struct { apiKey string @@ -119,7 +122,7 @@ func (c *Client) RemoveRecord(ctx context.Context, zoneID, recordID string) erro } func (c *Client) do(req *http.Request, result any) error { - req.Header.Set("X-API-Key", c.apiKey) + req.Header.Set(APIKeyHeader, c.apiKey) resp, err := c.HTTPClient.Do(req) if err != nil { diff --git a/providers/dns/ionos/internal/client_test.go b/providers/dns/ionos/internal/client_test.go index 6a36dfde7..008d153bc 100644 --- a/providers/dns/ionos/internal/client_test.go +++ b/providers/dns/ionos/internal/client_test.go @@ -1,24 +1,38 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestClient_ListZones(t *testing.T) { - client, mux := setupTest(t) +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("secret") + if err != nil { + return nil, err + } - mux.HandleFunc("/v1/zones", mockHandler(http.MethodGet, http.StatusOK, "list_zones.json")) + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(), + servermock.CheckHeader().With(APIKeyHeader, "secret")) +} + +func TestClient_ListZones(t *testing.T) { + client := mockBuilder(). + Route("GET /v1/zones", + servermock.ResponseFromFixture("list_zones.json")). + Build(t) zones, err := client.ListZones(t.Context()) require.NoError(t, err) @@ -33,9 +47,11 @@ func TestClient_ListZones(t *testing.T) { } func TestClient_ListZones_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/zones", mockHandler(http.MethodGet, http.StatusUnauthorized, "list_zones_error.json")) + client := mockBuilder(). + Route("GET /v1/zones", + servermock.ResponseFromFixture("list_zones_error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) zones, err := client.ListZones(t.Context()) require.Error(t, err) @@ -48,9 +64,10 @@ func TestClient_ListZones_error(t *testing.T) { } func TestClient_GetRecords(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/zones/azone01", mockHandler(http.MethodGet, http.StatusOK, "get_records.json")) + client := mockBuilder(). + Route("GET /v1/zones/azone01", + servermock.ResponseFromFixture("get_records.json")). + Build(t) records, err := client.GetRecords(t.Context(), "azone01", nil) require.NoError(t, err) @@ -66,9 +83,11 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_GetRecords_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/zones/azone01", mockHandler(http.MethodGet, http.StatusUnauthorized, "get_records_error.json")) + client := mockBuilder(). + Route("GET /v1/zones/azone01", + servermock.ResponseFromFixture("get_records_error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) records, err := client.GetRecords(t.Context(), "azone01", nil) require.Error(t, err) @@ -81,18 +100,20 @@ func TestClient_GetRecords_error(t *testing.T) { } func TestClient_RemoveRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/zones/azone01/records/arecord01", mockHandler(http.MethodDelete, http.StatusOK, "")) + client := mockBuilder(). + Route("DELETE /v1/zones/azone01/records/arecord01", nil). + Build(t) err := client.RemoveRecord(t.Context(), "azone01", "arecord01") require.NoError(t, err) } func TestClient_RemoveRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/zones/azone01/records/arecord01", mockHandler(http.MethodDelete, http.StatusInternalServerError, "remove_record_error.json")) + client := mockBuilder(). + Route("DELETE /v1/zones/azone01/records/arecord01", + servermock.ResponseFromFixture("remove_record_error.json"). + WithStatusCode(http.StatusInternalServerError)). + Build(t) err := client.RemoveRecord(t.Context(), "azone01", "arecord01") require.Error(t, err) @@ -103,9 +124,9 @@ func TestClient_RemoveRecord_error(t *testing.T) { } func TestClient_ReplaceRecords(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/zones/azone01", mockHandler(http.MethodPatch, http.StatusOK, "")) + client := mockBuilder(). + Route("PATCH /v1/zones/azone01", nil). + Build(t) records := []Record{{ ID: "22af3414-abbe-9e11-5df5-66fbe8e334b4", @@ -119,9 +140,11 @@ func TestClient_ReplaceRecords(t *testing.T) { } func TestClient_ReplaceRecords_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v1/zones/azone01", mockHandler(http.MethodPatch, http.StatusBadRequest, "replace_records_error.json")) + client := mockBuilder(). + Route("PATCH /v1/zones/azone01", + servermock.ResponseFromFixture("replace_records_error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) records := []Record{{ ID: "22af3414-abbe-9e11-5df5-66fbe8e334b4", @@ -137,47 +160,3 @@ func TestClient_ReplaceRecords_error(t *testing.T) { assert.ErrorAs(t, err, &cErr) assert.Equal(t, http.StatusBadRequest, cErr.StatusCode) } - -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client, err := NewClient("secret") - require.NoError(t, err) - - client.BaseURL, _ = url.Parse(server.URL) - - return client, mux -} - -func mockHandler(method string, statusCode int, filename string) func(http.ResponseWriter, *http.Request) { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - if filename == "" { - rw.WriteHeader(statusCode) - return - } - - file, err := os.Open(filepath.FromSlash(path.Join("./fixtures", filename))) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - rw.WriteHeader(statusCode) - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } -} diff --git a/providers/dns/ipv64/internal/client_test.go b/providers/dns/ipv64/internal/client_test.go index 8f97d8ff2..ba5ede9fc 100644 --- a/providers/dns/ipv64/internal/client_test.go +++ b/providers/dns/ipv64/internal/client_test.go @@ -1,66 +1,33 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const testAPIKey = "secret" -func setupTest(t *testing.T, handler http.HandlerFunc) *Client { - t.Helper() - - server := httptest.NewServer(handler) - +func setupClient(server *httptest.Server) (*Client, error) { client := NewClient(OAuthStaticAccessToken(server.Client(), testAPIKey)) client.HTTPClient = server.Client() client.baseURL, _ = url.Parse(server.URL) - return client -} - -func testHandler(method, filename string, statusCode int) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get("Authorization") - if auth != "Bearer "+testAPIKey { - http.Error(rw, fmt.Sprintf("invalid API key: %s", auth), http.StatusUnauthorized) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(statusCode) - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } + return client, nil } func TestClient_GetDomains(t *testing.T) { - client := setupTest(t, testHandler(http.MethodGet, "get_domains.json", http.StatusOK)) + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /api", + servermock.ResponseFromFixture("get_domains.json"), + servermock.CheckQueryParameter().Strict(). + With("get_domains", "")). + Build(t) domains, err := client.GetDomains(t.Context()) require.NoError(t, err) @@ -111,7 +78,11 @@ func TestClient_GetDomains(t *testing.T) { } func TestClient_GetDomains_error(t *testing.T) { - client := setupTest(t, testHandler(http.MethodGet, "error.json", http.StatusUnauthorized)) + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /api", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) domains, err := client.GetDomains(t.Context()) require.Error(t, err) @@ -120,28 +91,53 @@ func TestClient_GetDomains_error(t *testing.T) { } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, testHandler(http.MethodPost, "add_record.json", http.StatusCreated)) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader().WithContentTypeFromURLEncoded()). + Route("POST /api", + servermock.ResponseFromFixture("add_record.json"). + WithStatusCode(http.StatusCreated), + servermock.CheckForm().Strict(). + With("add_record", "lego.ipv64.net"). + With("content", "value"). + With("praefix", "_acme-challenge"). + With("type", "TXT"), + ). + Build(t) err := client.AddRecord(t.Context(), "lego.ipv64.net", "_acme-challenge", "TXT", "value") require.NoError(t, err) } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, testHandler(http.MethodPost, "add_record-error.json", http.StatusBadRequest)) + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /api", + servermock.ResponseFromFixture("add_record-error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) err := client.AddRecord(t.Context(), "lego.ipv64.net", "_acme-challenge", "TXT", "value") require.Error(t, err) } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, testHandler(http.MethodDelete, "del_record.json", http.StatusAccepted)) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader().WithContentTypeFromURLEncoded()). + Route("DELETE /api", + // the query parameters can be checked because the Go server ignores the body of a DELETE request. + servermock.ResponseFromFixture("del_record.json"). + WithStatusCode(http.StatusAccepted)). + Build(t) err := client.DeleteRecord(t.Context(), "lego.ipv64.net", "_acme-challenge", "TXT", "value") require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, testHandler(http.MethodDelete, "del_record-error.json", http.StatusBadRequest)) + client := servermock.NewBuilder[*Client](setupClient). + Route("DELETE /api", + servermock.ResponseFromFixture("del_record-error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) err := client.DeleteRecord(t.Context(), "lego.ipv64.net", "_acme-challenge", "TXT", "value") require.Error(t, err) diff --git a/providers/dns/iwantmyname/internal/client_test.go b/providers/dns/iwantmyname/internal/client_test.go index 39dca6dca..c25eb56ef 100644 --- a/providers/dns/iwantmyname/internal/client_test.go +++ b/providers/dns/iwantmyname/internal/client_test.go @@ -7,72 +7,32 @@ import ( "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -func checkParameter(query url.Values, key, expected string) error { - if query.Get(key) != expected { - return fmt.Errorf("%s: want %s got %s", key, expected, query.Get(key)) - } - return nil -} - -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - +func setupClient(server *httptest.Server) (*Client, error) { client := NewClient("user", "secret") client.HTTPClient = server.Client() client.baseURL, _ = url.Parse(server.URL) - return client, mux + return client, nil } func TestClient_Do(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - username, password, ok := req.BasicAuth() - if !ok { - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - if username != "user" { - http.Error(rw, fmt.Sprintf("username: want %s got %s", username, "user"), http.StatusUnauthorized) - return - } - - if password != "secret" { - http.Error(rw, fmt.Sprintf("password: want %s got %s", password, "secret"), http.StatusUnauthorized) - return - } - - query := req.URL.Query() - - values := map[string]string{ - "hostname": "example.com", - "type": "TXT", - "value": "data", - "ttl": "120", - } - - for k, v := range values { - err := checkParameter(query, k, v) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - } - }) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader(). + WithBasicAuth("user", "secret"), + ). + Route("POST /", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + fmt.Println(req) + }), + servermock.CheckQueryParameter().Strict(). + With("hostname", "example.com"). + With("ttl", "120"). + With("type", "TXT"). + With("value", "data")). + Build(t) record := Record{ Hostname: "example.com", diff --git a/providers/dns/joker/internal/dmapi/client_test.go b/providers/dns/joker/internal/dmapi/client_test.go index b7a294e09..5b6d68740 100644 --- a/providers/dns/joker/internal/dmapi/client_test.go +++ b/providers/dns/joker/internal/dmapi/client_test.go @@ -7,6 +7,7 @@ import ( "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -23,14 +24,17 @@ const ( serverErrorUsername = "error" ) -func setupTest(t *testing.T) (*http.ServeMux, string) { - t.Helper() +func mockBuilder(auth AuthInfo) *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(auth) + client.BaseURL = server.URL + client.HTTPClient = server.Client() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - return mux, server.URL + return client, nil + }, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded()) } func TestClient_GetZone(t *testing.T) { @@ -70,29 +74,24 @@ func TestClient_GetZone(t *testing.T) { }, } - mux, serverURL := setupTest(t) + client := mockBuilder(AuthInfo{APIKey: "12345"}). + Route("POST /dns-zone-get", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + authSid := req.FormValue("auth-sid") + domain := req.FormValue("domain") - mux.HandleFunc("/dns-zone-get", func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, http.MethodPost, r.Method) - - authSid := r.FormValue("auth-sid") - domain := r.FormValue("domain") - - switch { - case authSid == correctAPIKey && domain == "known": - _, _ = io.WriteString(w, "Status-Code: 0\nStatus-Text: OK\n\n"+testZone) - case authSid == incorrectAPIKey || (authSid == correctAPIKey && domain == "unknown"): - _, _ = io.WriteString(w, "Status-Code: 2202\nStatus-Text: Authorization error") - default: - http.NotFound(w, r) - } - }) + switch { + case authSid == correctAPIKey && domain == "known": + _, _ = io.WriteString(rw, "Status-Code: 0\nStatus-Text: OK\n\n"+testZone) + case authSid == incorrectAPIKey || (authSid == correctAPIKey && domain == "unknown"): + _, _ = io.WriteString(rw, "Status-Code: 2202\nStatus-Text: Authorization error") + default: + http.NotFound(rw, req) + } + })). + Build(t) for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client := NewClient(AuthInfo{APIKey: "12345"}) - client.BaseURL = serverURL - response, err := client.GetZone(mockContext(t, test.authSid), test.domain) if test.expectedError { require.Error(t, err) diff --git a/providers/dns/joker/internal/dmapi/identity_test.go b/providers/dns/joker/internal/dmapi/identity_test.go index b84321096..d2a80f2e6 100644 --- a/providers/dns/joker/internal/dmapi/identity_test.go +++ b/providers/dns/joker/internal/dmapi/identity_test.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "net/http" - "net/http/httptest" "sync/atomic" "testing" "time" @@ -58,27 +57,22 @@ func TestClient_login_apikey(t *testing.T) { }, } - mux, serverURL := setupTest(t) - - mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, http.MethodPost, r.Method) - - switch r.FormValue("api-key") { - case correctAPIKey: - _, _ = io.WriteString(w, "Status-Code: 0\nStatus-Text: OK\nAuth-Sid: 123\n\ncom\nnet") - case incorrectAPIKey: - _, _ = io.WriteString(w, "Status-Code: 2200\nStatus-Text: Authentication error") - case serverErrorAPIKey: - http.NotFound(w, r) - default: - _, _ = io.WriteString(w, "Status-Code: 2202\nStatus-Text: OK\n\ncom\nnet") - } - }) - for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client := NewClient(AuthInfo{APIKey: test.apiKey}) - client.BaseURL = serverURL + client := mockBuilder(AuthInfo{APIKey: test.apiKey}). + Route("POST /login", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + switch req.FormValue("api-key") { + case correctAPIKey: + _, _ = io.WriteString(rw, "Status-Code: 0\nStatus-Text: OK\nAuth-Sid: 123\n\ncom\nnet") + case incorrectAPIKey: + _, _ = io.WriteString(rw, "Status-Code: 2200\nStatus-Text: Authentication error") + case serverErrorAPIKey: + http.NotFound(rw, req) + default: + _, _ = io.WriteString(rw, "Status-Code: 2202\nStatus-Text: OK\n\ncom\nnet") + } + })). + Build(t) response, err := client.login(t.Context()) if test.expectedError { @@ -133,27 +127,22 @@ func TestClient_login_username(t *testing.T) { }, } - mux, serverURL := setupTest(t) - - mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, http.MethodPost, r.Method) - - switch r.FormValue("username") { - case correctUsername: - _, _ = io.WriteString(w, "Status-Code: 0\nStatus-Text: OK\nAuth-Sid: 123\n\ncom\nnet") - case incorrectUsername: - _, _ = io.WriteString(w, "Status-Code: 2200\nStatus-Text: Authentication error") - case serverErrorUsername: - http.NotFound(w, r) - default: - _, _ = io.WriteString(w, "Status-Code: 2202\nStatus-Text: OK\n\ncom\nnet") - } - }) - for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client := NewClient(AuthInfo{Username: test.username, Password: test.password}) - client.BaseURL = serverURL + client := mockBuilder(AuthInfo{Username: test.username, Password: test.password}). + Route("POST /login", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + switch req.FormValue("username") { + case correctUsername: + _, _ = io.WriteString(rw, "Status-Code: 0\nStatus-Text: OK\nAuth-Sid: 123\n\ncom\nnet") + case incorrectUsername: + _, _ = io.WriteString(rw, "Status-Code: 2200\nStatus-Text: Authentication error") + case serverErrorUsername: + http.NotFound(rw, req) + default: + _, _ = io.WriteString(rw, "Status-Code: 2202\nStatus-Text: OK\n\ncom\nnet") + } + })). + Build(t) response, err := client.login(t.Context()) if test.expectedError { @@ -197,25 +186,21 @@ func TestClient_logout(t *testing.T) { }, } - mux, serverURL := setupTest(t) - - mux.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, http.MethodPost, r.Method) - - switch r.FormValue("auth-sid") { - case correctAPIKey: - _, _ = io.WriteString(w, "Status-Code: 0\nStatus-Text: OK\n") - case incorrectAPIKey: - _, _ = io.WriteString(w, "Status-Code: 2200\nStatus-Text: Authentication error") - default: - http.NotFound(w, r) - } - }) - for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client := NewClient(AuthInfo{APIKey: "12345"}) - client.BaseURL = serverURL + client := mockBuilder(AuthInfo{APIKey: "12345"}). + Route("POST /logout", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + switch req.FormValue("auth-sid") { + case correctAPIKey: + _, _ = io.WriteString(rw, "Status-Code: 0\nStatus-Text: OK\n") + case incorrectAPIKey: + _, _ = io.WriteString(rw, "Status-Code: 2200\nStatus-Text: Authentication error") + default: + http.NotFound(rw, req) + } + })). + Build(t) + client.token = &Token{SessionID: test.authSid} response, err := client.Logout(mockContext(t, test.authSid)) @@ -231,29 +216,21 @@ func TestClient_logout(t *testing.T) { } func TestClient_CreateAuthenticatedContext(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - id := atomic.Int32{} id.Add(100) - mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, http.MethodPost, r.Method) + client := mockBuilder(AuthInfo{Username: correctUsername, Password: "secret"}). + Route("POST /login", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + switch req.FormValue("username") { + case correctUsername: + _, _ = fmt.Fprintf(rw, "Status-Code: 0\nStatus-Text: OK\nAuth-Sid: %d\n\ncom\nnet", id.Load()) + id.Add(100) - switch r.FormValue("username") { - case correctUsername: - _, _ = fmt.Fprintf(w, "Status-Code: 0\nStatus-Text: OK\nAuth-Sid: %d\n\ncom\nnet", id.Load()) - id.Add(100) - - default: - _, _ = io.WriteString(w, "Status-Code: 2200\nStatus-Text: Authentication error") - } - }) - - client := NewClient(AuthInfo{Username: correctUsername, Password: "secret"}) - client.HTTPClient = server.Client() - client.BaseURL = server.URL + default: + _, _ = io.WriteString(rw, "Status-Code: 2200\nStatus-Text: Authentication error") + } + })). + Build(t) ctx, err := client.CreateAuthenticatedContext(t.Context()) require.NoError(t, err) diff --git a/providers/dns/joker/internal/svc/client_test.go b/providers/dns/joker/internal/svc/client_test.go index a396f67e5..a6cb299e4 100644 --- a/providers/dns/joker/internal/svc/client_test.go +++ b/providers/dns/joker/internal/svc/client_test.go @@ -1,51 +1,39 @@ package svc import ( - "fmt" - "io" - "net/http" "net/http/httptest" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("test", "secret") + client.BaseURL = server.URL + client.HTTPClient = server.Client() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient("test", "secret") - client.BaseURL = server.URL - client.HTTPClient = server.Client() - - return client, mux + return client, nil + }, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded()) } func TestClient_Send(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - all, _ := io.ReadAll(req.Body) - - if string(all) != "label=_acme-challenge&password=secret&type=TXT&username=test&value=123&zone=example.com" { - http.Error(rw, fmt.Sprintf("invalid request: %q", string(all)), http.StatusBadRequest) - return - } - - _, err := rw.Write([]byte("OK: 1 inserted, 0 deleted")) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("POST /", + servermock.RawStringResponse("OK: 1 inserted, 0 deleted"), + servermock.CheckForm().Strict(). + With("zone", "example.com"). + With("label", "_acme-challenge"). + With("type", "TXT"). + With("value", "123"). + With("username", "test"). + With("password", "secret"), + ). + Build(t) zone := "example.com" label := "_acme-challenge" @@ -56,27 +44,18 @@ func TestClient_Send(t *testing.T) { } func TestClient_Send_empty(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - all, _ := io.ReadAll(req.Body) - - if string(all) != "label=_acme-challenge&password=secret&type=TXT&username=test&value=&zone=example.com" { - http.Error(rw, fmt.Sprintf("invalid request: %q", string(all)), http.StatusBadRequest) - return - } - - _, err := rw.Write([]byte("OK: 1 inserted, 0 deleted")) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("POST /", + servermock.RawStringResponse("OK: 1 inserted, 0 deleted"), + servermock.CheckForm().Strict(). + With("zone", "example.com"). + With("label", "_acme-challenge"). + With("type", "TXT"). + With("value", ""). + With("username", "test"). + With("password", "secret"), + ). + Build(t) zone := "example.com" label := "_acme-challenge" diff --git a/providers/dns/liara/internal/client_test.go b/providers/dns/liara/internal/client_test.go index 233a4bc2b..57ac7e8b3 100644 --- a/providers/dns/liara/internal/client_test.go +++ b/providers/dns/liara/internal/client_test.go @@ -1,25 +1,34 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const apiKey = "key" -func TestClient_GetRecords(t *testing.T) { - client, mux := setupTest(t) +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(OAuthStaticAccessToken(server.Client(), apiKey)) + client.baseURL, _ = url.Parse(server.URL) - mux.HandleFunc("/api/v1/zones/example.com/dns-records", testHandler("./RecordsResponse.json", http.MethodGet, http.StatusOK)) + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Bearer "+apiKey)) +} + +func TestClient_GetRecords(t *testing.T) { + client := mockBuilder(). + Route("GET /api/v1/zones/example.com/dns-records", servermock.ResponseFromFixture("RecordsResponse.json")). + Build(t) records, err := client.GetRecords(t.Context(), "example.com") require.NoError(t, err) @@ -41,9 +50,9 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_GetRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/api/v1/zones/example.com/dns-records/123", testHandler("./RecordResponse.json", http.MethodGet, http.StatusOK)) + client := mockBuilder(). + Route("GET /api/v1/zones/example.com/dns-records/123", servermock.ResponseFromFixture("RecordResponse.json")). + Build(t) record, err := client.GetRecord(t.Context(), "example.com", "123") require.NoError(t, err) @@ -63,9 +72,12 @@ func TestClient_GetRecord(t *testing.T) { } func TestClient_CreateRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/api/v1/zones/example.com/dns-records", testHandler("./RecordResponse.json", http.MethodPost, http.StatusCreated)) + client := mockBuilder(). + Route("POST /api/v1/zones/example.com/dns-records", + servermock.ResponseFromFixture("RecordResponse.json"). + WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBody(`{"name":"string","type":"string","ttl":3600,"contents":[{"text":"string"}]}`)). + Build(t) data := Record{ Type: "string", @@ -97,76 +109,34 @@ func TestClient_CreateRecord(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/api/v1/zones/example.com/dns-records/123", func(rw http.ResponseWriter, req *http.Request) { - rw.WriteHeader(http.StatusNoContent) - }) + client := mockBuilder(). + Route("DELETE /api/v1/zones/example.com/dns-records/123", + servermock.Noop(). + WithStatusCode(http.StatusNoContent)). + Build(t) err := client.DeleteRecord(t.Context(), "example.com", "123") require.NoError(t, err) } func TestClient_DeleteRecord_NotFound_Response(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/api/v1/zones/example.com/dns-records/123", func(rw http.ResponseWriter, req *http.Request) { - rw.WriteHeader(http.StatusNotFound) - }) + client := mockBuilder(). + Route("DELETE /api/v1/zones/example.com/dns-records/123", + servermock.Noop(). + WithStatusCode(http.StatusNotFound)). + Build(t) err := client.DeleteRecord(t.Context(), "example.com", "123") require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/api/v1/zones/example.com/dns-records/123", testHandler("./error.json", http.MethodDelete, http.StatusUnauthorized)) + client := mockBuilder(). + Route("DELETE /api/v1/zones/example.com/dns-records/123", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) err := client.DeleteRecord(t.Context(), "example.com", "123") - require.Error(t, err) -} - -func testHandler(filename, method string, statusCode int) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get("Authorization") - if auth != "Bearer "+apiKey { - http.Error(rw, "invalid Authorization header", http.StatusUnauthorized) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(statusCode) - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } -} - -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient(OAuthStaticAccessToken(server.Client(), apiKey)) - client.baseURL, _ = url.Parse(server.URL) - - return client, mux + require.EqualError(t, err, "[status code: 401] Unauthorized: Invalid token missing header") } diff --git a/providers/dns/lightsail/lightsail_test.go b/providers/dns/lightsail/lightsail_test.go index 4a11f6eb4..010e794a9 100644 --- a/providers/dns/lightsail/lightsail_test.go +++ b/providers/dns/lightsail/lightsail_test.go @@ -1,6 +1,7 @@ package lightsail import ( + "net/http/httptest" "os" "testing" @@ -9,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/lightsail" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -30,20 +32,6 @@ var envTest = tester.NewEnvTest( WithDomain(EnvDNSZone). WithLiveTestRequirements(envAwsAccessKeyID, envAwsSecretAccessKey, EnvDNSZone) -func makeProvider(serverURL string) *DNSProvider { - config := aws.Config{ - Credentials: credentials.NewStaticCredentialsProvider("abc", "123", " "), - Region: "mock-region", - BaseEndpoint: aws.String(serverURL), - RetryMaxAttempts: 1, - } - - return &DNSProvider{ - client: lightsail.NewFromConfig(config), - config: NewDefaultConfig(), - } -} - func TestCredentialsFromEnv(t *testing.T) { defer envTest.RestoreEnv() envTest.ClearEnv() @@ -68,13 +56,20 @@ func TestCredentialsFromEnv(t *testing.T) { } func TestDNSProvider_Present(t *testing.T) { - mockResponses := map[string]MockResponse{ - "/": {StatusCode: 200, Body: ""}, - } - - serverURL := newMockServer(t, mockResponses) - - provider := makeProvider(serverURL) + provider := servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + return &DNSProvider{ + client: lightsail.NewFromConfig(aws.Config{ + Credentials: credentials.NewStaticCredentialsProvider("abc", "123", " "), + Region: "mock-region", + BaseEndpoint: aws.String(server.URL), + RetryMaxAttempts: 1, + }), + config: NewDefaultConfig(), + }, nil + }). + Route("POST /", nil). + Build(t) domain := "example.com" keyAuth := "123456d==" diff --git a/providers/dns/lightsail/mock_server_test.go b/providers/dns/lightsail/mock_server_test.go deleted file mode 100644 index 385c80850..000000000 --- a/providers/dns/lightsail/mock_server_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package lightsail - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -// MockResponse represents a predefined response used by a mock server. -type MockResponse struct { - StatusCode int - Body string -} - -func newMockServer(t *testing.T, responses map[string]MockResponse) string { - t.Helper() - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - path := r.URL.Path - resp, ok := responses[path] - if !ok { - msg := fmt.Sprintf("Requested path not found in response map: %s", path) - require.FailNow(t, msg) - } - - w.Header().Set("Content-Type", "application/xml") - w.WriteHeader(resp.StatusCode) - _, err := w.Write([]byte(resp.Body)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - })) - - t.Cleanup(server.Close) - - time.Sleep(100 * time.Millisecond) - - return server.URL -} diff --git a/providers/dns/limacity/internal/client_test.go b/providers/dns/limacity/internal/client_test.go index 307783953..c43f12ba2 100644 --- a/providers/dns/limacity/internal/client_test.go +++ b/providers/dns/limacity/internal/client_test.go @@ -1,69 +1,36 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const apiKey = "secret" -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(apiKey) + client.baseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient(apiKey) - client.baseURL, _ = url.Parse(server.URL) - - return client, mux -} - -func testHandler(filename, method string, statusCode int) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - username, key, ok := req.BasicAuth() - if username != "api" || key != apiKey || !ok { - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(statusCode) - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithBasicAuth("api", apiKey), + ) } func TestClient_GetDomains(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains.json", testHandler("get-domains.json", http.MethodGet, http.StatusOK)) + client := mockBuilder(). + Route("GET /domains.json", servermock.ResponseFromFixture("get-domains.json")). + Build(t) domains, err := client.GetDomains(t.Context()) require.NoError(t, err) @@ -79,18 +46,20 @@ func TestClient_GetDomains(t *testing.T) { } func TestClient_GetDomains_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains.json", testHandler("error.json", http.MethodGet, http.StatusBadRequest)) + client := mockBuilder(). + Route("GET /domains.json", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) _, err := client.GetDomains(t.Context()) require.EqualError(t, err, "[status code: 400] status: invalid_resource, details: name: [muss ausgefüllt werden]") } func TestClient_GetRecords(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/123/records.json", testHandler("get-records.json", http.MethodGet, http.StatusOK)) + client := mockBuilder(). + Route("GET /domains/123/records.json", servermock.ResponseFromFixture("get-records.json")). + Build(t) records, err := client.GetRecords(t.Context(), 123) require.NoError(t, err) @@ -115,18 +84,22 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_GetRecords_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/123/records.json", testHandler("error.json", http.MethodGet, http.StatusBadRequest)) + client := mockBuilder(). + Route("GET /domains/123/records.json", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) _, err := client.GetRecords(t.Context(), 123) require.EqualError(t, err, "[status code: 400] status: invalid_resource, details: name: [muss ausgefüllt werden]") } func TestClient_AddRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/123/records.json", testHandler("ok.json", http.MethodPost, http.StatusOK)) + client := mockBuilder(). + Route("POST /domains/123/records.json", + servermock.ResponseFromFixture("ok.json"), + servermock.CheckRequestJSONBody(`{"nameserver_record":{"name":"foo","content":"bar","ttl":12,"type":"TXT"}}`)). + Build(t) record := Record{ Name: "foo", @@ -140,9 +113,11 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/123/records.json", testHandler("error.json", http.MethodPost, http.StatusBadRequest)) + client := mockBuilder(). + Route("POST /domains/123/records.json", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) record := Record{ Name: "foo", @@ -156,36 +131,43 @@ func TestClient_AddRecord_error(t *testing.T) { } func TestClient_UpdateRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/123/records/456", testHandler("ok.json", http.MethodPut, http.StatusOK)) + client := mockBuilder(). + Route("PUT /domains/123/records/456", + servermock.ResponseFromFixture("ok.json"), + servermock.CheckRequestJSONBody(`{"nameserver_record":{}}`)). + Build(t) err := client.UpdateRecord(t.Context(), 123, 456, Record{}) require.NoError(t, err) } func TestClient_UpdateRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/123/records/456", testHandler("error.json", http.MethodPut, http.StatusBadRequest)) + client := mockBuilder(). + Route("PUT /domains/123/records/456", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) err := client.UpdateRecord(t.Context(), 123, 456, Record{}) require.EqualError(t, err, "[status code: 400] status: invalid_resource, details: name: [muss ausgefüllt werden]") } func TestClient_DeleteRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/123/records/456", testHandler("ok.json", http.MethodDelete, http.StatusOK)) + client := mockBuilder(). + Route("DELETE /domains/123/records/456", + servermock.ResponseFromFixture("ok.json")). + Build(t) err := client.DeleteRecord(t.Context(), 123, 456) require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/123/records/456", testHandler("error.json", http.MethodDelete, http.StatusBadRequest)) + client := mockBuilder(). + Route("DELETE /domains/123/records/456", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) err := client.DeleteRecord(t.Context(), 123, 456) require.EqualError(t, err, "[status code: 400] status: invalid_resource, details: name: [muss ausgefüllt werden]") diff --git a/providers/dns/linode/linode_test.go b/providers/dns/linode/linode_test.go index a6b8041f8..08549ab7e 100644 --- a/providers/dns/linode/linode_test.go +++ b/providers/dns/linode/linode_test.go @@ -1,69 +1,20 @@ package linode import ( - "encoding/json" - "fmt" "net/http" "net/http/httptest" "os" "testing" - "time" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/linode/linodego" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -type MockResponseMap map[string]any - var envTest = tester.NewEnvTest(EnvToken) -func setupTest(t *testing.T, responses MockResponseMap) string { - t.Helper() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Ensure that we support the requested action. - action := r.Method + ":" + r.URL.Path - resp, ok := responses[action] - if !ok { - http.Error(w, fmt.Sprintf("Unsupported mock action: %q", action), http.StatusInternalServerError) - return - } - - rawResponse, err := json.Marshal(resp) - if err != nil { - http.Error(w, fmt.Sprintf("Failed to JSON encode response: %v", err), http.StatusInternalServerError) - return - } - - // Send the response. - w.Header().Set("Content-Type", "application/json") - if err, ok := resp.(linodego.APIError); ok { - if err.Errors[0].Reason == "Not found" { - w.WriteHeader(http.StatusNotFound) - } else { - w.WriteHeader(http.StatusBadRequest) - } - } else { - w.WriteHeader(http.StatusOK) - } - - _, err = w.Write(rawResponse) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - server := httptest.NewServer(handler) - t.Cleanup(server.Close) - - time.Sleep(100 * time.Millisecond) - - return server.URL -} - func TestNewDNSProvider(t *testing.T) { testCases := []struct { desc string @@ -145,81 +96,77 @@ func TestDNSProvider_Present(t *testing.T) { defer envTest.RestoreEnv() os.Setenv(EnvToken, "testing") - p, err := NewDNSProvider() - require.NoError(t, err) - require.NotNil(t, p) - domain := "example.com" keyAuth := "dGVzdGluZw==" testCases := []struct { desc string - mockResponses MockResponseMap + builder *servermock.Builder[*DNSProvider] expectedError string }{ { desc: "Success", - mockResponses: MockResponseMap{ - "GET:/v4/domains": linodego.DomainsPagedResponse{ - PageOptions: &linodego.PageOptions{ - Pages: 1, - Results: 1, - Page: 1, - }, - Data: []linodego.Domain{{ - Domain: domain, - ID: 1234, - }}, - }, - "POST:/v4/domains/1234/records": linodego.DomainRecord{ + builder: mockBuilder(). + Route("GET /v4/domains", + servermock.JSONEncode(linodego.DomainsPagedResponse{ + PageOptions: &linodego.PageOptions{ + Pages: 1, + Results: 1, + Page: 1, + }, + Data: []linodego.Domain{{ + Domain: domain, + ID: 1234, + }}, + })). + Route("POST /v4/domains/1234/records", servermock.JSONEncode(linodego.DomainRecord{ ID: 1234, - }, - }, + })), }, { desc: "NoDomain", - mockResponses: MockResponseMap{ - "GET:/v4/domains": linodego.APIError{ - Errors: []linodego.APIErrorReason{{ - Reason: "Not found", - }}, - }, - }, + builder: mockBuilder(). + Route("GET /v4/domains", + servermock.JSONEncode(linodego.APIError{ + Errors: []linodego.APIErrorReason{{ + Reason: "Not found", + }}, + }). + WithStatusCode(http.StatusNotFound)), expectedError: "[404] Not found", }, { desc: "CreateFailed", - mockResponses: MockResponseMap{ - "GET:/v4/domains": &linodego.DomainsPagedResponse{ - PageOptions: &linodego.PageOptions{ - Pages: 1, - Results: 1, - Page: 1, - }, - Data: []linodego.Domain{{ - Domain: "example.com", - ID: 1234, - }}, - }, - "POST:/v4/domains/1234/records": linodego.APIError{ - Errors: []linodego.APIErrorReason{{ - Reason: "Failed to create domain resource", - Field: "somefield", - }}, - }, - }, + builder: mockBuilder(). + Route("GET /v4/domains", + servermock.JSONEncode(&linodego.DomainsPagedResponse{ + PageOptions: &linodego.PageOptions{ + Pages: 1, + Results: 1, + Page: 1, + }, + Data: []linodego.Domain{{ + Domain: "example.com", + ID: 1234, + }}, + })). + Route("POST /v4/domains/1234/records", + servermock.JSONEncode(linodego.APIError{ + Errors: []linodego.APIErrorReason{{ + Reason: "Failed to create domain resource", + Field: "somefield", + }}, + }). + WithStatusCode(http.StatusBadRequest)), expectedError: "[400] [somefield] Failed to create domain resource", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - serverURL := setupTest(t, test.mockResponses) + provider := test.builder.Build(t) - assert.NotNil(t, p.client) - p.client.SetBaseURL(serverURL) - - err = p.Present(domain, "", keyAuth) + err := provider.Present(domain, "", keyAuth) if test.expectedError == "" { assert.NoError(t, err) } else { @@ -233,107 +180,111 @@ func TestDNSProvider_CleanUp(t *testing.T) { defer envTest.RestoreEnv() os.Setenv(EnvToken, "testing") - p, err := NewDNSProvider() - require.NoError(t, err) - domain := "example.com" keyAuth := "dGVzdGluZw==" testCases := []struct { desc string - mockResponses MockResponseMap + builder *servermock.Builder[*DNSProvider] expectedError string }{ { desc: "Success", - mockResponses: MockResponseMap{ - "GET:/v4/domains": &linodego.DomainsPagedResponse{ - PageOptions: &linodego.PageOptions{ - Pages: 1, - Results: 1, - Page: 1, - }, - Data: []linodego.Domain{{ - Domain: "foobar.com", - ID: 1234, - }}, - }, - "GET:/v4/domains/1234/records": &linodego.DomainRecordsPagedResponse{ - PageOptions: &linodego.PageOptions{ - Pages: 1, - Results: 1, - Page: 1, - }, - Data: []linodego.DomainRecord{{ - ID: 1234, - Name: "_acme-challenge", - Target: "ElbOJKOkFWiZLQeoxf-wb3IpOsQCdvoM0y_wn0TEkxM", - Type: "TXT", - }}, - }, - "DELETE:/v4/domains/1234/records/1234": struct{}{}, - }, + builder: mockBuilder(). + Route("GET /v4/domains", + servermock.JSONEncode(&linodego.DomainsPagedResponse{ + PageOptions: &linodego.PageOptions{ + Pages: 1, + Results: 1, + Page: 1, + }, + Data: []linodego.Domain{{ + Domain: "foobar.com", + ID: 1234, + }}, + })). + Route("GET /v4/domains/1234/records", + servermock.JSONEncode(&linodego.DomainRecordsPagedResponse{ + PageOptions: &linodego.PageOptions{ + Pages: 1, + Results: 1, + Page: 1, + }, + Data: []linodego.DomainRecord{{ + ID: 1234, + Name: "_acme-challenge", + Target: "ElbOJKOkFWiZLQeoxf-wb3IpOsQCdvoM0y_wn0TEkxM", + Type: "TXT", + }}, + })). + Route("DELETE /v4/domains/1234/records/1234", + servermock.RawStringResponse("{}").WithHeader("Content-Type", "application/json")), }, { desc: "NoDomain", - mockResponses: MockResponseMap{ - "GET:/v4/domains": linodego.APIError{ - Errors: []linodego.APIErrorReason{{ - Reason: "Not found", - }}, - }, - "GET:/v4/domains/1234/records": linodego.APIError{ - Errors: []linodego.APIErrorReason{{ - Reason: "Not found", - }}, - }, - }, + builder: mockBuilder(). + Route("GET /v4/domains", + servermock.JSONEncode(linodego.APIError{ + Errors: []linodego.APIErrorReason{{ + Reason: "Not found", + }}, + }). + WithStatusCode(http.StatusNotFound)). + Route("GET /v4/domains/1234/records", + servermock.JSONEncode(linodego.APIError{ + Errors: []linodego.APIErrorReason{{ + Reason: "Not found", + }}, + }, + ). + WithStatusCode(http.StatusNotFound)), expectedError: "[404] Not found", }, { desc: "DeleteFailed", - mockResponses: MockResponseMap{ - "GET:/v4/domains": linodego.DomainsPagedResponse{ - PageOptions: &linodego.PageOptions{ - Pages: 1, - Results: 1, - Page: 1, - }, - Data: []linodego.Domain{{ - ID: 1234, - Domain: "example.com", - }}, - }, - "GET:/v4/domains/1234/records": linodego.DomainRecordsPagedResponse{ - PageOptions: &linodego.PageOptions{ - Pages: 1, - Results: 1, - Page: 1, - }, - Data: []linodego.DomainRecord{{ - ID: 1234, - Name: "_acme-challenge", - Target: "ElbOJKOkFWiZLQeoxf-wb3IpOsQCdvoM0y_wn0TEkxM", - Type: "TXT", - }}, - }, - "DELETE:/v4/domains/1234/records/1234": linodego.APIError{ - Errors: []linodego.APIErrorReason{{ - Reason: "Failed to delete domain resource", - }}, - }, - }, + builder: mockBuilder(). + Route("GET /v4/domains", + servermock.JSONEncode(linodego.DomainsPagedResponse{ + PageOptions: &linodego.PageOptions{ + Pages: 1, + Results: 1, + Page: 1, + }, + Data: []linodego.Domain{{ + ID: 1234, + Domain: "example.com", + }}, + })). + Route("GET /v4/domains/1234/records", + servermock.JSONEncode(linodego.DomainRecordsPagedResponse{ + PageOptions: &linodego.PageOptions{ + Pages: 1, + Results: 1, + Page: 1, + }, + Data: []linodego.DomainRecord{{ + ID: 1234, + Name: "_acme-challenge", + Target: "ElbOJKOkFWiZLQeoxf-wb3IpOsQCdvoM0y_wn0TEkxM", + Type: "TXT", + }}, + })). + Route("DELETE /v4/domains/1234/records/1234", + servermock.JSONEncode(linodego.APIError{ + Errors: []linodego.APIErrorReason{{ + Reason: "Failed to delete domain resource", + }}, + }). + WithStatusCode(http.StatusBadRequest)), expectedError: "[400] Failed to delete domain resource", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - serverURL := setupTest(t, test.mockResponses) + provider := test.builder.Build(t) - p.client.SetBaseURL(serverURL) - - err = p.CleanUp(domain, "", keyAuth) + err := provider.CleanUp(domain, "", keyAuth) if test.expectedError == "" { assert.NoError(t, err) } else { @@ -356,3 +307,16 @@ func TestLiveCleanUp(t *testing.T) { } // TODO implement this test } + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { + p, err := NewDNSProvider() + if err != nil { + return nil, err + } + + p.client.SetBaseURL(server.URL) + + return p, nil + }) +} diff --git a/providers/dns/liquidweb/liquidweb_test.go b/providers/dns/liquidweb/liquidweb_test.go index a26b18e1b..b0788c7f5 100644 --- a/providers/dns/liquidweb/liquidweb_test.go +++ b/providers/dns/liquidweb/liquidweb_test.go @@ -18,22 +18,6 @@ var envTest = tester.NewEnvTest( EnvZone). WithDomain(envDomain) -func setupTest(t *testing.T, initRecs ...network.DNSRecord) *DNSProvider { - t.Helper() - - serverURL := mockAPIServer(t, initRecs) - - config := NewDefaultConfig() - config.Username = "blars" - config.Password = "tacoman" - config.BaseURL = serverURL - - provider, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - return provider -} - func TestNewDNSProvider(t *testing.T) { testCases := []struct { desc string @@ -161,14 +145,14 @@ func TestNewDNSProviderConfig(t *testing.T) { } func TestDNSProvider_Present(t *testing.T) { - provider := setupTest(t) + provider := mockProvider(t) err := provider.Present("tacoman.com", "", "") require.NoError(t, err) } func TestDNSProvider_CleanUp(t *testing.T) { - provider := setupTest(t, network.DNSRecord{ + provider := mockProvider(t, network.DNSRecord{ Name: "_acme-challenge.tacoman.com", RData: "123d==", Type: "TXT", @@ -239,7 +223,7 @@ func TestDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - provider := setupTest(t, test.initRecs...) + provider := mockProvider(t, test.initRecs...) if test.present { err := provider.Present(test.domain, test.token, test.keyAuth) diff --git a/providers/dns/liquidweb/servermock_test.go b/providers/dns/liquidweb/servermock_test.go index 8c22595af..9cb434761 100644 --- a/providers/dns/liquidweb/servermock_test.go +++ b/providers/dns/liquidweb/servermock_test.go @@ -1,7 +1,6 @@ package liquidweb import ( - "bytes" "encoding/json" "fmt" "io" @@ -10,11 +9,12 @@ import ( "net/http/httptest" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/liquidweb/liquidweb-go/network" "github.com/liquidweb/liquidweb-go/types" ) -func mockAPIServer(t *testing.T, initRecs []network.DNSRecord) string { +func mockProvider(t *testing.T, initRecs ...network.DNSRecord) *DNSProvider { t.Helper() recs := make(map[int]network.DNSRecord) @@ -23,157 +23,137 @@ func mockAPIServer(t *testing.T, initRecs []network.DNSRecord) string { recs[int(rec.ID)] = rec } - mux := http.NewServeMux() - mux.Handle("/v1/Network/DNS/Record/delete", mockAPIDelete(recs)) - mux.Handle("/v1/Network/DNS/Record/create", mockAPICreate(recs)) - mux.Handle("/v1/Network/DNS/Zone/list", mockAPIListZones()) - mux.Handle("/bleed/Network/DNS/Record/delete", mockAPIDelete(recs)) - mux.Handle("/bleed/Network/DNS/Record/create", mockAPICreate(recs)) - mux.Handle("/bleed/Network/DNS/Zone/list", mockAPIListZones()) + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.Username = "blars" + config.Password = "tacoman" + config.BaseURL = server.URL - server := httptest.NewServer(requireBasicAuth(requireJSON(mux))) - t.Cleanup(server.Close) - - return server.URL -} - -func requireBasicAuth(next http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - username, password, ok := r.BasicAuth() - if ok && username == "blars" && password == "tacoman" { - next.ServeHTTP(w, r) - return - } - - http.Error(w, "invalid auth", http.StatusForbidden) - } -} - -func requireJSON(next http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - buf := &bytes.Buffer{} - - _, err := buf.ReadFrom(r.Body) - if err != nil { - http.Error(w, "malformed request - json required", http.StatusBadRequest) - return - } - - r.Body = io.NopCloser(buf) - next.ServeHTTP(w, r) - } + return NewDNSProviderConfig(config) + }, + servermock.CheckHeader(). + WithBasicAuth("blars", "tacoman"), + ). + Route("/v1/Network/DNS/Record/delete", mockAPIDelete(recs)). + Route("/v1/Network/DNS/Record/create", mockAPICreate(recs)). + Route("/v1/Network/DNS/Zone/list", mockAPIListZones()). + Route("/bleed/Network/DNS/Record/delete", mockAPIDelete(recs)). + Route("/bleed/Network/DNS/Record/create", mockAPICreate(recs)). + Route("/bleed/Network/DNS/Zone/list", mockAPIListZones()). + Build(t) } func mockAPICreate(recs map[int]network.DNSRecord) http.HandlerFunc { _, mockAPIServerZones := makeMockZones() - return func(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) + return func(rw http.ResponseWriter, req *http.Request) { + body, err := io.ReadAll(req.Body) if err != nil { - http.Error(w, "invalid request", http.StatusInternalServerError) + http.Error(rw, "invalid request", http.StatusInternalServerError) return } - req := struct { + payload := struct { Params network.DNSRecord `json:"params"` }{} - if err = json.Unmarshal(body, &req); err != nil { - http.Error(w, makeEncodingError(body), http.StatusBadRequest) + if err = json.Unmarshal(body, &payload); err != nil { + http.Error(rw, makeEncodingError(body), http.StatusBadRequest) return } - req.Params.ID = types.FlexInt(rand.Intn(10000000)) - req.Params.ZoneID = types.FlexInt(mockAPIServerZones[req.Params.Name]) + payload.Params.ID = types.FlexInt(rand.Intn(10000000)) + payload.Params.ZoneID = types.FlexInt(mockAPIServerZones[payload.Params.Name]) - if _, exists := recs[int(req.Params.ID)]; exists { - http.Error(w, "dns record already exists", http.StatusTeapot) + if _, exists := recs[int(payload.Params.ID)]; exists { + http.Error(rw, "dns record already exists", http.StatusTeapot) return } - recs[int(req.Params.ID)] = req.Params + recs[int(payload.Params.ID)] = payload.Params - resp, err := json.Marshal(req.Params) + resp, err := json.Marshal(payload.Params) if err != nil { - http.Error(w, "", http.StatusInternalServerError) + http.Error(rw, "", http.StatusInternalServerError) return } - http.Error(w, string(resp), http.StatusOK) + http.Error(rw, string(resp), http.StatusOK) } } func mockAPIDelete(recs map[int]network.DNSRecord) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) + return func(rw http.ResponseWriter, req *http.Request) { + body, err := io.ReadAll(req.Body) if err != nil { - http.Error(w, "invalid request", http.StatusInternalServerError) + http.Error(rw, "invalid request", http.StatusInternalServerError) return } - req := struct { + payload := struct { Params struct { Name string `json:"name"` ID int `json:"id"` } `json:"params"` }{} - if err := json.Unmarshal(body, &req); err != nil { - http.Error(w, makeEncodingError(body), http.StatusBadRequest) + if err := json.Unmarshal(body, &payload); err != nil { + http.Error(rw, makeEncodingError(body), http.StatusBadRequest) return } - if req.Params.ID == 0 { - http.Error(w, `{"error":"","error_class":"LW::Exception::Input::Multiple","errors":[{"error":"","error_class":"LW::Exception::Input::Required","field":"id","full_message":"The required field 'id' was missing a value.","position":null}],"field":["id"],"full_message":"The following input errors occurred:\nThe required field 'id' was missing a value.","type":null}`, http.StatusOK) + if payload.Params.ID == 0 { + http.Error(rw, `{"error":"","error_class":"LW::Exception::Input::Multiple","errors":[{"error":"","error_class":"LW::Exception::Input::Required","field":"id","full_message":"The required field 'id' was missing a value.","position":null}],"field":["id"],"full_message":"The following input errors occurred:\nThe required field 'id' was missing a value.","type":null}`, http.StatusOK) return } - if _, ok := recs[req.Params.ID]; !ok { - http.Error(w, fmt.Sprintf(`{"error":"","error_class":"LW::Exception::RecordNotFound","field":"network_dns_rr","full_message":"Record 'network_dns_rr: %d' not found","input":"%d","public_message":null}`, req.Params.ID, req.Params.ID), http.StatusOK) + if _, ok := recs[payload.Params.ID]; !ok { + http.Error(rw, fmt.Sprintf(`{"error":"","error_class":"LW::Exception::RecordNotFound","field":"network_dns_rr","full_message":"Record 'network_dns_rr: %d' not found","input":"%d","public_message":null}`, payload.Params.ID, payload.Params.ID), http.StatusOK) return } - delete(recs, req.Params.ID) - http.Error(w, fmt.Sprintf("{\"deleted\":%d}", req.Params.ID), http.StatusOK) + delete(recs, payload.Params.ID) + http.Error(rw, fmt.Sprintf("{\"deleted\":%d}", payload.Params.ID), http.StatusOK) } } func mockAPIListZones() http.HandlerFunc { mockZones, mockAPIServerZones := makeMockZones() - return func(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) + return func(rw http.ResponseWriter, req *http.Request) { + body, err := io.ReadAll(req.Body) if err != nil { - http.Error(w, "invalid request", http.StatusInternalServerError) + http.Error(rw, "invalid request", http.StatusInternalServerError) return } - req := struct { + payload := struct { Params struct { PageNum int `json:"page_num"` } `json:"params"` }{} - if err = json.Unmarshal(body, &req); err != nil { - http.Error(w, makeEncodingError(body), http.StatusBadRequest) + if err = json.Unmarshal(body, &payload); err != nil { + http.Error(rw, makeEncodingError(body), http.StatusBadRequest) return } switch { - case req.Params.PageNum < 1: - req.Params.PageNum = 1 - case req.Params.PageNum > len(mockZones): - req.Params.PageNum = len(mockZones) + case payload.Params.PageNum < 1: + payload.Params.PageNum = 1 + case payload.Params.PageNum > len(mockZones): + payload.Params.PageNum = len(mockZones) } - resp := mockZones[req.Params.PageNum] + resp := mockZones[payload.Params.PageNum] resp.ItemTotal = types.FlexInt(len(mockAPIServerZones)) - resp.PageNum = types.FlexInt(req.Params.PageNum) + resp.PageNum = types.FlexInt(payload.Params.PageNum) resp.PageSize = 5 resp.PageTotal = types.FlexInt(len(mockZones)) var respBody []byte if respBody, err = json.Marshal(resp); err == nil { - http.Error(w, string(respBody), http.StatusOK) + http.Error(rw, string(respBody), http.StatusOK) return } - http.Error(w, "", http.StatusInternalServerError) + http.Error(rw, "", http.StatusInternalServerError) } } diff --git a/providers/dns/loopia/internal/client_test.go b/providers/dns/loopia/internal/client_test.go index a84b7c9ad..63962b06e 100644 --- a/providers/dns/loopia/internal/client_test.go +++ b/providers/dns/loopia/internal/client_test.go @@ -2,61 +2,76 @@ package internal import ( "encoding/xml" - "fmt" - "io" "net/http" "net/http/httptest" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func mockBuilder(password string) *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("apiuser", password) + client.BaseURL = server.URL + "/" + + return client, nil + }, + servermock.CheckHeader().WithContentType("text/xml"), + ) +} + func TestClient_AddZoneRecord(t *testing.T) { - serverResponses := map[string]string{ - addZoneRecordGoodAuth: responseOk, - addZoneRecordBadAuth: responseAuthError, - addZoneRecordNonValidDomain: responseUnknownError, - addZoneRecordEmptyResponse: "", - } - - serverURL := createFakeServer(t, serverResponses) - testCases := []struct { desc string password string domain string + request string + response string err string }{ { desc: "auth ok", password: "goodpassword", domain: exampleDomain, + request: addZoneRecordGoodAuth, + response: responseOk, }, { desc: "auth error", password: "badpassword", domain: exampleDomain, + request: addZoneRecordBadAuth, + response: responseAuthError, err: "authentication error", }, { desc: "unknown error", password: "goodpassword", domain: "badexample.com", + request: addZoneRecordNonValidDomain, + response: responseUnknownError, err: `unknown error: "UNKNOWN_ERROR"`, }, { desc: "empty response", password: "goodpassword", domain: "empty.com", + request: addZoneRecordEmptyResponse, + response: "", err: "unmarshal error: EOF", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client := NewClient("apiuser", test.password) - client.BaseURL = serverURL + "/" + client := mockBuilder(test.password). + Route("POST /", + servermock.RawStringResponse(test.response), + servermock.CheckRequestBody(test.request)). + Build(t) err := client.AddTXTRecord(t.Context(), test.domain, exampleSubDomain, 123, "TXTrecord") if test.err == "" { @@ -70,50 +85,54 @@ func TestClient_AddZoneRecord(t *testing.T) { } func TestClient_RemoveSubdomain(t *testing.T) { - serverResponses := map[string]string{ - removeSubdomainGoodAuth: responseOk, - removeSubdomainBadAuth: responseAuthError, - removeSubdomainNonValidDomain: responseUnknownError, - removeSubdomainEmptyResponse: "", - } - - serverURL := createFakeServer(t, serverResponses) - testCases := []struct { desc string password string domain string + request string + response string err string }{ { desc: "auth ok", password: "goodpassword", domain: exampleDomain, + request: removeSubdomainGoodAuth, + response: responseOk, }, { desc: "auth error", password: "badpassword", domain: exampleDomain, + request: removeSubdomainBadAuth, + response: responseAuthError, err: "authentication error", }, { desc: "unknown error", password: "goodpassword", domain: "badexample.com", + request: removeSubdomainNonValidDomain, + response: responseUnknownError, err: `unknown error: "UNKNOWN_ERROR"`, }, { desc: "empty response", password: "goodpassword", domain: "empty.com", + request: removeSubdomainEmptyResponse, + response: "", err: "unmarshal error: EOF", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client := NewClient("apiuser", test.password) - client.BaseURL = serverURL + "/" + client := mockBuilder(test.password). + Route("POST /", + servermock.RawStringResponse(test.response), + servermock.CheckRequestBody(test.request)). + Build(t) err := client.RemoveSubdomain(t.Context(), test.domain, exampleSubDomain) if test.err == "" { @@ -127,50 +146,54 @@ func TestClient_RemoveSubdomain(t *testing.T) { } func TestClient_RemoveZoneRecord(t *testing.T) { - serverResponses := map[string]string{ - removeRecordGoodAuth: responseOk, - removeRecordBadAuth: responseAuthError, - removeRecordNonValidDomain: responseUnknownError, - removeRecordEmptyResponse: "", - } - - serverURL := createFakeServer(t, serverResponses) - testCases := []struct { desc string password string domain string + request string + response string err string }{ { desc: "auth ok", password: "goodpassword", domain: exampleDomain, + request: removeRecordGoodAuth, + response: responseOk, }, { desc: "auth error", password: "badpassword", domain: exampleDomain, + request: removeRecordBadAuth, + response: responseAuthError, err: "authentication error", }, { desc: "uknown error", password: "goodpassword", domain: "badexample.com", + request: removeRecordNonValidDomain, + response: responseUnknownError, err: `unknown error: "UNKNOWN_ERROR"`, }, { desc: "empty response", password: "goodpassword", domain: "empty.com", + request: removeRecordEmptyResponse, + response: "", err: "unmarshal error: EOF", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client := NewClient("apiuser", test.password) - client.BaseURL = serverURL + "/" + client := mockBuilder(test.password). + Route("POST /", + servermock.RawStringResponse(test.response), + servermock.CheckRequestBody(test.request)). + Build(t) err := client.RemoveTXTRecord(t.Context(), test.domain, exampleSubDomain, 12345678) if test.err == "" { @@ -184,14 +207,11 @@ func TestClient_RemoveZoneRecord(t *testing.T) { } func TestClient_GetZoneRecord(t *testing.T) { - serverResponses := map[string]string{ - getZoneRecords: getZoneRecordsResponse, - } - - serverURL := createFakeServer(t, serverResponses) - - client := NewClient("apiuser", "goodpassword") - client.BaseURL = serverURL + "/" + client := mockBuilder("goodpassword"). + Route("POST /", + servermock.RawStringResponse(getZoneRecordsResponse), + servermock.CheckRequestBody(getZoneRecords)). + Build(t) recordObjs, err := client.GetTXTRecords(t.Context(), exampleDomain, exampleSubDomain) require.NoError(t, err) @@ -209,23 +229,11 @@ func TestClient_GetZoneRecord(t *testing.T) { } func TestClient_rpcCall_404(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - w.WriteHeader(http.StatusNotFound) - - _, err = fmt.Fprint(w, "") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - })) - - t.Cleanup(server.Close) + client := mockBuilder("apipassword"). + Route("POST /", + servermock.RawStringResponse(""). + WithStatusCode(http.StatusNotFound)). + Build(t) call := &methodCall{ MethodName: "dummyMethod", @@ -234,29 +242,15 @@ func TestClient_rpcCall_404(t *testing.T) { }, } - client := NewClient("apiuser", "apipassword") - client.BaseURL = server.URL + "/" - err := client.rpcCall(t.Context(), call, &responseString{}) require.EqualError(t, err, "unexpected status code: [status code: 404] body: ") } func TestClient_rpcCall_RPCError(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - _, err = fmt.Fprint(w, responseRPCError) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - })) - - t.Cleanup(server.Close) + client := mockBuilder("apipassword"). + Route("POST /", + servermock.RawStringResponse(responseRPCError)). + Build(t) call := &methodCall{ MethodName: "getDomains", @@ -265,9 +259,6 @@ func TestClient_rpcCall_RPCError(t *testing.T) { }, } - client := NewClient("apiuser", "apipassword") - client.BaseURL = server.URL + "/" - err := client.rpcCall(t.Context(), call, &responseString{}) require.EqualError(t, err, "RPC Error: (201) Method signature error: 42") } @@ -300,37 +291,3 @@ func TestUnmarshallFaultyRecordObject(t *testing.T) { }) } } - -func createFakeServer(t *testing.T, serverResponses map[string]string) string { - t.Helper() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("Content-Type") != "text/xml" { - http.Error(w, fmt.Sprintf("invalid content type: %s", r.Header.Get("Content-Type")), http.StatusBadRequest) - return - } - - req, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - resp, ok := serverResponses[string(req)] - if !ok { - http.Error(w, "no response for request", http.StatusBadRequest) - return - } - - _, err = fmt.Fprint(w, resp) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - server := httptest.NewServer(handler) - t.Cleanup(server.Close) - - return server.URL -} diff --git a/providers/dns/luadns/internal/client_test.go b/providers/dns/luadns/internal/client_test.go index 1b09814ef..0a3a79e6c 100644 --- a/providers/dns/luadns/internal/client_test.go +++ b/providers/dns/luadns/internal/client_test.go @@ -1,60 +1,32 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" - "os" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, apiToken string) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder(apiToken string) *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("me", apiToken) + client.baseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient("me", apiToken) - client.baseURL, _ = url.Parse(server.URL) - client.HTTPClient = server.Client() - - return client, mux + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithBasicAuth("me", apiToken)) } func TestClient_ListZones(t *testing.T) { - client, mux := setupTest(t, "secretA") - - mux.HandleFunc("/v1/zones", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("invalid method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get("Authorization") - if auth != "Basic bWU6c2VjcmV0QQ==" { - http.Error(rw, fmt.Sprintf("invalid authentication: %s", auth), http.StatusUnauthorized) - return - } - - file, err := os.Open("./fixtures/list_zones.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder("secretA"). + Route("GET /v1/zones", servermock.ResponseFromFixture("list_zones.json")). + Build(t) zones, err := client.ListZones(t.Context()) require.NoError(t, err) @@ -88,33 +60,11 @@ func TestClient_ListZones(t *testing.T) { } func TestClient_CreateRecord(t *testing.T) { - client, mux := setupTest(t, "secretB") - - mux.HandleFunc("/v1/zones/1/records", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("invalid method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get("Authorization") - if auth != "Basic bWU6c2VjcmV0Qg==" { - http.Error(rw, fmt.Sprintf("invalid authentication: %s", auth), http.StatusUnauthorized) - return - } - - file, err := os.Open("./fixtures/create_record.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder("secretB"). + Route("POST /v1/zones/1/records", + servermock.ResponseFromFixture("create_record.json"), + servermock.CheckRequestJSONBody(`{"name":"example.com.","type":"MX","content":"10 mail.example.com.","ttl":300}`)). + Build(t) zone := DNSZone{ID: 1} @@ -141,33 +91,11 @@ func TestClient_CreateRecord(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client, mux := setupTest(t, "secretC") - - mux.HandleFunc("/v1/zones/1/records/2", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, fmt.Sprintf("invalid method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get("Authorization") - if auth != "Basic bWU6c2VjcmV0Qw==" { - http.Error(rw, fmt.Sprintf("invalid authentication: %s", auth), http.StatusUnauthorized) - return - } - - file, err := os.Open("./fixtures/delete_record.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder("secretC"). + Route("DELETE /v1/zones/1/records/2", + servermock.ResponseFromFixture("delete_record.json"), + servermock.CheckRequestJSONBody(`{"id":2,"name":"example.com.","type":"MX","content":"10 mail.example.com.","ttl":300,"zone_id":1}`)). + Build(t) record := &DNSRecord{ ID: 2, diff --git a/providers/dns/manageengine/internal/client_test.go b/providers/dns/manageengine/internal/client_test.go index a47d0b9a8..0c18a245f 100644 --- a/providers/dns/manageengine/internal/client_test.go +++ b/providers/dns/manageengine/internal/client_test.go @@ -1,57 +1,35 @@ package internal import ( - "io" + "context" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, status int, filename string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(context.Background(), "abc", "secret") - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client.httpClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if filename == "" { - rw.WriteHeader(status) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client := NewClient(t.Context(), "abc", "secret") - - client.httpClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader(). + WithAccept("application/json")) } func TestClient_GetAllZones(t *testing.T) { - client := setupTest(t, "GET /dns/domain", http.StatusOK, "zone_domains_all.json") + client := mockBuilder(). + Route("GET /dns/domain", servermock.ResponseFromFixture("zone_domains_all.json")). + Build(t) groups, err := client.GetAllZones(t.Context()) require.NoError(t, err) @@ -132,7 +110,11 @@ func TestClient_GetAllZones(t *testing.T) { } func TestClient_GetAllZones_error(t *testing.T) { - client := setupTest(t, "GET /dns/domain", http.StatusUnauthorized, "error.json") + client := mockBuilder(). + Route("GET /dns/domain", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.GetAllZones(t.Context()) require.Error(t, err) @@ -141,7 +123,9 @@ func TestClient_GetAllZones_error(t *testing.T) { } func TestClient_GetAllZoneRecords(t *testing.T) { - client := setupTest(t, "GET /dns/domain/4/records/SPF_TXT", http.StatusOK, "zone_records_all.json") + client := mockBuilder(). + Route("GET /dns/domain/4/records/SPF_TXT", servermock.ResponseFromFixture("zone_records_all.json")). + Build(t) groups, err := client.GetAllZoneRecords(t.Context(), 4) require.NoError(t, err) @@ -179,7 +163,11 @@ func TestClient_GetAllZoneRecords(t *testing.T) { } func TestClient_GetAllZoneRecords_error(t *testing.T) { - client := setupTest(t, "GET /dns/domain/4/records/SPF_TXT", http.StatusUnauthorized, "error.json") + client := mockBuilder(). + Route("GET /dns/domain/4/records/SPF_TXT", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.GetAllZoneRecords(t.Context(), 4) require.Error(t, err) @@ -188,14 +176,20 @@ func TestClient_GetAllZoneRecords_error(t *testing.T) { } func TestClient_DeleteZoneRecord(t *testing.T) { - client := setupTest(t, "DELETE /dns/domain/4/records/SPF_TXT/6", http.StatusOK, "zone_record_delete.json") + client := mockBuilder(). + Route("DELETE /dns/domain/4/records/SPF_TXT/6", servermock.ResponseFromFixture("zone_record_delete.json")). + Build(t) err := client.DeleteZoneRecord(t.Context(), 4, 6) require.NoError(t, err) } func TestClient_DeleteZoneRecord_error(t *testing.T) { - client := setupTest(t, "DELETE /dns/domain/4/records/SPF_TXT/6", http.StatusUnauthorized, "error.json") + client := mockBuilder(). + Route("DELETE /dns/domain/4/records/SPF_TXT/6", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) err := client.DeleteZoneRecord(t.Context(), 4, 6) require.Error(t, err) @@ -204,16 +198,45 @@ func TestClient_DeleteZoneRecord_error(t *testing.T) { } func TestClient_CreateZoneRecord(t *testing.T) { - client := setupTest(t, "POST /dns/domain/4/records/SPF_TXT/", http.StatusOK, "zone_record_create.json") + client := mockBuilder(). + Route("POST /dns/domain/4/records/SPF_TXT/", + servermock.ResponseFromFixture("zone_record_create.json"), + servermock.CheckHeader(). + WithContentTypeFromURLEncoded(), + servermock.CheckForm().Strict(). + With("config", `[{"zone_id":1,"spf_txt_domain_id":2,"domain_name":"example.com","domain_ttl":120,"domain_location_id":3,"record_type":"TXT","records":[{"record_id":123,"value":["value1"],"domain_id":1}]}] +`)). + Build(t) - record := ZoneRecord{} + record := ZoneRecord{ + ZoneID: 1, + SpfTxtDomainID: 2, + DomainName: "example.com", + DomainTTL: 120, + DomainLocationID: 3, + RecordType: "TXT", + Records: []Record{ + { + ID: 123, + Values: []string{"value1"}, + Disabled: false, + DomainID: 1, + }, + }, + } err := client.CreateZoneRecord(t.Context(), 4, record) require.NoError(t, err) } func TestClient_CreateZoneRecord_error(t *testing.T) { - client := setupTest(t, "POST /dns/domain/4/records/SPF_TXT/", http.StatusUnauthorized, "error.json") + client := mockBuilder(). + Route("POST /dns/domain/4/records/SPF_TXT/", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized), + servermock.CheckHeader(). + WithContentTypeFromURLEncoded()). + Build(t) record := ZoneRecord{} @@ -224,7 +247,13 @@ func TestClient_CreateZoneRecord_error(t *testing.T) { } func TestClient_CreateZoneRecord_error_bad_request(t *testing.T) { - client := setupTest(t, "POST /dns/domain/4/records/SPF_TXT/", http.StatusBadRequest, "error_bad_request.json") + client := mockBuilder(). + Route("POST /dns/domain/4/records/SPF_TXT/", + servermock.ResponseFromFixture("error_bad_request.json"). + WithStatusCode(http.StatusBadRequest), + servermock.CheckHeader(). + WithContentTypeFromURLEncoded()). + Build(t) record := ZoneRecord{} @@ -235,7 +264,15 @@ func TestClient_CreateZoneRecord_error_bad_request(t *testing.T) { } func TestClient_UpdateZoneRecord(t *testing.T) { - client := setupTest(t, "PUT /dns/domain/4/records/SPF_TXT/6/", http.StatusOK, "zone_record_update.json") + client := mockBuilder(). + Route("PUT /dns/domain/4/records/SPF_TXT/6/", + servermock.ResponseFromFixture("zone_record_update.json"), + servermock.CheckHeader(). + WithContentTypeFromURLEncoded(), + servermock.CheckForm().Strict(). + With("config", `[{"zone_id":4,"spf_txt_domain_id":6,"records":null}] +`)). + Build(t) record := ZoneRecord{ SpfTxtDomainID: 6, @@ -247,7 +284,13 @@ func TestClient_UpdateZoneRecord(t *testing.T) { } func TestClient_UpdateZoneRecord_error(t *testing.T) { - client := setupTest(t, "PUT /dns/domain/4/records/SPF_TXT/6/", http.StatusUnauthorized, "error.json") + client := mockBuilder(). + Route("PUT /dns/domain/4/records/SPF_TXT/6/", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized), + servermock.CheckHeader(). + WithContentTypeFromURLEncoded()). + Build(t) record := ZoneRecord{ SpfTxtDomainID: 6, diff --git a/providers/dns/metaregistrar/internal/client.go b/providers/dns/metaregistrar/internal/client.go index f2838c532..4d3c8adc4 100644 --- a/providers/dns/metaregistrar/internal/client.go +++ b/providers/dns/metaregistrar/internal/client.go @@ -16,6 +16,8 @@ import ( const defaultBaseURL = "https://api.metaregistrar.com" +const tokenHeader = "token" + // Client is a client to interact with the Metaregistrar API. type Client struct { token string @@ -61,7 +63,7 @@ func (c Client) UpdateDNSZone(ctx context.Context, domain string, updateRequest } func (c Client) do(req *http.Request, result any) error { - req.Header.Add("token", c.token) + req.Header.Add(tokenHeader, c.token) resp, err := c.HTTPClient.Do(req) if err != nil { diff --git a/providers/dns/metaregistrar/internal/client_test.go b/providers/dns/metaregistrar/internal/client_test.go index 8486fc899..33e92cd7b 100644 --- a/providers/dns/metaregistrar/internal/client_test.go +++ b/providers/dns/metaregistrar/internal/client_test.go @@ -1,58 +1,39 @@ package internal import ( - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, status int, filename string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("secret") + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if filename == "" { - rw.WriteHeader(status) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client, err := NewClient("token") - require.NoError(t, err) - - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + With(tokenHeader, "secret")) } func TestClient_UpdateDNSZone(t *testing.T) { - client := setupTest(t, "PATCH /dnszone/example.com", http.StatusOK, "update-dns-zone.json") + client := mockBuilder(). + Route("PATCH /dnszone/example.com", + servermock.ResponseFromFixture("update-dns-zone.json"), + servermock.CheckRequestJSONBody(`{"add":[{"name":"@","type":"TXT","ttl":60,"content":"value"}]}`)). + Build(t) updateRequest := DNSZoneUpdateRequest{ Add: []Record{{ @@ -95,7 +76,11 @@ func TestClient_UpdateDNSZone_error(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client := setupTest(t, "PATCH /dnszone/example.com", http.StatusUnprocessableEntity, test.filename) + client := mockBuilder(). + Route("PATCH /dnszone/example.com", + servermock.ResponseFromFixture(test.filename). + WithStatusCode(http.StatusUnprocessableEntity)). + Build(t) updateRequest := DNSZoneUpdateRequest{ Add: []Record{{ diff --git a/providers/dns/mijnhost/internal/client_test.go b/providers/dns/mijnhost/internal/client_test.go index a1dc326b7..208616541 100644 --- a/providers/dns/mijnhost/internal/client_test.go +++ b/providers/dns/mijnhost/internal/client_test.go @@ -1,69 +1,35 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const apiKey = "secret" -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(apiKey) + client.baseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient(apiKey) - client.baseURL, _ = url.Parse(server.URL) - - return client, mux -} - -func testHandler(filename, method string, statusCode int) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get(authorizationHeader) - if auth != apiKey { - http.Error(rw, "invalid Authorization header", http.StatusUnauthorized) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(statusCode) - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + With(authorizationHeader, apiKey), + ) } func TestClient_ListDomains(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains", testHandler("./list-domains.json", http.MethodGet, http.StatusOK)) + client := mockBuilder(). + Route("GET /domains", servermock.ResponseFromFixture("list-domains.json")). + Build(t) domains, err := client.ListDomains(t.Context()) require.NoError(t, err) @@ -81,9 +47,9 @@ func TestClient_ListDomains(t *testing.T) { } func TestClient_GetRecords(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/domains/example.com/dns", testHandler("./get-dns-records.json", http.MethodGet, http.StatusOK)) + client := mockBuilder(). + Route("GET /domains/example.com/dns", servermock.ResponseFromFixture("get-dns-records.json")). + Build(t) records, err := client.GetRecords(t.Context(), "example.com") require.NoError(t, err) @@ -119,10 +85,19 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_UpdateRecords(t *testing.T) { - client, mux := setupTest(t) + client := mockBuilder(). + Route("PUT /domains/example.com/dns", + servermock.ResponseFromFixture("update-dns-records.json"), + servermock.CheckRequestJSONBody(`{"records":[{"type":"TXT","name":"foo","value":"value1","ttl":120}]}`)). + Build(t) - mux.HandleFunc("/domains/example.com/dns", testHandler("./update-dns-records.json", http.MethodPut, http.StatusOK)) + records := []Record{{ + Type: "TXT", + Name: "foo", + Value: "value1", + TTL: 120, + }} - err := client.UpdateRecords(t.Context(), "example.com", nil) + err := client.UpdateRecords(t.Context(), "example.com", records) require.NoError(t, err) } diff --git a/providers/dns/mittwald/internal/client_test.go b/providers/dns/mittwald/internal/client_test.go index f73a36cc1..e57c80f7a 100644 --- a/providers/dns/mittwald/internal/client_test.go +++ b/providers/dns/mittwald/internal/client_test.go @@ -1,72 +1,34 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, handler http.HandlerFunc) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("secret") + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(pattern, handler) - - client := NewClient("secret") - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client -} - -func testHandler(method string, statusCode int, filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf(`{"message":"unsupported method: %s"}`, req.Method), http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get(authorizationHeader) - if auth != "Bearer secret" { - http.Error(rw, fmt.Sprintf("invalid API Token: %s", auth), http.StatusUnauthorized) - return - } - - rw.WriteHeader(statusCode) - - if statusCode == http.StatusNoContent { - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, fmt.Sprintf(`{"message":"%v"}`, err), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, fmt.Sprintf(`{"message":"%v"}`, err), http.StatusInternalServerError) - return - } - } + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Bearer secret"), + ) } func TestClient_ListDomains(t *testing.T) { - client := setupTest(t, "/domains", testHandler(http.MethodGet, http.StatusOK, "domain-list-domains.json")) + client := mockBuilder(). + Route("GET /domains", servermock.ResponseFromFixture("domain-list-domains.json")). + Build(t) domains, err := client.ListDomains(t.Context()) require.NoError(t, err) @@ -83,14 +45,20 @@ func TestClient_ListDomains(t *testing.T) { } func TestClient_ListDomains_error(t *testing.T) { - client := setupTest(t, "/domains", testHandler(http.MethodGet, http.StatusBadRequest, "error-client.json")) + client := mockBuilder(). + Route("GET /domains", + servermock.ResponseFromFixture("error-client.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) _, err := client.ListDomains(t.Context()) require.EqualError(t, err, "[status code 400] ValidationError: Validation failed [format: should be string (.address.street, email)]") } func TestClient_ListDNSZones(t *testing.T) { - client := setupTest(t, "/projects/my-project-id/dns-zones", testHandler(http.MethodGet, http.StatusOK, "dns-list-dns-zones.json")) + client := mockBuilder(). + Route("GET /projects/my-project-id/dns-zones", servermock.ResponseFromFixture("dns-list-dns-zones.json")). + Build(t) zones, err := client.ListDNSZones(t.Context(), "my-project-id") require.NoError(t, err) @@ -109,7 +77,9 @@ func TestClient_ListDNSZones(t *testing.T) { } func TestClient_GetDNSZone(t *testing.T) { - client := setupTest(t, "/dns-zones/my-zone-id", testHandler(http.MethodGet, http.StatusOK, "dns-get-dns-zone.json")) + client := mockBuilder(). + Route("GET /dns-zones/my-zone-id", servermock.ResponseFromFixture("dns-get-dns-zone.json")). + Build(t) zone, err := client.GetDNSZone(t.Context(), "my-zone-id") require.NoError(t, err) @@ -126,7 +96,11 @@ func TestClient_GetDNSZone(t *testing.T) { } func TestClient_CreateDNSZone(t *testing.T) { - client := setupTest(t, "/dns-zones", testHandler(http.MethodPost, http.StatusCreated, "dns-create-dns-zone.json")) + client := mockBuilder(). + Route("POST /dns-zones", + servermock.ResponseFromFixture("dns-create-dns-zone.json"), + servermock.CheckRequestJSONBody(`{"name":"test","parentZoneId":"my-parent-zone-id"}`)). + Build(t) request := CreateDNSZoneRequest{ Name: "test", @@ -144,7 +118,12 @@ func TestClient_CreateDNSZone(t *testing.T) { } func TestClient_UpdateTXTRecord(t *testing.T) { - client := setupTest(t, "/dns-zones/my-zone-id/record-sets/txt", testHandler(http.MethodPut, http.StatusNoContent, "")) + client := mockBuilder(). + Route("PUT /dns-zones/my-zone-id/record-sets/txt", + servermock.Noop(). + WithStatusCode(http.StatusNoContent), + servermock.CheckRequestJSONBody(`{"settings":{"ttl":{"auto":true}},"entries":["txt"]}`)). + Build(t) record := TXTRecord{ Settings: Settings{ @@ -158,14 +137,21 @@ func TestClient_UpdateTXTRecord(t *testing.T) { } func TestClient_DeleteDNSZone(t *testing.T) { - client := setupTest(t, "/dns-zones/my-zone-id", testHandler(http.MethodDelete, http.StatusOK, "")) + client := mockBuilder(). + Route("DELETE /dns-zones/my-zone-id", + servermock.Noop()). + Build(t) err := client.DeleteDNSZone(t.Context(), "my-zone-id") require.NoError(t, err) } func TestClient_DeleteDNSZone_error(t *testing.T) { - client := setupTest(t, "/dns-zones/my-zone-id", testHandler(http.MethodDelete, http.StatusInternalServerError, "error.json")) + client := mockBuilder(). + Route("DELETE /dns-zones/my-zone-id", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusInternalServerError)). + Build(t) err := client.DeleteDNSZone(t.Context(), "my-zone-id") assert.EqualError(t, err, "[status code 500] InternalServerError: Something went wrong") diff --git a/providers/dns/myaddr/internal/client_test.go b/providers/dns/myaddr/internal/client_test.go index 794a501fb..36506d94a 100644 --- a/providers/dns/myaddr/internal/client_test.go +++ b/providers/dns/myaddr/internal/client_test.go @@ -1,75 +1,61 @@ package internal import ( - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, status int, filename string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + credentials := map[string]string{ + "example": "secret", + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client, err := NewClient(credentials) + if err != nil { + return nil, err + } - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if filename == "" { - rw.WriteHeader(status) - return - } + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - credentials := map[string]string{ - "example": "secret", - } - - client, err := NewClient(credentials) - require.NoError(t, err) - - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(), + ) } func TestClient_AddTXTRecord(t *testing.T) { - client := setupTest(t, "POST /update", http.StatusOK, "") + client := mockBuilder(). + Route("POST /update", nil, + servermock.CheckRequestJSONBody(`{"key":"secret","acme_challenge":"txt"}`)). + Build(t) err := client.AddTXTRecord(t.Context(), "example", "txt") require.NoError(t, err) } func TestClient_AddTXTRecord_error(t *testing.T) { - client := setupTest(t, "POST /update", http.StatusBadRequest, "error.txt") + client := mockBuilder(). + Route("POST /update", + servermock.ResponseFromFixture("error.txt"). + WithStatusCode(http.StatusBadRequest)). + Build(t) err := client.AddTXTRecord(t.Context(), "example", "txt") require.EqualError(t, err, `unexpected status code: [status code: 400] body: invalid value for "key"`) } func TestClient_AddTXTRecord_error_credentials(t *testing.T) { - client := setupTest(t, "POST /update", http.StatusOK, "") + client := mockBuilder(). + Route("POST /update", nil). + Build(t) err := client.AddTXTRecord(t.Context(), "nx", "txt") require.EqualError(t, err, "subdomain nx not found in credentials, check your credentials map") diff --git a/providers/dns/mydnsjp/internal/client_test.go b/providers/dns/mydnsjp/internal/client_test.go index a0f9ab8c7..41ccbba87 100644 --- a/providers/dns/mydnsjp/internal/client_test.go +++ b/providers/dns/mydnsjp/internal/client_test.go @@ -1,90 +1,49 @@ package internal import ( - "fmt" - "net/http" "net/http/httptest" "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, cmdName string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("xxx", "secret") + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("invalid method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - username, password, ok := req.BasicAuth() - if !ok { - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - if username != "xxx" { - http.Error(rw, fmt.Sprintf("username: want %s got %s", username, "xxx"), http.StatusUnauthorized) - return - } - - if password != "secret" { - http.Error(rw, fmt.Sprintf("password: want %s got %s", password, "secret"), http.StatusUnauthorized) - return - } - - if req.Header.Get("Content-Type") != "application/x-www-form-urlencoded" { - http.Error(rw, fmt.Sprintf("invalid Content-Type: %s", req.Header.Get("Content-Type")), http.StatusBadRequest) - return - } - - err := req.ParseForm() - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - domain := req.Form.Get("CERTBOT_DOMAIN") - if domain != "example.com" { - http.Error(rw, fmt.Sprintf("unexpected CERTBOT_DOMAIN: %s", domain), http.StatusBadRequest) - return - } - - validation := req.Form.Get("CERTBOT_VALIDATION") - if validation != "txt" { - http.Error(rw, fmt.Sprintf("unexpected CERTBOT_VALIDATION: %s", validation), http.StatusBadRequest) - return - } - - cmd := req.Form.Get("EDIT_CMD") - if cmd != cmdName { - http.Error(rw, fmt.Sprintf("unexpected EDIT_CMD: %s", cmd), http.StatusBadRequest) - return - } - }) - - client := NewClient("xxx", "secret") - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded(). + WithBasicAuth("xxx", "secret")) } func TestClient_AddTXTRecord(t *testing.T) { - client := setupTest(t, "REGIST") + client := mockBuilder(). + Route("POST /", nil, + servermock.CheckForm().Strict(). + With("CERTBOT_DOMAIN", "example.com"). + With("CERTBOT_VALIDATION", "txt"). + With("EDIT_CMD", "REGIST")). + Build(t) err := client.AddTXTRecord(t.Context(), "example.com", "txt") require.NoError(t, err) } func TestClient_DeleteTXTRecord(t *testing.T) { - client := setupTest(t, "DELETE") + client := mockBuilder(). + Route("POST /", nil, + servermock.CheckForm().Strict(). + With("CERTBOT_DOMAIN", "example.com"). + With("CERTBOT_VALIDATION", "txt"). + With("EDIT_CMD", "DELETE")). + Build(t) err := client.DeleteTXTRecord(t.Context(), "example.com", "txt") require.NoError(t, err) diff --git a/providers/dns/mythicbeasts/internal/client_test.go b/providers/dns/mythicbeasts/internal/client_test.go index 1e5f83e3c..acbf85268 100644 --- a/providers/dns/mythicbeasts/internal/client_test.go +++ b/providers/dns/mythicbeasts/internal/client_test.go @@ -1,68 +1,53 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" "time" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, handler http.HandlerFunc) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret") + client.HTTPClient = server.Client() + client.APIEndpoint, _ = url.Parse(server.URL) + client.token = &Token{ + Token: "secret", + Lifetime: 60, + TokenType: "bearer", + Deadline: time.Now().Add(1 * time.Minute), + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(pattern, handler) - - client := NewClient("user", "secret") - client.HTTPClient = server.Client() - client.APIEndpoint, _ = url.Parse(server.URL) - client.token = &Token{ - Token: "secret", - Lifetime: 60, - TokenType: "bearer", - Deadline: time.Now().Add(1 * time.Minute), - } - - return client -} - -func writeFixtureHandler(method, filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, _ = io.Copy(rw, file) - } + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Bearer "+fakeToken), + ) } func TestClient_CreateTXTRecord(t *testing.T) { - client := setupTest(t, "/zones/example.com/records/foo/TXT", writeFixtureHandler(http.MethodPost, "post-zoneszonerecords.json")) + client := mockBuilder(). + Route("POST /zones/example.com/records/foo/TXT", + servermock.ResponseFromFixture("post-zoneszonerecords.json"), + servermock.CheckRequestJSONBody(`{"records":[{"host":"foo","ttl":120,"type":"TXT","data":"txt"}]}`)). + Build(t) err := client.CreateTXTRecord(mockContext(t), "example.com", "foo", "txt", 120) require.NoError(t, err) } func TestClient_RemoveTXTRecord(t *testing.T) { - client := setupTest(t, "/zones/example.com/records/foo/TXT", writeFixtureHandler(http.MethodDelete, "delete-zoneszonerecords.json")) + client := mockBuilder(). + Route("DELETE /zones/example.com/records/foo/TXT", + servermock.ResponseFromFixture("delete-zoneszonerecords.json"), + servermock.CheckQueryParameter().Strict(). + With("data", "txt")). + Build(t) err := client.RemoveTXTRecord(mockContext(t), "example.com", "foo", "txt") require.NoError(t, err) diff --git a/providers/dns/mythicbeasts/internal/fixtures/token.json b/providers/dns/mythicbeasts/internal/fixtures/token.json new file mode 100644 index 000000000..f23fe58ea --- /dev/null +++ b/providers/dns/mythicbeasts/internal/fixtures/token.json @@ -0,0 +1,5 @@ +{ + "access_token": "xxx", + "expires_in": 666, + "token_type": "bearer" +} diff --git a/providers/dns/mythicbeasts/internal/identity_test.go b/providers/dns/mythicbeasts/internal/identity_test.go index e26bad6aa..3e1e8ba4f 100644 --- a/providers/dns/mythicbeasts/internal/identity_test.go +++ b/providers/dns/mythicbeasts/internal/identity_test.go @@ -2,52 +2,45 @@ package internal import ( "context" - "encoding/json" - "fmt" - "net/http" "net/http/httptest" "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +const fakeToken = "xxx" + func mockContext(t *testing.T) context.Context { t.Helper() - return context.WithValue(t.Context(), tokenKey, &Token{Token: "xxx"}) + return context.WithValue(t.Context(), tokenKey, &Token{Token: fakeToken}) } -func tokenHandler(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("invalid method, got %s want %s", req.Method, http.MethodPost), http.StatusMethodNotAllowed) - return - } +func mockBuilderIdentity() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret") + client.HTTPClient = server.Client() + client.AuthEndpoint, _ = url.Parse(server.URL) - username, password, ok := req.BasicAuth() - if !ok || username != "user" || password != "secret" { - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - _ = json.NewEncoder(rw).Encode(Token{ - Token: "xxx", - Lifetime: 666, - TokenType: "bearer", - }) + return client, nil + }, + servermock.CheckHeader(). + WithBasicAuth("user", "secret"), + servermock.CheckHeader(). + WithContentTypeFromURLEncoded()) } func TestClient_obtainToken(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/", tokenHandler) - - client := NewClient("user", "secret") - client.HTTPClient = server.Client() - client.AuthEndpoint, _ = url.Parse(server.URL) + client := mockBuilderIdentity(). + Route("POST /", + servermock.ResponseFromFixture("token.json"), + servermock.CheckForm().Strict(). + With("grant_type", "client_credentials")). + Build(t) assert.Nil(t, client.token) @@ -56,19 +49,16 @@ func TestClient_obtainToken(t *testing.T) { assert.NotNil(t, tok) assert.NotZero(t, tok.Deadline) - assert.Equal(t, "xxx", tok.Token) + assert.Equal(t, fakeToken, tok.Token) } func TestClient_CreateAuthenticatedContext(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/", tokenHandler) - - client := NewClient("user", "secret") - client.HTTPClient = server.Client() - client.AuthEndpoint, _ = url.Parse(server.URL) + client := mockBuilderIdentity(). + Route("POST /", + servermock.ResponseFromFixture("token.json"), + servermock.CheckForm().Strict(). + With("grant_type", "client_credentials")). + Build(t) assert.Nil(t, client.token) @@ -79,5 +69,5 @@ func TestClient_CreateAuthenticatedContext(t *testing.T) { assert.NotNil(t, tok) assert.NotZero(t, tok.Deadline) - assert.Equal(t, "xxx", tok.Token) + assert.Equal(t, fakeToken, tok.Token) } diff --git a/providers/dns/namecheap/internal/client_test.go b/providers/dns/namecheap/internal/client_test.go index 6a6ba201a..d7bea7b6e 100644 --- a/providers/dns/namecheap/internal/client_test.go +++ b/providers/dns/namecheap/internal/client_test.go @@ -1,72 +1,36 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, handler http.HandlerFunc) *Client { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/", handler) - +func setupClient(server *httptest.Server) (*Client, error) { client := NewClient("user", "secret", "127.0.0.1") client.HTTPClient = server.Client() client.BaseURL = server.URL - return client -} - -func writeFixture(rw http.ResponseWriter, filename string) { - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, _ = io.Copy(rw, file) + return client, nil } func TestClient_GetHosts(t *testing.T) { - client := setupTest(t, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - expectedParams := map[string]string{ - "ApiKey": "secret", - "ApiUser": "user", - "ClientIp": "127.0.0.1", - "Command": "namecheap.domains.dns.getHosts", - "SLD": "foo", - "TLD": "example.com", - "UserName": "user", - } - - query := req.URL.Query() - for k, v := range expectedParams { - if query.Get(k) != v { - http.Error(rw, fmt.Sprintf("invalid query parameter %s value: %s", k, query.Get(k)), http.StatusBadRequest) - return - } - } - - writeFixture(rw, "getHosts.xml") - }) + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /", + servermock.ResponseFromFixture("getHosts.xml"), + servermock.CheckQueryParameter().Strict(). + With("ApiKey", "secret"). + With("ApiUser", "user"). + With("ClientIp", "127.0.0.1"). + With("Command", "namecheap.domains.dns.getHosts"). + With("SLD", "foo"). + With("TLD", "example.com"). + With("UserName", "user"), + ). + Build(t) hosts, err := client.GetHosts(t.Context(), "foo", "example.com") require.NoError(t, err) @@ -80,68 +44,41 @@ func TestClient_GetHosts(t *testing.T) { } func TestClient_GetHosts_error(t *testing.T) { - client := setupTest(t, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - writeFixture(rw, "getHosts_errorBadAPIKey1.xml") - }) + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /", + servermock.ResponseFromFixture("getHosts_errorBadAPIKey1.xml")). + Build(t) _, err := client.GetHosts(t.Context(), "foo", "example.com") require.ErrorAs(t, err, &apiError{}) } func TestClient_SetHosts(t *testing.T) { - client := setupTest(t, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - if req.Header.Get("Content-Type") != "application/x-www-form-urlencoded" { - http.Error(rw, fmt.Sprintf("invalid Content-Type: %s", req.Header.Get("Content-Type")), http.StatusBadRequest) - return - } - - err := req.ParseForm() - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - expectedParams := map[string]string{ - "HostName1": "_acme-challenge.test.example.com", - "RecordType1": "TXT", - "Address1": "txtTXTtxt", - "MXPref1": "10", - "TTL1": "120", - - "HostName2": "_acme-challenge.test.example.org", - "RecordType2": "TXT", - "Address2": "txtTXTtxt", - "MXPref2": "10", - "TTL2": "120", - - "ApiKey": "secret", - "ApiUser": "user", - "ClientIp": "127.0.0.1", - "Command": "namecheap.domains.dns.setHosts", - "SLD": "foo", - "TLD": "example.com", - "UserName": "user", - } - - for k, v := range expectedParams { - if req.Form.Get(k) != v { - http.Error(rw, fmt.Sprintf("invalid form data %s value: %q", k, req.Form.Get(k)), http.StatusBadRequest) - return - } - } - - writeFixture(rw, "setHosts.xml") - }) + client := servermock.NewBuilder[*Client](setupClient, servermock.CheckHeader().WithContentTypeFromURLEncoded()). + Route("POST /", + servermock.ResponseFromFixture("setHosts.xml"), + servermock.CheckForm().Strict(). + With("ApiKey", "secret"). + With("ApiUser", "user"). + With("ClientIp", "127.0.0.1"). + With("Command", "namecheap.domains.dns.setHosts"). + With("SLD", "foo"). + With("TLD", "example.com"). + With("UserName", "user"). + // entry 1 + With("HostName1", "_acme-challenge.test.example.com"). + With("RecordType1", "TXT"). + With("Address1", "txtTXTtxt"). + With("MXPref1", "10"). + With("TTL1", "120"). + // entry 2 + With("HostName2", "_acme-challenge.test.example.org"). + With("RecordType2", "TXT"). + With("Address2", "txtTXTtxt"). + With("MXPref2", "10"). + With("TTL2", "120"), + ). + Build(t) records := []Record{ {Name: "_acme-challenge.test.example.com", Type: "TXT", Address: "txtTXTtxt", MXPref: "10", TTL: "120"}, @@ -153,14 +90,10 @@ func TestClient_SetHosts(t *testing.T) { } func TestClient_SetHosts_error(t *testing.T) { - client := setupTest(t, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - writeFixture(rw, "setHosts_errorBadAPIKey1.xml") - }) + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /", + servermock.ResponseFromFixture("setHosts_errorBadAPIKey1.xml")). + Build(t) records := []Record{ {Name: "_acme-challenge.test.example.com", Type: "TXT", Address: "txtTXTtxt", MXPref: "10", TTL: "120"}, diff --git a/providers/dns/namecheap/namecheap_test.go b/providers/dns/namecheap/namecheap_test.go index 01f87aaf0..fedbc3162 100644 --- a/providers/dns/namecheap/namecheap_test.go +++ b/providers/dns/namecheap/namecheap_test.go @@ -1,16 +1,13 @@ package namecheap import ( - "io" "net/http" "net/http/httptest" - "net/url" - "os" "path/filepath" "testing" "time" - "github.com/go-acme/lego/v4/providers/dns/namecheap/internal" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -24,7 +21,6 @@ const ( type testCase struct { name string domain string - hosts []internal.Record errString string getHostsResponse string setHostsResponse string @@ -32,26 +28,14 @@ type testCase struct { var testCases = []testCase{ { - name: "Test:Success:1", - domain: "test.example.com", - hosts: []internal.Record{ - {Type: "A", Name: "home", Address: "10.0.0.1", MXPref: "10", TTL: "1799"}, - {Type: "A", Name: "www", Address: "10.0.0.2", MXPref: "10", TTL: "1200"}, - {Type: "AAAA", Name: "a", Address: "::0", MXPref: "10", TTL: "1799"}, - {Type: "CNAME", Name: "*", Address: "example.com.", MXPref: "10", TTL: "1799"}, - {Type: "MXE", Name: "example.com", Address: "10.0.0.5", MXPref: "10", TTL: "1800"}, - {Type: "URL", Name: "xyz", Address: "https://google.com", MXPref: "10", TTL: "1799"}, - }, + name: "Test:Success:1", + domain: "test.example.com", getHostsResponse: "getHosts_success1.xml", setHostsResponse: "setHosts_success1.xml", }, { - name: "Test:Success:2", - domain: "example.com", - hosts: []internal.Record{ - {Type: "A", Name: "@", Address: "10.0.0.2", MXPref: "10", TTL: "1200"}, - {Type: "A", Name: "www", Address: "10.0.0.3", MXPref: "10", TTL: "60"}, - }, + name: "Test:Success:2", + domain: "example.com", getHostsResponse: "getHosts_success2.xml", setHostsResponse: "setHosts_success2.xml", }, @@ -63,96 +47,37 @@ var testCases = []testCase{ }, } -func setupTest(t *testing.T, tc *testCase) *DNSProvider { - t.Helper() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - values := r.URL.Query() - cmd := values.Get("Command") - switch cmd { - case "namecheap.domains.dns.getHosts": - assertHdr(t, tc, &values) - w.WriteHeader(http.StatusOK) - writeFixture(w, tc.getHostsResponse) - default: - t.Errorf("Unexpected GET command: %s", cmd) - } - - case http.MethodPost: - err := r.ParseForm() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - values := r.Form - cmd := values.Get("Command") - switch cmd { - case "namecheap.domains.dns.setHosts": - assertHdr(t, tc, &values) - w.WriteHeader(http.StatusOK) - writeFixture(w, tc.setHostsResponse) - default: - t.Errorf("Unexpected POST command: %s", cmd) - } - - default: - t.Errorf("Unexpected http method: %s", r.Method) - } - }) - - server := httptest.NewServer(handler) - t.Cleanup(server.Close) - - return mockDNSProvider(t, server.URL) -} - -func mockDNSProvider(t *testing.T, baseURL string) *DNSProvider { - t.Helper() - - config := NewDefaultConfig() - config.BaseURL = baseURL - config.APIUser = envTestUser - config.APIKey = envTestKey - config.ClientIP = envTestClientIP - config.HTTPClient = &http.Client{Timeout: 60 * time.Second} - - provider, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - return provider -} - -func assertHdr(t *testing.T, tc *testCase, values *url.Values) { - t.Helper() - - ch, _ := newPseudoRecord(tc.domain, "") - assert.Equal(t, envTestUser, values.Get("ApiUser"), "ApiUser") - assert.Equal(t, envTestKey, values.Get("ApiKey"), "ApiKey") - assert.Equal(t, envTestUser, values.Get("UserName"), "UserName") - assert.Equal(t, envTestClientIP, values.Get("ClientIp"), "ClientIp") - assert.Equal(t, ch.sld, values.Get("SLD"), "SLD") - assert.Equal(t, ch.tld, values.Get("TLD"), "TLD") -} - -func writeFixture(rw http.ResponseWriter, filename string) { - file, err := os.Open(filepath.Join("internal", "fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, _ = io.Copy(rw, file) -} - func TestDNSProvider_Present(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - p := setupTest(t, &test) + ch, _ := newPseudoRecord(test.domain, "") - err := p.Present(test.domain, "", "dummyKey") + provider := mockBuilder(). + Route("GET /", + servermock.ResponseFromFile(filepath.Join("internal", "fixtures", test.getHostsResponse)), + servermock.CheckForm().Strict(). + With("ClientIp", "10.0.0.1"). + With("Command", "namecheap.domains.dns.getHosts"). + With("SLD", ch.sld). + With("TLD", ch.tld). + With("UserName", "foo"). + With("ApiKey", "bar"). + With("ApiUser", "foo"), + ). + Route("POST /", + servermock.ResponseFromFile(filepath.Join("internal", "fixtures", test.setHostsResponse)), + servermock.CheckForm(). + With("ClientIp", "10.0.0.1"). + With("Command", "namecheap.domains.dns.setHosts"). + With("SLD", ch.sld). + With("TLD", ch.tld). + With("UserName", "foo"). + With("ApiKey", "bar"). + With("ApiUser", "foo"), + ). + Build(t) + + err := provider.Present(test.domain, "", "dummyKey") if test.errString != "" { assert.EqualError(t, err, "namecheap: "+test.errString) } else { @@ -165,9 +90,34 @@ func TestDNSProvider_Present(t *testing.T) { func TestDNSProvider_CleanUp(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - p := setupTest(t, &test) + ch, _ := newPseudoRecord(test.domain, "") - err := p.CleanUp(test.domain, "", "dummyKey") + provider := mockBuilder(). + Route("GET /", + servermock.ResponseFromFile(filepath.Join("internal", "fixtures", test.getHostsResponse)), + servermock.CheckForm().Strict(). + With("ClientIp", "10.0.0.1"). + With("Command", "namecheap.domains.dns.getHosts"). + With("SLD", ch.sld). + With("TLD", ch.tld). + With("UserName", "foo"). + With("ApiKey", "bar"). + With("ApiUser", "foo"), + ). + Route("POST /", + servermock.ResponseFromFile(filepath.Join("internal", "fixtures", test.setHostsResponse)), + servermock.CheckForm(). + With("ClientIp", "10.0.0.1"). + With("Command", "namecheap.domains.dns.setHosts"). + With("SLD", ch.sld). + With("TLD", ch.tld). + With("UserName", "foo"). + With("ApiKey", "bar"). + With("ApiUser", "foo"), + ). + Build(t) + + err := provider.CleanUp(test.domain, "", "dummyKey") if test.errString != "" { assert.EqualError(t, err, "namecheap: "+test.errString) } else { @@ -226,3 +176,16 @@ func Test_newPseudoRecord_domainSplit(t *testing.T) { }) } } + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.BaseURL = server.URL + config.APIUser = envTestUser + config.APIKey = envTestKey + config.ClientIP = envTestClientIP + config.HTTPClient = &http.Client{Timeout: 60 * time.Second} + + return NewDNSProviderConfig(config) + }) +} diff --git a/providers/dns/nearlyfreespeech/internal/client_test.go b/providers/dns/nearlyfreespeech/internal/client_test.go index 9c0329978..1445286c3 100644 --- a/providers/dns/nearlyfreespeech/internal/client_test.go +++ b/providers/dns/nearlyfreespeech/internal/client_test.go @@ -1,26 +1,18 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" "testing" "time" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - +func setupClient(server *httptest.Server) (*Client, error) { client := NewClient("user", "secret") client.HTTPClient = server.Client() client.baseURL, _ = url.Parse(server.URL) @@ -28,66 +20,22 @@ func setupTest(t *testing.T) (*Client, *http.ServeMux) { client.signer.saltShaker = func() []byte { return []byte("0123456789ABCDEF") } client.signer.clock = func() time.Time { return time.Unix(1692475113, 0) } - return client, mux -} - -func testHandler(params map[string]string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - if req.Header.Get(authenticationHeader) == "" { - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - err := req.ParseForm() - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - for k, v := range params { - if req.PostForm.Get(k) != v { - http.Error(rw, fmt.Sprintf("data: got %s want %s", k, v), http.StatusBadRequest) - return - } - } - } -} - -func testErrorHandler() http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - file, err := os.Open("./fixtures/error.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - rw.WriteHeader(http.StatusUnauthorized) - - _, _ = io.Copy(rw, file) - } + return client, nil } func TestClient_AddRecord(t *testing.T) { - client, mux := setupTest(t) - - params := map[string]string{ - "data": "txtTXTtxt", - "name": "sub", - "type": "TXT", - "ttl": "30", - } - - mux.Handle("/dns/example.com/addRR", testHandler(params)) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded(). + With(authenticationHeader, "user;1692475113;0123456789ABCDEF;24a32faf74c7bd0525f560ff12a1c1fb6545bafc"), + ). + Route("POST /dns/example.com/addRR", nil, servermock.CheckForm().Strict(). + With("data", "txtTXTtxt"). + With("name", "sub"). + With("type", "TXT"). + With("ttl", "30"), + ). + Build(t) record := Record{ Name: "sub", @@ -101,9 +49,15 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.Handle("/dns/example.com/addRR", testErrorHandler()) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded(). + With(authenticationHeader, "user;1692475113;0123456789ABCDEF;24a32faf74c7bd0525f560ff12a1c1fb6545bafc"), + ). + Route("POST /dns/example.com/addRR", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) record := Record{ Name: "sub", @@ -117,15 +71,18 @@ func TestClient_AddRecord_error(t *testing.T) { } func TestClient_RemoveRecord(t *testing.T) { - client, mux := setupTest(t) - - params := map[string]string{ - "data": "txtTXTtxt", - "name": "sub", - "type": "TXT", - } - - mux.Handle("/dns/example.com/removeRR", testHandler(params)) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded(). + With(authenticationHeader, "user;1692475113;0123456789ABCDEF;699f01f077ca487bd66ac370d6dfc5b122c65522"), + ). + Route("POST /dns/example.com/removeRR", nil, + servermock.CheckForm().Strict(). + With("data", "txtTXTtxt"). + With("name", "sub"). + With("type", "TXT"), + ). + Build(t) record := Record{ Name: "sub", @@ -138,9 +95,15 @@ func TestClient_RemoveRecord(t *testing.T) { } func TestClient_RemoveRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.Handle("/dns/example.com/removeRR", testErrorHandler()) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded(). + With(authenticationHeader, "user;1692475113;0123456789ABCDEF;699f01f077ca487bd66ac370d6dfc5b122c65522"), + ). + Route("POST /dns/example.com/removeRR", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) record := Record{ Name: "sub", diff --git a/providers/dns/netcup/internal/client_live_test.go b/providers/dns/netcup/internal/client_live_test.go new file mode 100644 index 000000000..3cf6c8c0b --- /dev/null +++ b/providers/dns/netcup/internal/client_live_test.go @@ -0,0 +1,137 @@ +package internal + +import ( + "fmt" + "strconv" + "strings" + "testing" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var envTest = tester.NewEnvTest( + "NETCUP_CUSTOMER_NUMBER", + "NETCUP_API_KEY", + "NETCUP_API_PASSWORD"). + WithDomain("NETCUP_DOMAIN") + +func TestClient_GetDNSRecords_Live(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + // Setup + envTest.RestoreEnv() + + client, err := NewClient( + envTest.GetValue("NETCUP_CUSTOMER_NUMBER"), + envTest.GetValue("NETCUP_API_KEY"), + envTest.GetValue("NETCUP_API_PASSWORD")) + require.NoError(t, err) + + ctx, err := client.CreateSessionContext(t.Context()) + require.NoError(t, err) + + info := dns01.GetChallengeInfo(envTest.GetDomain(), "123d==") + + zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + require.NoError(t, err, "error finding DNSZone") + + zone = dns01.UnFqdn(zone) + + // TestMethod + _, err = client.GetDNSRecords(ctx, zone) + require.NoError(t, err) + + // Tear down + err = client.Logout(ctx) + require.NoError(t, err) +} + +func TestClient_UpdateDNSRecord_Live(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + // Setup + envTest.RestoreEnv() + + client, err := NewClient( + envTest.GetValue("NETCUP_CUSTOMER_NUMBER"), + envTest.GetValue("NETCUP_API_KEY"), + envTest.GetValue("NETCUP_API_PASSWORD")) + require.NoError(t, err) + + ctx, err := client.CreateSessionContext(t.Context()) + require.NoError(t, err) + + info := dns01.GetChallengeInfo(envTest.GetDomain(), "123d==") + + zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + require.NotErrorIs(t, err, fmt.Errorf("error finding DNSZone, %w", err)) + + hostname := strings.Replace(info.EffectiveFQDN, "."+zone, "", 1) + + record := DNSRecord{ + Hostname: hostname, + RecordType: "TXT", + Destination: "asdf5678", + DeleteRecord: false, + } + + // test + zone = dns01.UnFqdn(zone) + + err = client.UpdateDNSRecord(ctx, zone, []DNSRecord{record}) + require.NoError(t, err) + + records, err := client.GetDNSRecords(ctx, zone) + require.NoError(t, err) + + recordIdx, err := GetDNSRecordIdx(records, record) + require.NoError(t, err) + + assert.Equal(t, record.Hostname, records[recordIdx].Hostname) + assert.Equal(t, record.RecordType, records[recordIdx].RecordType) + assert.Equal(t, record.Destination, records[recordIdx].Destination) + assert.Equal(t, record.DeleteRecord, records[recordIdx].DeleteRecord) + + records[recordIdx].DeleteRecord = true + + // Tear down + err = client.UpdateDNSRecord(ctx, envTest.GetDomain(), []DNSRecord{records[recordIdx]}) + require.NoError(t, err, "Did not remove record! Please do so yourself.") + + err = client.Logout(ctx) + require.NoError(t, err) +} + +func TestLiveClientAuth(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + // Setup + envTest.RestoreEnv() + + client, err := NewClient( + envTest.GetValue("NETCUP_CUSTOMER_NUMBER"), + envTest.GetValue("NETCUP_API_KEY"), + envTest.GetValue("NETCUP_API_PASSWORD")) + require.NoError(t, err) + + for i := range 4 { + t.Run("Test_"+strconv.Itoa(i+1), func(t *testing.T) { + t.Parallel() + + ctx, err := client.CreateSessionContext(t.Context()) + require.NoError(t, err) + + err = client.Logout(ctx) + require.NoError(t, err) + }) + } +} diff --git a/providers/dns/netcup/internal/client_test.go b/providers/dns/netcup/internal/client_test.go index 501629e8f..a1c91aac4 100644 --- a/providers/dns/netcup/internal/client_test.go +++ b/providers/dns/netcup/internal/client_test.go @@ -1,40 +1,30 @@ package internal import ( - "bytes" - "fmt" - "io" "net/http" "net/http/httptest" - "strings" "testing" - "github.com/go-acme/lego/v4/challenge/dns01" - "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -var envTest = tester.NewEnvTest( - "NETCUP_CUSTOMER_NUMBER", - "NETCUP_API_KEY", - "NETCUP_API_PASSWORD"). - WithDomain("NETCUP_DOMAIN") +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("a", "b", "c") + if err != nil { + return nil, err + } -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() + client.baseURL = server.URL + client.HTTPClient = server.Client() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client, err := NewClient("a", "b", "c") - require.NoError(t, err) - - client.baseURL = server.URL - client.HTTPClient = server.Client() - - return client, mux + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(), + ) } func TestGetDNSRecordIdx(t *testing.T) { @@ -139,59 +129,10 @@ func TestGetDNSRecordIdx(t *testing.T) { } func TestClient_GetDNSRecords(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { - raw, err := io.ReadAll(req.Body) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - if string(bytes.TrimSpace(raw)) != `{"action":"infoDnsRecords","param":{"domainname":"example.com","customernumber":"a","apikey":"b","apisessionid":""}}` { - http.Error(rw, fmt.Sprintf("invalid request body: %s", string(raw)), http.StatusBadRequest) - return - } - - response := ` - { - "serverrequestid":"srv-request-id", - "clientrequestid":"", - "action":"infoDnsRecords", - "status":"success", - "statuscode":2000, - "shortmessage":"Login successful", - "longmessage":"Session has been created successful.", - "responsedata":{ - "apisessionid":"api-session-id", - "dnsrecords":[ - { - "id":"1", - "hostname":"example.com", - "type":"TXT", - "priority":"1", - "destination":"bGVnbzE=", - "state":"yes", - "ttl":300 - }, - { - "id":"2", - "hostname":"example2.com", - "type":"TXT", - "priority":"1", - "destination":"bGVnbw==", - "state":"yes", - "ttl":300 - } - ] - } - }` - _, err = rw.Write([]byte(response)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("get_dns_records.json"), + servermock.CheckRequestJSONBodyFromFile("get_dns_records-request.json")). + Build(t) expected := []DNSRecord{{ ID: 1, @@ -219,67 +160,24 @@ func TestClient_GetDNSRecords(t *testing.T) { func TestClient_GetDNSRecords_errors(t *testing.T) { testCases := []struct { - desc string - handler func(rw http.ResponseWriter, req *http.Request) + desc string + handler http.Handler + expected string }{ { - desc: "HTTP error", - handler: func(rw http.ResponseWriter, _ *http.Request) { - http.Error(rw, "error message", http.StatusInternalServerError) - }, + desc: "HTTP error", + handler: servermock.Noop().WithStatusCode(http.StatusInternalServerError), + expected: `error when sending the request: unexpected status code: [status code: 500] body: `, }, { - desc: "API error", - handler: func(rw http.ResponseWriter, _ *http.Request) { - response := ` - { - "serverrequestid":"YxTr4EzdbJ101T211zR4yzUEMVE", - "clientrequestid":"", - "action":"infoDnsRecords", - "status":"error", - "statuscode":4013, - "shortmessage":"Validation Error.", - "longmessage":"Message is empty.", - "responsedata":"" - }` - _, err := rw.Write([]byte(response)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }, + desc: "API error", + handler: servermock.ResponseFromFixture("get_dns_records_error.json"), + expected: `error when sending the request: an error occurred during the action infoDnsRecords: [Status=error, StatusCode=4013, ShortMessage=Validation Error., LongMessage=Message is empty.]`, }, { - desc: "responsedata marshaling error", - handler: func(rw http.ResponseWriter, req *http.Request) { - raw, err := io.ReadAll(req.Body) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - if string(raw) != `{"action":"infoDnsRecords","param":{"domainname":"example.com","customernumber":"a","apikey":"b","apisessionid":"api-session-id"}}` { - http.Error(rw, fmt.Sprintf("invalid request body: %s", string(raw)), http.StatusBadRequest) - return - } - - response := ` - { - "serverrequestid":"srv-request-id", - "clientrequestid":"", - "action":"infoDnsRecords", - "status":"success", - "statuscode":2000, - "shortmessage":"Login successful", - "longmessage":"Session has been created successful.", - "responsedata":"" - }` - _, err = rw.Write([]byte(response)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }, + desc: "responsedata marshaling error", + handler: servermock.ResponseFromFixture("get_dns_records_error_unmarshal.json"), + expected: `error when sending the request: unable to unmarshal response: [status code: 200] body: "" error: json: cannot unmarshal string into Go value of type internal.InfoDNSRecordsResponse`, }, } @@ -287,104 +185,13 @@ func TestClient_GetDNSRecords_errors(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - client, mux := setupTest(t) - - mux.HandleFunc("/", test.handler) + client := mockBuilder(). + Route("POST /", test.handler). + Build(t) records, err := client.GetDNSRecords(t.Context(), "example.com") - require.Error(t, err) + require.EqualError(t, err, test.expected) assert.Empty(t, records) }) } } - -func TestClient_GetDNSRecords_Live(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - // Setup - envTest.RestoreEnv() - - client, err := NewClient( - envTest.GetValue("NETCUP_CUSTOMER_NUMBER"), - envTest.GetValue("NETCUP_API_KEY"), - envTest.GetValue("NETCUP_API_PASSWORD")) - require.NoError(t, err) - - ctx, err := client.CreateSessionContext(t.Context()) - require.NoError(t, err) - - info := dns01.GetChallengeInfo(envTest.GetDomain(), "123d==") - - zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - require.NoError(t, err, "error finding DNSZone") - - zone = dns01.UnFqdn(zone) - - // TestMethod - _, err = client.GetDNSRecords(ctx, zone) - require.NoError(t, err) - - // Tear down - err = client.Logout(ctx) - require.NoError(t, err) -} - -func TestClient_UpdateDNSRecord_Live(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - // Setup - envTest.RestoreEnv() - - client, err := NewClient( - envTest.GetValue("NETCUP_CUSTOMER_NUMBER"), - envTest.GetValue("NETCUP_API_KEY"), - envTest.GetValue("NETCUP_API_PASSWORD")) - require.NoError(t, err) - - ctx, err := client.CreateSessionContext(t.Context()) - require.NoError(t, err) - - info := dns01.GetChallengeInfo(envTest.GetDomain(), "123d==") - - zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - require.NotErrorIs(t, err, fmt.Errorf("error finding DNSZone, %w", err)) - - hostname := strings.Replace(info.EffectiveFQDN, "."+zone, "", 1) - - record := DNSRecord{ - Hostname: hostname, - RecordType: "TXT", - Destination: "asdf5678", - DeleteRecord: false, - } - - // test - zone = dns01.UnFqdn(zone) - - err = client.UpdateDNSRecord(ctx, zone, []DNSRecord{record}) - require.NoError(t, err) - - records, err := client.GetDNSRecords(ctx, zone) - require.NoError(t, err) - - recordIdx, err := GetDNSRecordIdx(records, record) - require.NoError(t, err) - - assert.Equal(t, record.Hostname, records[recordIdx].Hostname) - assert.Equal(t, record.RecordType, records[recordIdx].RecordType) - assert.Equal(t, record.Destination, records[recordIdx].Destination) - assert.Equal(t, record.DeleteRecord, records[recordIdx].DeleteRecord) - - records[recordIdx].DeleteRecord = true - - // Tear down - err = client.UpdateDNSRecord(ctx, envTest.GetDomain(), []DNSRecord{records[recordIdx]}) - require.NoError(t, err, "Did not remove record! Please do so yourself.") - - err = client.Logout(ctx) - require.NoError(t, err) -} diff --git a/providers/dns/netcup/internal/fixtures/get_dns_records-request.json b/providers/dns/netcup/internal/fixtures/get_dns_records-request.json new file mode 100644 index 000000000..bcf8e5310 --- /dev/null +++ b/providers/dns/netcup/internal/fixtures/get_dns_records-request.json @@ -0,0 +1,9 @@ +{ + "action": "infoDnsRecords", + "param": { + "domainname": "example.com", + "customernumber": "a", + "apikey": "b", + "apisessionid": "" + } +} diff --git a/providers/dns/netcup/internal/fixtures/get_dns_records.json b/providers/dns/netcup/internal/fixtures/get_dns_records.json new file mode 100644 index 000000000..e521a8e24 --- /dev/null +++ b/providers/dns/netcup/internal/fixtures/get_dns_records.json @@ -0,0 +1,32 @@ +{ + "serverrequestid": "srv-request-id", + "clientrequestid": "", + "action": "infoDnsRecords", + "status": "success", + "statuscode": 2000, + "shortmessage": "Login successful", + "longmessage": "Session has been created successful.", + "responsedata": { + "apisessionid": "api-session-id", + "dnsrecords": [ + { + "id": "1", + "hostname": "example.com", + "type": "TXT", + "priority": "1", + "destination": "bGVnbzE=", + "state": "yes", + "ttl": 300 + }, + { + "id": "2", + "hostname": "example2.com", + "type": "TXT", + "priority": "1", + "destination": "bGVnbw==", + "state": "yes", + "ttl": 300 + } + ] + } +} diff --git a/providers/dns/netcup/internal/fixtures/get_dns_records_error.json b/providers/dns/netcup/internal/fixtures/get_dns_records_error.json new file mode 100644 index 000000000..3ba472366 --- /dev/null +++ b/providers/dns/netcup/internal/fixtures/get_dns_records_error.json @@ -0,0 +1,10 @@ +{ + "serverrequestid":"YxTr4EzdbJ101T211zR4yzUEMVE", + "clientrequestid":"", + "action":"infoDnsRecords", + "status":"error", + "statuscode":4013, + "shortmessage":"Validation Error.", + "longmessage":"Message is empty.", + "responsedata":"" +} diff --git a/providers/dns/netcup/internal/fixtures/get_dns_records_error_unmarshal.json b/providers/dns/netcup/internal/fixtures/get_dns_records_error_unmarshal.json new file mode 100644 index 000000000..f8f91329f --- /dev/null +++ b/providers/dns/netcup/internal/fixtures/get_dns_records_error_unmarshal.json @@ -0,0 +1,10 @@ +{ + "serverrequestid":"srv-request-id", + "clientrequestid":"", + "action":"infoDnsRecords", + "status":"success", + "statuscode":2000, + "shortmessage":"Login successful", + "longmessage":"Session has been created successful.", + "responsedata":"" +} diff --git a/providers/dns/netcup/internal/fixtures/login-request.json b/providers/dns/netcup/internal/fixtures/login-request.json new file mode 100644 index 000000000..1e287dfe0 --- /dev/null +++ b/providers/dns/netcup/internal/fixtures/login-request.json @@ -0,0 +1,8 @@ +{ + "action": "login", + "param": { + "customernumber": "a", + "apikey": "b", + "apipassword": "c" + } +} diff --git a/providers/dns/netcup/internal/fixtures/login.json b/providers/dns/netcup/internal/fixtures/login.json new file mode 100644 index 000000000..a66979544 --- /dev/null +++ b/providers/dns/netcup/internal/fixtures/login.json @@ -0,0 +1,12 @@ +{ + "serverrequestid": "srv-request-id", + "clientrequestid": "", + "action": "login", + "status": "success", + "statuscode": 2000, + "shortmessage": "Login successful", + "longmessage": "Session has been created successful.", + "responsedata": { + "apisessionid": "api-session-id" + } +} diff --git a/providers/dns/netcup/internal/fixtures/login_error.json b/providers/dns/netcup/internal/fixtures/login_error.json new file mode 100644 index 000000000..a32568f78 --- /dev/null +++ b/providers/dns/netcup/internal/fixtures/login_error.json @@ -0,0 +1,10 @@ +{ + "serverrequestid":"YxTr4EzdbJ101T211zR4yzUEMVE", + "clientrequestid":"", + "action":"login", + "status":"error", + "statuscode":4013, + "shortmessage":"Validation Error.", + "longmessage":"Message is empty.", + "responsedata":"" +} diff --git a/providers/dns/netcup/internal/fixtures/login_error_unmarshal.json b/providers/dns/netcup/internal/fixtures/login_error_unmarshal.json new file mode 100644 index 000000000..96e7cbd0c --- /dev/null +++ b/providers/dns/netcup/internal/fixtures/login_error_unmarshal.json @@ -0,0 +1,10 @@ +{ + "serverrequestid": "srv-request-id", + "clientrequestid": "", + "action": "login", + "status": "success", + "statuscode": 2000, + "shortmessage": "Login successful", + "longmessage": "Session has been created successful.", + "responsedata": "" +} diff --git a/providers/dns/netcup/internal/fixtures/logout-request.json b/providers/dns/netcup/internal/fixtures/logout-request.json new file mode 100644 index 000000000..add759c3a --- /dev/null +++ b/providers/dns/netcup/internal/fixtures/logout-request.json @@ -0,0 +1,8 @@ +{ + "action": "logout", + "param": { + "customernumber": "a", + "apikey": "b", + "apisessionid": "session-id" + } +} diff --git a/providers/dns/netcup/internal/fixtures/logout.json b/providers/dns/netcup/internal/fixtures/logout.json new file mode 100644 index 000000000..50881fff3 --- /dev/null +++ b/providers/dns/netcup/internal/fixtures/logout.json @@ -0,0 +1,10 @@ +{ + "serverrequestid": "request-id", + "clientrequestid": "", + "action": "logout", + "status": "success", + "statuscode": 2000, + "shortmessage": "Logout successful", + "longmessage": "Session has been terminated successful.", + "responsedata": "" +} diff --git a/providers/dns/netcup/internal/fixtures/logout_error.json b/providers/dns/netcup/internal/fixtures/logout_error.json new file mode 100644 index 000000000..a2de32da1 --- /dev/null +++ b/providers/dns/netcup/internal/fixtures/logout_error.json @@ -0,0 +1,10 @@ +{ + "serverrequestid":"YxTr4EzdbJ101T211zR4yzUEMVE", + "clientrequestid":"", + "action":"logout", + "status":"error", + "statuscode":4013, + "shortmessage":"Validation Error.", + "longmessage":"Message is empty.", + "responsedata":"" +} diff --git a/providers/dns/netcup/internal/session_test.go b/providers/dns/netcup/internal/session_test.go index ceec56708..27442b347 100644 --- a/providers/dns/netcup/internal/session_test.go +++ b/providers/dns/netcup/internal/session_test.go @@ -1,14 +1,11 @@ package internal import ( - "bytes" "context" - "fmt" - "io" "net/http" - "strconv" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -20,40 +17,10 @@ func mockContext(t *testing.T) context.Context { } func TestClient_Login(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { - raw, err := io.ReadAll(req.Body) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - if string(bytes.TrimSpace(raw)) != `{"action":"login","param":{"customernumber":"a","apikey":"b","apipassword":"c"}}` { - http.Error(rw, fmt.Sprintf("invalid request body: %s", string(raw)), http.StatusBadRequest) - return - } - - response := ` - { - "serverrequestid": "srv-request-id", - "clientrequestid": "", - "action": "login", - "status": "success", - "statuscode": 2000, - "shortmessage": "Login successful", - "longmessage": "Session has been created successful.", - "responsedata": { - "apisessionid": "api-session-id" - } - } - ` - _, err = rw.Write([]byte(response)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("login.json"), + servermock.CheckRequestJSONBodyFromFile("login-request.json")). + Build(t) sessionID, err := client.login(t.Context()) require.NoError(t, err) @@ -63,56 +30,24 @@ func TestClient_Login(t *testing.T) { func TestClient_Login_errors(t *testing.T) { testCases := []struct { - desc string - handler func(rw http.ResponseWriter, req *http.Request) + desc string + handler http.Handler + expected string }{ { - desc: "HTTP error", - handler: func(rw http.ResponseWriter, _ *http.Request) { - http.Error(rw, "error message", http.StatusInternalServerError) - }, + desc: "HTTP error", + handler: servermock.Noop().WithStatusCode(http.StatusInternalServerError), + expected: `loging error: unexpected status code: [status code: 500] body: `, }, { - desc: "API error", - handler: func(rw http.ResponseWriter, _ *http.Request) { - response := ` - { - "serverrequestid":"YxTr4EzdbJ101T211zR4yzUEMVE", - "clientrequestid":"", - "action":"login", - "status":"error", - "statuscode":4013, - "shortmessage":"Validation Error.", - "longmessage":"Message is empty.", - "responsedata":"" - }` - _, err := rw.Write([]byte(response)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }, + desc: "API error", + handler: servermock.ResponseFromFixture("login_error.json"), + expected: `loging error: an error occurred during the action login: [Status=error, StatusCode=4013, ShortMessage=Validation Error., LongMessage=Message is empty.]`, }, { - desc: "responsedata marshaling error", - handler: func(rw http.ResponseWriter, _ *http.Request) { - response := ` - { - "serverrequestid": "srv-request-id", - "clientrequestid": "", - "action": "login", - "status": "success", - "statuscode": 2000, - "shortmessage": "Login successful", - "longmessage": "Session has been created successful.", - "responsedata": "" - }` - _, err := rw.Write([]byte(response)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }, + desc: "responsedata marshaling error", + handler: servermock.ResponseFromFixture("login_error_unmarshal.json"), + expected: `loging error: unable to unmarshal response: [status code: 200] body: "" error: json: cannot unmarshal string into Go value of type internal.LoginResponse`, }, } @@ -120,49 +55,22 @@ func TestClient_Login_errors(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - client, mux := setupTest(t) - - mux.HandleFunc("/", test.handler) + client := mockBuilder(). + Route("POST /", test.handler). + Build(t) sessionID, err := client.login(t.Context()) - assert.Error(t, err) + assert.EqualError(t, err, test.expected) assert.Empty(t, sessionID) }) } } func TestClient_Logout(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { - raw, err := io.ReadAll(req.Body) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - if string(bytes.TrimSpace(raw)) != `{"action":"logout","param":{"customernumber":"a","apikey":"b","apisessionid":"session-id"}}` { - http.Error(rw, fmt.Sprintf("invalid request body: %s", string(raw)), http.StatusBadRequest) - return - } - - response := ` - { - "serverrequestid": "request-id", - "clientrequestid": "", - "action": "logout", - "status": "success", - "statuscode": 2000, - "shortmessage": "Logout successful", - "longmessage": "Session has been terminated successful.", - "responsedata": "" - }` - _, err = rw.Write([]byte(response)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("POST /", servermock.ResponseFromFixture("logout.json"), + servermock.CheckRequestJSONBodyFromFile("logout-request.json")). + Build(t) err := client.Logout(mockContext(t)) require.NoError(t, err) @@ -170,35 +78,17 @@ func TestClient_Logout(t *testing.T) { func TestClient_Logout_errors(t *testing.T) { testCases := []struct { - desc string - handler func(rw http.ResponseWriter, req *http.Request) + desc string + handler http.Handler + expected string }{ { - desc: "HTTP error", - handler: func(rw http.ResponseWriter, _ *http.Request) { - http.Error(rw, "error message", http.StatusInternalServerError) - }, + desc: "HTTP error", + handler: servermock.Noop().WithStatusCode(http.StatusInternalServerError), }, { - desc: "API error", - handler: func(rw http.ResponseWriter, _ *http.Request) { - response := ` - { - "serverrequestid":"YxTr4EzdbJ101T211zR4yzUEMVE", - "clientrequestid":"", - "action":"logout", - "status":"error", - "statuscode":4013, - "shortmessage":"Validation Error.", - "longmessage":"Message is empty.", - "responsedata":"" - }` - _, err := rw.Write([]byte(response)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }, + desc: "API error", + handler: servermock.ResponseFromFixture("login_error.json"), }, } @@ -206,39 +96,12 @@ func TestClient_Logout_errors(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - client, mux := setupTest(t) - - mux.HandleFunc("/", test.handler) + client := mockBuilder(). + Route("POST /", test.handler). + Build(t) err := client.Logout(t.Context()) require.Error(t, err) }) } } - -func TestLiveClientAuth(t *testing.T) { - if !envTest.IsLiveTest() { - t.Skip("skipping live test") - } - - // Setup - envTest.RestoreEnv() - - client, err := NewClient( - envTest.GetValue("NETCUP_CUSTOMER_NUMBER"), - envTest.GetValue("NETCUP_API_KEY"), - envTest.GetValue("NETCUP_API_PASSWORD")) - require.NoError(t, err) - - for i := range 4 { - t.Run("Test_"+strconv.Itoa(i+1), func(t *testing.T) { - t.Parallel() - - ctx, err := client.CreateSessionContext(t.Context()) - require.NoError(t, err) - - err = client.Logout(ctx) - require.NoError(t, err) - }) - } -} diff --git a/providers/dns/netlify/internal/client_test.go b/providers/dns/netlify/internal/client_test.go index a1e9e09a3..b19a8f071 100644 --- a/providers/dns/netlify/internal/client_test.go +++ b/providers/dns/netlify/internal/client_test.go @@ -1,61 +1,33 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, token string) (*Client, *http.ServeMux) { - t.Helper() +func setupClient(token string) func(server *httptest.Server) (*Client, error) { + return func(server *httptest.Server) (*Client, error) { + client := NewClient(OAuthStaticAccessToken(server.Client(), token)) + client.baseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient(OAuthStaticAccessToken(server.Client(), token)) - client.baseURL, _ = url.Parse(server.URL) - - return client, mux + return client, nil + } } func TestClient_GetRecords(t *testing.T) { - client, mux := setupTest(t, "tokenA") - - mux.HandleFunc("/dns_zones/zoneID/dns_records", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, "unsupported method", http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get("Authorization") - if auth != "Bearer tokenA" { - http.Error(rw, fmt.Sprintf("invali token: %s", auth), http.StatusUnauthorized) - return - } - - rw.Header().Set("Content-Type", "application/json; charset=utf-8") - - file, err := os.Open("./fixtures/get_records.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := servermock.NewBuilder[*Client](setupClient("tokenA"), + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Bearer tokenA"), + ). + Route("GET /dns_zones/zoneID/dns_records", + servermock.ResponseFromFixture("get_records.json")). + Build(t) records, err := client.GetRecords(t.Context(), "zoneID") require.NoError(t, err) @@ -69,36 +41,16 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_CreateRecord(t *testing.T) { - client, mux := setupTest(t, "tokenB") - - mux.HandleFunc("/dns_zones/zoneID/dns_records", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, "unsupported method", http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get("Authorization") - if auth != "Bearer tokenB" { - http.Error(rw, fmt.Sprintf("invali token: %s", auth), http.StatusUnauthorized) - return - } - - rw.Header().Set("Content-Type", "application/json; charset=utf-8") - rw.WriteHeader(http.StatusCreated) - - file, err := os.Open("./fixtures/create_record.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := servermock.NewBuilder[*Client](setupClient("tokenB"), + servermock.CheckHeader(). + WithAccept("application/json"). + WithContentType("application/json; charset=utf-8"). + WithAuthorization("Bearer tokenB"), + ). + Route("POST /dns_zones/zoneID/dns_records", + servermock.ResponseFromFixture("create_record.json"). + WithStatusCode(http.StatusCreated)). + Build(t) record := DNSRecord{ Hostname: "_acme-challenge.example.com", @@ -122,22 +74,14 @@ func TestClient_CreateRecord(t *testing.T) { } func TestClient_RemoveRecord(t *testing.T) { - client, mux := setupTest(t, "tokenC") - - mux.HandleFunc("/dns_zones/zoneID/dns_records/recordID", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, "unsupported method", http.StatusMethodNotAllowed) - return - } - - auth := req.Header.Get("Authorization") - if auth != "Bearer tokenC" { - http.Error(rw, fmt.Sprintf("invali token: %s", auth), http.StatusUnauthorized) - return - } - - rw.WriteHeader(http.StatusNoContent) - }) + client := servermock.NewBuilder[*Client](setupClient("tokenC"), + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Bearer tokenC"), + ). + Route("DELETE /dns_zones/zoneID/dns_records/recordID", + servermock.Noop(). + WithStatusCode(http.StatusNoContent)). + Build(t) err := client.RemoveRecord(t.Context(), "zoneID", "recordID") require.NoError(t, err) diff --git a/providers/dns/nicmanager/internal/client_test.go b/providers/dns/nicmanager/internal/client_test.go index 9c8679bea..1eb7d5a36 100644 --- a/providers/dns/nicmanager/internal/client_test.go +++ b/providers/dns/nicmanager/internal/client_test.go @@ -1,21 +1,42 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + opts := Options{ + Login: "l", + Username: "u", + Password: "p", + OTP: "2hsn", + } + + client := NewClient(opts) + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) + + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithBasicAuth("l.u", "p"). + WithRegexp(headerTOTPToken, `\d{6}`)) +} + func TestClient_GetZone(t *testing.T) { - client := setupTest(t, "/anycast/nicmanager-anycastdns4.net", testHandler(http.MethodGet, http.StatusOK, "zone.json")) + client := mockBuilder(). + Route("GET /anycast/nicmanager-anycastdns4.net", + servermock.ResponseFromFixture("zone.json")). + Build(t) zone, err := client.GetZone(t.Context(), "nicmanager-anycastdns4.net") require.NoError(t, err) @@ -38,14 +59,22 @@ func TestClient_GetZone(t *testing.T) { } func TestClient_GetZone_error(t *testing.T) { - client := setupTest(t, "/anycast/foo", testHandler(http.MethodGet, http.StatusNotFound, "error.json")) + client := mockBuilder(). + Route("GET /anycast/foo", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusNotFound)). + Build(t) _, err := client.GetZone(t.Context(), "foo") - require.Error(t, err) + require.EqualError(t, err, "404: Not Found") } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, "/anycast/zonedomain.tld/records", testHandler(http.MethodPost, http.StatusAccepted, "error.json")) + client := mockBuilder(). + Route("POST /anycast/zonedomain.tld/records", + servermock.Noop(). + WithStatusCode(http.StatusAccepted)). + Build(t) record := RecordCreateUpdate{ Type: "TXT", @@ -59,7 +88,11 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, "/anycast/zonedomain.tld", testHandler(http.MethodPost, http.StatusUnauthorized, "error.json")) + client := mockBuilder(). + Route("POST /anycast/zonedomain.tld/records", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) record := RecordCreateUpdate{ Type: "TXT", @@ -69,77 +102,27 @@ func TestClient_AddRecord_error(t *testing.T) { } err := client.AddRecord(t.Context(), "zonedomain.tld", record) - require.Error(t, err) + require.EqualError(t, err, "401: Not Found") } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, "/anycast/zonedomain.tld/records/6", testHandler(http.MethodDelete, http.StatusAccepted, "error.json")) + client := mockBuilder(). + Route("DELETE /anycast/zonedomain.tld/records/6", + servermock.Noop(). + WithStatusCode(http.StatusAccepted)). + Build(t) err := client.DeleteRecord(t.Context(), "zonedomain.tld", 6) require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, "/anycast/zonedomain.tld/records/6", testHandler(http.MethodDelete, http.StatusNoContent, "")) + client := mockBuilder(). + Route("DELETE /anycast/zonedomain.tld/records/6", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusNotFound)). + Build(t) - err := client.DeleteRecord(t.Context(), "zonedomain.tld", 7) - require.Error(t, err) -} - -func setupTest(t *testing.T, path string, handler http.Handler) *Client { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.Handle(path, handler) - - opts := Options{ - Login: "foo", - Username: "bar", - Password: "foo", - OTP: "2hsn", - } - - client := NewClient(opts) - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client -} - -func testHandler(method string, statusCode int, filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf(`{"message":"unsupported method: %s"}`, req.Method), http.StatusMethodNotAllowed) - return - } - - username, password, ok := req.BasicAuth() - if !ok || username != "foo.bar" || password != "foo" { - http.Error(rw, `{"message":"Unauthenticated"}`, http.StatusUnauthorized) - return - } - - rw.WriteHeader(statusCode) - - if statusCode == http.StatusNoContent { - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, fmt.Sprintf(`{"message":"%v"}`, err), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, fmt.Sprintf(`{"message":"%v"}`, err), http.StatusInternalServerError) - return - } - } + err := client.DeleteRecord(t.Context(), "zonedomain.tld", 6) + require.EqualError(t, err, "404: Not Found") } diff --git a/providers/dns/nicru/internal/client_test.go b/providers/dns/nicru/internal/client_test.go index d49aa4014..f01300406 100644 --- a/providers/dns/nicru/internal/client_test.go +++ b/providers/dns/nicru/internal/client_test.go @@ -1,63 +1,36 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, handler http.HandlerFunc) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(server.Client()) + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client.baseURL, _ = url.Parse(server.URL) - mux.HandleFunc(pattern, handler) - - client, err := NewClient(server.Client()) - require.NoError(t, err) - - client.baseURL, _ = url.Parse(server.URL) - - return client -} - -func writeFixtures(method, filename string, status int) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } + return client, nil + }, + servermock.CheckHeader(). + WithAccept("text/xml"), + ) } func TestClient_GetServices(t *testing.T) { - client := setupTest(t, "/services", - writeFixtures(http.MethodGet, "services_GET.xml", http.StatusOK)) + client := mockBuilder(). + Route("GET /services", servermock.ResponseFromFixture("services_GET.xml")). + Build(t) zones, err := client.GetServices(t.Context()) require.NoError(t, err) @@ -91,8 +64,9 @@ func TestClient_GetServices(t *testing.T) { } func TestClient_ListZones(t *testing.T) { - client := setupTest(t, "/zones", - writeFixtures(http.MethodGet, "zones_all_GET.xml", http.StatusOK)) + client := mockBuilder(). + Route("GET /zones", servermock.ResponseFromFixture("zones_all_GET.xml")). + Build(t) zones, err := client.ListZones(t.Context()) require.NoError(t, err) @@ -137,8 +111,9 @@ func TestClient_ListZones(t *testing.T) { } func TestClient_ListZones_error(t *testing.T) { - client := setupTest(t, "/zones", - writeFixtures(http.MethodGet, "errors.xml", http.StatusOK)) + client := mockBuilder(). + Route("GET /zones", servermock.ResponseFromFixture("errors.xml")). + Build(t) _, err := client.ListZones(t.Context()) require.ErrorIs(t, err, Error{ @@ -148,8 +123,10 @@ func TestClient_ListZones_error(t *testing.T) { } func TestClient_GetZonesByService(t *testing.T) { - client := setupTest(t, "/services/test/zones", - writeFixtures(http.MethodGet, "zones_GET.xml", http.StatusOK)) + client := mockBuilder(). + Route("GET /services/test/zones", + servermock.ResponseFromFixture("zones_GET.xml")). + Build(t) zones, err := client.GetZonesByService(t.Context(), "test") require.NoError(t, err) @@ -194,8 +171,10 @@ func TestClient_GetZonesByService(t *testing.T) { } func TestClient_GetZonesByService_error(t *testing.T) { - client := setupTest(t, "/services/test/zones", - writeFixtures(http.MethodGet, "errors.xml", http.StatusOK)) + client := mockBuilder(). + Route("GET /services/test/zones", + servermock.ResponseFromFixture("errors.xml")). + Build(t) _, err := client.GetZonesByService(t.Context(), "test") require.ErrorIs(t, err, Error{ @@ -205,8 +184,10 @@ func TestClient_GetZonesByService_error(t *testing.T) { } func TestClient_GetRecords(t *testing.T) { - client := setupTest(t, "/services/test/zones/example.com./records", - writeFixtures(http.MethodGet, "records_GET.xml", http.StatusOK)) + client := mockBuilder(). + Route("GET /services/test/zones/example.com./records", + servermock.ResponseFromFixture("records_GET.xml")). + Build(t) records, err := client.GetRecords(t.Context(), "test", "example.com.") require.NoError(t, err) @@ -270,8 +251,10 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_GetRecords_error(t *testing.T) { - client := setupTest(t, "/services/test/zones/example.com./records", - writeFixtures(http.MethodGet, "errors.xml", http.StatusOK)) + client := mockBuilder(). + Route("GET /services/test/zones/example.com./records", + servermock.ResponseFromFixture("errors.xml")). + Build(t) _, err := client.GetRecords(t.Context(), "test", "example.com.") require.ErrorIs(t, err, Error{ @@ -281,8 +264,12 @@ func TestClient_GetRecords_error(t *testing.T) { } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, "/services/test/zones/example.com./records", - writeFixtures(http.MethodPut, "records_PUT.xml", http.StatusOK)) + client := mockBuilder(). + Route("PUT /services/test/zones/example.com./records", + servermock.ResponseFromFixture("records_PUT.xml"), + servermock.CheckHeader(). + WithContentType("text/xml")). + Build(t) rrs := []RR{ { @@ -337,8 +324,12 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, "/services/test/zones/example.com./records", - writeFixtures(http.MethodPut, "errors.xml", http.StatusOK)) + client := mockBuilder(). + Route("PUT /services/test/zones/example.com./records", + servermock.ResponseFromFixture("errors.xml"), + servermock.CheckHeader(). + WithContentType("text/xml")). + Build(t) rrs := []RR{ { @@ -361,16 +352,20 @@ func TestClient_AddRecord_error(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, "/services/test/zones/example.com./records/123", - writeFixtures(http.MethodDelete, "record_DELETE.xml", http.StatusUnauthorized)) + client := mockBuilder(). + Route("DELETE /services/test/zones/example.com./records/123", + servermock.ResponseFromFixture("record_DELETE.xml")). + Build(t) err := client.DeleteRecord(t.Context(), "test", "example.com.", "123") require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, "/services/test/zones/example.com./records/123", - writeFixtures(http.MethodDelete, "errors.xml", http.StatusUnauthorized)) + client := mockBuilder(). + Route("DELETE /services/test/zones/example.com./records/123", + servermock.ResponseFromFixture("errors.xml")). + Build(t) err := client.DeleteRecord(t.Context(), "test", "example.com.", "123") require.ErrorIs(t, err, Error{ @@ -380,14 +375,20 @@ func TestClient_DeleteRecord_error(t *testing.T) { } func TestClient_CommitZone(t *testing.T) { - client := setupTest(t, "/services/test/zones/example.com./commit", writeFixtures(http.MethodPost, "commit_POST.xml", http.StatusOK)) + client := mockBuilder(). + Route("POST /services/test/zones/example.com./commit", + servermock.ResponseFromFixture("commit_POST.xml")). + Build(t) err := client.CommitZone(t.Context(), "test", "example.com.") require.NoError(t, err) } func TestClient_CommitZone_error(t *testing.T) { - client := setupTest(t, "/services/test/zones/example.com./commit", writeFixtures(http.MethodPost, "errors.xml", http.StatusOK)) + client := mockBuilder(). + Route("POST /services/test/zones/example.com./commit", + servermock.ResponseFromFixture("errors.xml")). + Build(t) err := client.CommitZone(t.Context(), "test", "example.com.") require.ErrorIs(t, err, Error{ diff --git a/providers/dns/nifcloud/internal/client_test.go b/providers/dns/nifcloud/internal/client_test.go index 91f9d36e2..501265ada 100644 --- a/providers/dns/nifcloud/internal/client_test.go +++ b/providers/dns/nifcloud/internal/client_test.go @@ -1,37 +1,35 @@ package internal import ( - "fmt" "net/http" "net/http/httptest" "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, responseBody string, statusCode int) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("A", "B") + if err != nil { + return nil, err + } - handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(statusCode) - _, _ = fmt.Fprintln(w, responseBody) - }) + client.HTTPClient = server.Client() + client.BaseURL, _ = url.Parse(server.URL) - server := httptest.NewServer(handler) - t.Cleanup(server.Close) - - client, err := NewClient("A", "B") - require.NoError(t, err) - - client.HTTPClient = server.Client() - client.BaseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader(). + WithRegexp("X-Nifty-Authorization", "NIFTY3-HTTPS NiftyAccessKeyId=A,Algorithm=HmacSHA1,Signature=.+"), + ) } -func TestChangeResourceRecordSets(t *testing.T) { +func TestClient_ChangeResourceRecordSets(t *testing.T) { responseBody := ` @@ -42,7 +40,10 @@ func TestChangeResourceRecordSets(t *testing.T) { ` - client := setupTest(t, responseBody, http.StatusOK) + client := mockBuilder(). + Route("POST /", servermock.RawStringResponse(responseBody), + servermock.CheckHeader().WithContentType("text/xml; charset=utf-8")). + Build(t) res, err := client.ChangeResourceRecordSets(t.Context(), "example.com", ChangeResourceRecordSetsRequest{}) require.NoError(t, err) @@ -52,7 +53,7 @@ func TestChangeResourceRecordSets(t *testing.T) { assert.Equal(t, "2015-08-05T00:00:00.000Z", res.ChangeInfo.SubmittedAt) } -func TestChangeResourceRecordSetsErrors(t *testing.T) { +func TestClient_ChangeResourceRecordSets_errors(t *testing.T) { testCases := []struct { desc string responseBody string @@ -89,7 +90,13 @@ func TestChangeResourceRecordSetsErrors(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client := setupTest(t, test.responseBody, test.statusCode) + client := mockBuilder(). + Route("POST /", + servermock.RawStringResponse(test.responseBody). + WithStatusCode(test.statusCode), + servermock.CheckHeader(). + WithContentType("text/xml; charset=utf-8")). + Build(t) res, err := client.ChangeResourceRecordSets(t.Context(), "example.com", ChangeResourceRecordSetsRequest{}) assert.Nil(t, res) @@ -98,7 +105,7 @@ func TestChangeResourceRecordSetsErrors(t *testing.T) { } } -func TestGetChange(t *testing.T) { +func TestClient_GetChange(t *testing.T) { responseBody := ` @@ -109,7 +116,9 @@ func TestGetChange(t *testing.T) { ` - client := setupTest(t, responseBody, http.StatusOK) + client := mockBuilder(). + Route("GET /", servermock.RawStringResponse(responseBody)). + Build(t) res, err := client.GetChange(t.Context(), "12345") require.NoError(t, err) @@ -119,7 +128,7 @@ func TestGetChange(t *testing.T) { assert.Equal(t, "2015-08-05T00:00:00.000Z", res.ChangeInfo.SubmittedAt) } -func TestGetChangeErrors(t *testing.T) { +func TestClient_GetChange_errors(t *testing.T) { testCases := []struct { desc string responseBody string @@ -156,7 +165,10 @@ func TestGetChangeErrors(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - client := setupTest(t, test.responseBody, test.statusCode) + client := mockBuilder(). + Route("GET /", + servermock.RawStringResponse(test.responseBody).WithStatusCode(test.statusCode)). + Build(t) res, err := client.GetChange(t.Context(), "12345") assert.Nil(t, res) diff --git a/providers/dns/njalla/internal/client_test.go b/providers/dns/njalla/internal/client_test.go index 9ad58f24b..ec9309078 100644 --- a/providers/dns/njalla/internal/client_test.go +++ b/providers/dns/njalla/internal/client_test.go @@ -1,75 +1,31 @@ package internal import ( - "encoding/json" - "fmt" - "net/http" "net/http/httptest" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, handler func(http.ResponseWriter, *http.Request)) *Client { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - token := req.Header.Get(authorizationHeader) - if token != "Njalla secret" { - _, _ = rw.Write([]byte(`{"jsonrpc":"2.0", "Error": {"code": 403, "message": "Invalid token."}}`)) - return - } - - if handler != nil { - handler(rw, req) - } else { - _, _ = rw.Write([]byte(`{"jsonrpc":"2.0"}`)) - } - }) - +func setupClient(server *httptest.Server) (*Client, error) { client := NewClient("secret") client.apiEndpoint = server.URL + client.HTTPClient = server.Client() - return client + return client, nil } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, func(rw http.ResponseWriter, req *http.Request) { - apiReq := struct { - Method string `json:"method"` - Params Record `json:"params"` - }{} - - err := json.NewDecoder(req.Body).Decode(&apiReq) - if err != nil { - http.Error(rw, "failed to marshal test request body", http.StatusInternalServerError) - return - } - - apiReq.Params.ID = "123" - - resp := map[string]any{ - "jsonrpc": "2.0", - "id": "897", - "result": apiReq.Params, - } - - err = json.NewEncoder(rw).Encode(resp) - if err != nil { - http.Error(rw, "failed to marshal test response", http.StatusInternalServerError) - return - } - }) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Njalla secret"), + ). + Route("POST /", + servermock.ResponseFromFixture("add_record.json"), + servermock.CheckRequestJSONBodyFromFile("add_record-request.json")). + Build(t) record := Record{ Content: "foobar", @@ -94,7 +50,13 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, nil) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Njalla invalid"), + ). + Route("POST /", servermock.ResponseFromFixture("auth_error.json")). + Build(t) + client.token = "invalid" record := Record{ @@ -106,55 +68,20 @@ func TestClient_AddRecord_error(t *testing.T) { } result, err := client.AddRecord(t.Context(), record) - require.Error(t, err) + require.EqualError(t, err, "code: 403, message: Invalid token.") assert.Nil(t, result) } func TestClient_ListRecords(t *testing.T) { - client := setupTest(t, func(rw http.ResponseWriter, req *http.Request) { - apiReq := struct { - Method string `json:"method"` - Params Record `json:"params"` - }{} - - err := json.NewDecoder(req.Body).Decode(&apiReq) - if err != nil { - http.Error(rw, "failed to marshal test request body", http.StatusInternalServerError) - return - } - - resp := map[string]any{ - "jsonrpc": "2.0", - "id": "897", - "result": Records{ - Records: []Record{ - { - ID: "1", - Domain: apiReq.Params.Domain, - Content: "test", - Name: "test01", - TTL: 300, - Type: "TXT", - }, - { - ID: "2", - Domain: apiReq.Params.Domain, - Content: "txtTxt", - Name: "test02", - TTL: 120, - Type: "TXT", - }, - }, - }, - } - - err = json.NewEncoder(rw).Encode(resp) - if err != nil { - http.Error(rw, "failed to marshal test response", http.StatusInternalServerError) - return - } - }) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Njalla secret"), + ). + Route("POST /", + servermock.ResponseFromFixture("list_records.json"), + servermock.CheckRequestJSONBodyFromFile("list_records-request.json")). + Build(t) records, err := client.ListRecords(t.Context(), "example.com") require.NoError(t, err) @@ -182,49 +109,43 @@ func TestClient_ListRecords(t *testing.T) { } func TestClient_ListRecords_error(t *testing.T) { - client := setupTest(t, nil) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Njalla invalid"), + ). + Route("POST /", servermock.ResponseFromFixture("auth_error.json")). + Build(t) + client.token = "invalid" records, err := client.ListRecords(t.Context(), "example.com") - require.Error(t, err) + require.EqualError(t, err, "code: 403, message: Invalid token.") assert.Empty(t, records) } func TestClient_RemoveRecord(t *testing.T) { - client := setupTest(t, func(rw http.ResponseWriter, req *http.Request) { - apiReq := struct { - Method string `json:"method"` - Params Record `json:"params"` - }{} - - err := json.NewDecoder(req.Body).Decode(&apiReq) - if err != nil { - http.Error(rw, "failed to marshal test request body", http.StatusInternalServerError) - return - } - - if apiReq.Params.ID == "" { - _, _ = rw.Write([]byte(`{"jsonrpc":"2.0", "Error": {"code": 400, "message": ""missing ID"}}`)) - return - } - - if apiReq.Params.Domain == "" { - _, _ = rw.Write([]byte(`{"jsonrpc":"2.0", "Error": {"code": 400, "message": ""missing domain"}}`)) - return - } - - _, _ = rw.Write([]byte(`{"jsonrpc":"2.0"}`)) - }) + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Njalla secret"), + ). + Route("POST /", + servermock.RawStringResponse(`{"jsonrpc":"2.0"}`), + servermock.CheckRequestJSONBodyFromFile("remove_record-request.json")). + Build(t) err := client.RemoveRecord(t.Context(), "123", "example.com") require.NoError(t, err) } func TestClient_RemoveRecord_error(t *testing.T) { - client := setupTest(t, nil) - client.token = "invalid" + client := servermock.NewBuilder[*Client](setupClient, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Njalla secret"), + ). + Route("POST /", servermock.ResponseFromFixture("remove_record_error_missing_domain.json")). + Build(t) err := client.RemoveRecord(t.Context(), "123", "example.com") - require.Error(t, err) + require.EqualError(t, err, "code: 400, message: missing domain") } diff --git a/providers/dns/njalla/internal/fixtures/add_record-request.json b/providers/dns/njalla/internal/fixtures/add_record-request.json new file mode 100644 index 000000000..a85e1aaf1 --- /dev/null +++ b/providers/dns/njalla/internal/fixtures/add_record-request.json @@ -0,0 +1,10 @@ +{ + "method": "add-record", + "params": { + "content": "foobar", + "domain": "test", + "name": "example.com", + "ttl": 300, + "type": "TXT" + } +} diff --git a/providers/dns/njalla/internal/fixtures/add_record.json b/providers/dns/njalla/internal/fixtures/add_record.json new file mode 100644 index 000000000..a537762bf --- /dev/null +++ b/providers/dns/njalla/internal/fixtures/add_record.json @@ -0,0 +1,12 @@ +{ + "id": "897", + "jsonrpc": "2.0", + "result": { + "id": "123", + "content": "foobar", + "domain": "test", + "name": "example.com", + "ttl": 300, + "type": "TXT" + } +} diff --git a/providers/dns/njalla/internal/fixtures/auth_error.json b/providers/dns/njalla/internal/fixtures/auth_error.json new file mode 100644 index 000000000..e9d07be51 --- /dev/null +++ b/providers/dns/njalla/internal/fixtures/auth_error.json @@ -0,0 +1,7 @@ +{ + "jsonrpc": "2.0", + "Error": { + "code": 403, + "message": "Invalid token." + } +} diff --git a/providers/dns/njalla/internal/fixtures/list_records-request.json b/providers/dns/njalla/internal/fixtures/list_records-request.json new file mode 100644 index 000000000..ebe5ccf72 --- /dev/null +++ b/providers/dns/njalla/internal/fixtures/list_records-request.json @@ -0,0 +1,6 @@ +{ + "method": "list-records", + "params": { + "domain": "example.com" + } +} diff --git a/providers/dns/njalla/internal/fixtures/list_records.json b/providers/dns/njalla/internal/fixtures/list_records.json new file mode 100644 index 000000000..a280a4b3f --- /dev/null +++ b/providers/dns/njalla/internal/fixtures/list_records.json @@ -0,0 +1,24 @@ +{ + "id": "897", + "jsonrpc": "2.0", + "result": { + "records": [ + { + "id": "1", + "content": "test", + "domain": "example.com", + "name": "test01", + "ttl": 300, + "type": "TXT" + }, + { + "id": "2", + "content": "txtTxt", + "domain": "example.com", + "name": "test02", + "ttl": 120, + "type": "TXT" + } + ] + } +} diff --git a/providers/dns/njalla/internal/fixtures/remove_record-request.json b/providers/dns/njalla/internal/fixtures/remove_record-request.json new file mode 100644 index 000000000..c96e94423 --- /dev/null +++ b/providers/dns/njalla/internal/fixtures/remove_record-request.json @@ -0,0 +1,7 @@ +{ + "method": "remove-record", + "params": { + "id": "123", + "domain": "example.com" + } +} diff --git a/providers/dns/njalla/internal/fixtures/remove_record_error_missing_domain.json b/providers/dns/njalla/internal/fixtures/remove_record_error_missing_domain.json new file mode 100644 index 000000000..f65d254d0 --- /dev/null +++ b/providers/dns/njalla/internal/fixtures/remove_record_error_missing_domain.json @@ -0,0 +1,7 @@ +{ + "jsonrpc": "2.0", + "Error": { + "code": 400, + "message": "missing domain" + } +} diff --git a/providers/dns/njalla/internal/fixtures/remove_record_error_missing_id.json b/providers/dns/njalla/internal/fixtures/remove_record_error_missing_id.json new file mode 100644 index 000000000..544cd4d1c --- /dev/null +++ b/providers/dns/njalla/internal/fixtures/remove_record_error_missing_id.json @@ -0,0 +1,7 @@ +{ + "jsonrpc": "2.0", + "Error": { + "code": 400, + "message": "missing ID" + } +} diff --git a/providers/dns/otc/internal/client_test.go b/providers/dns/otc/internal/client_test.go new file mode 100644 index 000000000..ea3835a56 --- /dev/null +++ b/providers/dns/otc/internal/client_test.go @@ -0,0 +1,109 @@ +package internal + +import ( + "context" + "net/http/httptest" + "net/url" + "strconv" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder( + func(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret", "example.com", "test") + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) + + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(), + ) +} + +func TestClient_GetZoneID(t *testing.T) { + client := mockBuilder(). + Route("GET /zones", + servermock.ResponseFromFixture("zones_GET.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com.")). + Build(t) + + zoneID, err := client.GetZoneID(context.Background(), "example.com.") + require.NoError(t, err) + + assert.Equal(t, "123123", zoneID) +} + +func TestClient_GetZoneID_error(t *testing.T) { + client := mockBuilder(). + Route("GET /zones", + servermock.ResponseFromFixture("zones_GET_empty.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com.")). + Build(t) + + _, err := client.GetZoneID(context.Background(), "example.com.") + require.EqualError(t, err, "zone example.com. not found") +} + +func TestClient_GetRecordSetID(t *testing.T) { + client := mockBuilder(). + Route("GET /zones/123123/recordsets", + servermock.ResponseFromFixture("zones-recordsets_GET.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com."). + With("type", "TXT"), + ). + Build(t) + + recordSetID, err := client.GetRecordSetID(context.Background(), "123123", "example.com.") + require.NoError(t, err) + + assert.Equal(t, "321321", recordSetID) +} + +func TestClient_GetRecordSetID_error(t *testing.T) { + client := mockBuilder(). + Route("GET /zones/123123/recordsets", + servermock.ResponseFromFixture("zones-recordsets_GET_empty.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com."). + With("type", "TXT"), + ). + Build(t) + + _, err := client.GetRecordSetID(context.Background(), "123123", "example.com.") + require.EqualError(t, err, "record not found") +} + +func TestClient_CreateRecordSet(t *testing.T) { + client := mockBuilder(). + Route("POST /zones/123123/recordsets", + servermock.ResponseFromFixture("zones-recordsets_POST.json")). + Build(t) + + rs := RecordSets{ + Name: "_acme-challenge.example.com.", + Description: "Added TXT record for ACME dns-01 challenge using lego client", + Type: "TXT", + TTL: 300, + Records: []string{strconv.Quote("w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI")}, + } + err := client.CreateRecordSet(context.Background(), "123123", rs) + require.NoError(t, err) +} + +func TestClient_DeleteRecordSet(t *testing.T) { + client := mockBuilder(). + Route("DELETE /zones/123123/recordsets/321321", + servermock.ResponseFromFixture("zones-recordsets_DELETE.json")). + Build(t) + + err := client.DeleteRecordSet(context.Background(), "123123", "321321") + require.NoError(t, err) +} diff --git a/providers/dns/otc/internal/identity_test.go b/providers/dns/otc/internal/identity_test.go index c8bda7027..4dce72afc 100644 --- a/providers/dns/otc/internal/identity_test.go +++ b/providers/dns/otc/internal/identity_test.go @@ -1,24 +1,36 @@ package internal import ( + "net/http/httptest" "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestClient_Login(t *testing.T) { - mock := NewDNSServerMock(t) - mock.HandleAuthSuccessfully() + var serverURL *url.URL - client := NewClient("user", "secret", "example.com", "test") - client.IdentityEndpoint, _ = url.JoinPath(mock.GetServerURL(), "/v3/auth/token") + client := servermock.NewBuilder( + func(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret", "example.com", "test") + client.HTTPClient = server.Client() + client.IdentityEndpoint = server.URL + "/v3/auth/token" + + serverURL, _ = url.Parse(server.URL) + + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(), + ). + Route("POST /v3/auth/token", IdentityHandlerMock()). + Build(t) err := client.Login(t.Context()) require.NoError(t, err) - serverURL, _ := url.Parse(mock.GetServerURL()) assert.Equal(t, serverURL.JoinPath("v2").String(), client.baseURL.String()) assert.Equal(t, fakeOTCToken, client.token) } diff --git a/providers/dns/otc/internal/mock.go b/providers/dns/otc/internal/mock.go index 2ed7f84de..46da61e4c 100644 --- a/providers/dns/otc/internal/mock.go +++ b/providers/dns/otc/internal/mock.go @@ -2,62 +2,13 @@ package internal import ( "fmt" - "io" "net/http" - "net/http/httptest" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) const fakeOTCToken = "62244bc21da68d03ebac94e6636ff01f" -func writeFixture(rw http.ResponseWriter, filename string) { - file, err := os.Open(filepath.Join("internal", "fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, _ = io.Copy(rw, file) -} - -// DNSServerMock mock. -type DNSServerMock struct { - t *testing.T - server *httptest.Server - mux *http.ServeMux -} - -// NewDNSServerMock create a new DNSServerMock. -func NewDNSServerMock(t *testing.T) *DNSServerMock { - t.Helper() - - mux := http.NewServeMux() - - return &DNSServerMock{ - t: t, - server: httptest.NewServer(mux), - mux: mux, - } -} - -func (m *DNSServerMock) GetServerURL() string { - return m.server.URL -} - -// ShutdownServer creates the mock server. -func (m *DNSServerMock) ShutdownServer() { - m.server.Close() -} - -// HandleAuthSuccessfully Handle auth successfully. -func (m *DNSServerMock) HandleAuthSuccessfully() { - m.mux.HandleFunc("/v3/auth/token", func(w http.ResponseWriter, _ *http.Request) { +func IdentityHandlerMock() http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { w.Header().Set("X-Subject-Token", fakeOTCToken) _, _ = fmt.Fprintf(w, `{ @@ -69,7 +20,7 @@ func (m *DNSServerMock) HandleAuthSuccessfully() { "name": "", "endpoints": [ { - "url": "%s", + "url": "http://%s", "region": "eu-de", "region_id": "eu-de", "interface": "public", @@ -78,87 +29,6 @@ func (m *DNSServerMock) HandleAuthSuccessfully() { ] } ] - }}`, m.server.URL) - }) -} - -// HandleListZonesSuccessfully Handle list zones successfully. -func (m *DNSServerMock) HandleListZonesSuccessfully() { - m.mux.HandleFunc("/v2/zones", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(m.t, http.MethodGet, r.Method) - assert.Equal(m.t, "/v2/zones", r.URL.Path) - assert.Equal(m.t, "name=example.com.", r.URL.RawQuery) - assert.Equal(m.t, "application/json", r.Header.Get("Accept")) - - writeFixture(w, "zones_GET.json") - }) -} - -// HandleListZonesEmpty Handle list zones empty. -func (m *DNSServerMock) HandleListZonesEmpty() { - m.mux.HandleFunc("/v2/zones", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(m.t, http.MethodGet, r.Method) - assert.Equal(m.t, "/v2/zones", r.URL.Path) - assert.Equal(m.t, "name=example.com.", r.URL.RawQuery) - assert.Equal(m.t, "application/json", r.Header.Get("Accept")) - - writeFixture(w, "zones_GET_empty.json") - }) -} - -// HandleDeleteRecordsetsSuccessfully Handle delete recordsets successfully. -func (m *DNSServerMock) HandleDeleteRecordsetsSuccessfully() { - m.mux.HandleFunc("/v2/zones/123123/recordsets/321321", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(m.t, http.MethodDelete, r.Method) - assert.Equal(m.t, "/v2/zones/123123/recordsets/321321", r.URL.Path) - assert.Equal(m.t, "application/json", r.Header.Get("Accept")) - - writeFixture(w, "zones-recordsets_DELETE.json") - }) -} - -// HandleListRecordsetsEmpty Handle list recordsets empty. -func (m *DNSServerMock) HandleListRecordsetsEmpty() { - m.mux.HandleFunc("/v2/zones/123123/recordsets", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(m.t, "/v2/zones/123123/recordsets", r.URL.Path) - assert.Equal(m.t, "name=_acme-challenge.example.com.&type=TXT", r.URL.RawQuery) - - writeFixture(w, "zones-recordsets_GET_empty.json") - }) -} - -// HandleListRecordsetsSuccessfully Handle list recordsets successfully. -func (m *DNSServerMock) HandleListRecordsetsSuccessfully() { - m.mux.HandleFunc("/v2/zones/123123/recordsets", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(m.t, "application/json", r.Header.Get("Accept")) - - if r.Method == http.MethodGet { - assert.Equal(m.t, "/v2/zones/123123/recordsets", r.URL.Path) - assert.Equal(m.t, "name=_acme-challenge.example.com.&type=TXT", r.URL.RawQuery) - - writeFixture(w, "zones-recordsets_GET.json") - return - } - - if r.Method == http.MethodPost { - assert.Equal(m.t, "application/json", r.Header.Get("Content-Type")) - - raw, err := io.ReadAll(r.Body) - require.NoError(m.t, err) - exceptedString := `{ - "name": "_acme-challenge.example.com.", - "description": "Added TXT record for ACME dns-01 challenge using lego client", - "type": "TXT", - "ttl": 300, - "records": ["\"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI\""] - }` - - assert.JSONEq(m.t, exceptedString, string(raw)) - - writeFixture(w, "zones-recordsets_POST.json") - return - } - - http.Error(w, fmt.Sprintf("Expected method to be 'GET' or 'POST' but got '%s'", r.Method), http.StatusBadRequest) - }) + }}`, req.Context().Value(http.LocalAddrContextKey)) + } } diff --git a/providers/dns/otc/otc_test.go b/providers/dns/otc/otc_test.go index 54907b69e..1e53f31cc 100644 --- a/providers/dns/otc/otc_test.go +++ b/providers/dns/otc/otc_test.go @@ -2,129 +2,296 @@ package otc import ( "fmt" - "os" + "net/http/httptest" + "path" "testing" + "time" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/go-acme/lego/v4/providers/dns/otc/internal" - "github.com/stretchr/testify/suite" + "github.com/stretchr/testify/require" ) -type OTCSuite struct { - suite.Suite +const envDomain = envNamespace + "DOMAIN" - mock *internal.DNSServerMock - envTest *tester.EnvTest +var envTest = tester.NewEnvTest( + EnvDomainName, + EnvUserName, + EnvPassword, + EnvProjectName, + EnvIdentityEndpoint). + WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvDomainName: "example.com", + EnvUserName: "user", + EnvPassword: "secret", + EnvProjectName: "test", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{ + EnvDomainName: "", + EnvUserName: "", + EnvPassword: "", + EnvProjectName: "", + }, + expected: "otc: some credentials information are missing: OTC_DOMAIN_NAME,OTC_USER_NAME,OTC_PASSWORD,OTC_PROJECT_NAME", + }, + { + desc: "missing domain name", + envVars: map[string]string{ + EnvDomainName: "", + EnvUserName: "user", + EnvPassword: "secret", + EnvProjectName: "test", + }, + expected: "otc: some credentials information are missing: OTC_DOMAIN_NAME", + }, + { + desc: "missing username", + envVars: map[string]string{ + EnvDomainName: "example.com", + EnvUserName: "", + EnvPassword: "secret", + EnvProjectName: "test", + }, + expected: "otc: some credentials information are missing: OTC_USER_NAME", + }, + { + desc: "missing password", + envVars: map[string]string{ + EnvDomainName: "example.com", + EnvUserName: "user", + EnvPassword: "", + EnvProjectName: "test", + }, + expected: "otc: some credentials information are missing: OTC_PASSWORD", + }, + { + desc: "missing project name", + envVars: map[string]string{ + EnvDomainName: "example.com", + EnvUserName: "user", + EnvPassword: "secret", + EnvProjectName: "", + }, + expected: "otc: some credentials information are missing: OTC_PROJECT_NAME", + }, + } + + 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) + } else { + require.EqualError(t, err, test.expected) + } + }) + } } -func (s *OTCSuite) SetupTest() { - s.mock = internal.NewDNSServerMock(s.T()) - s.mock.HandleAuthSuccessfully() - s.envTest = tester.NewEnvTest( - EnvDomainName, - EnvUserName, - EnvPassword, - EnvProjectName, - EnvIdentityEndpoint, - ) +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + domainName string + projectName string + username string + password string + expected string + }{ + { + desc: "success", + domainName: "example.com", + projectName: "test", + username: "user", + password: "secret", + }, + { + desc: "missing credentials", + expected: "otc: credentials missing", + }, + { + desc: "missing domain name", + domainName: "", + projectName: "test", + username: "user", + password: "secret", + expected: "otc: credentials missing", + }, + { + desc: "missing project name", + domainName: "example.com", + projectName: "", + username: "user", + password: "secret", + expected: "otc: credentials missing", + }, + { + desc: "missing username", + domainName: "example.com", + projectName: "test", + username: "", + password: "secret", + expected: "otc: credentials missing", + }, + { + desc: "missing password ", + domainName: "example.com", + projectName: "test", + username: "user", + password: "", + expected: "otc: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.DomainName = test.domainName + config.ProjectName = test.projectName + config.UserName = test.username + config.Password = test.password + + p, err := NewDNSProviderConfig(config) + + if test.expected == "" { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + } else { + require.EqualError(t, err, test.expected) + } + }) + } } -func (s *OTCSuite) TearDownTest() { - s.envTest.RestoreEnv() - s.mock.ShutdownServer() -} - -func TestTestSuite(t *testing.T) { - suite.Run(t, new(OTCSuite)) -} - -func (s *OTCSuite) createDNSProvider() (*DNSProvider, error) { - config := NewDefaultConfig() - config.UserName = "UserName" - config.Password = "Password" - config.DomainName = "DomainName" - config.ProjectName = "ProjectName" - config.IdentityEndpoint = fmt.Sprintf("%s/v3/auth/token", s.mock.GetServerURL()) - - return NewDNSProviderConfig(config) -} - -func (s *OTCSuite) TestLoginEnv() { - s.envTest.ClearEnv() - - s.envTest.Apply(map[string]string{ - EnvDomainName: "unittest1", - EnvUserName: "unittest2", - EnvPassword: "unittest3", - EnvProjectName: "unittest4", - EnvIdentityEndpoint: "unittest5", - }) +func TestLivePresent(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + envTest.RestoreEnv() provider, err := NewDNSProvider() - s.Require().NoError(err) + require.NoError(t, err) - s.Equal("unittest1", provider.config.DomainName) - s.Equal("unittest2", provider.config.UserName) - s.Equal("unittest3", provider.config.Password) - s.Equal("unittest4", provider.config.ProjectName) - s.Equal("unittest5", provider.config.IdentityEndpoint) - - os.Setenv(EnvIdentityEndpoint, "") - - provider, err = NewDNSProvider() - s.Require().NoError(err) - - s.Equal("https://iam.eu-de.otc.t-systems.com:443/v3/auth/tokens", provider.config.IdentityEndpoint) + err = provider.Present(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) } -func (s *OTCSuite) TestLoginEnvEmpty() { - s.envTest.ClearEnv() +func TestLiveCleanUp(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } - _, err := NewDNSProvider() - s.EqualError(err, "otc: some credentials information are missing: OTC_DOMAIN_NAME,OTC_USER_NAME,OTC_PASSWORD,OTC_PROJECT_NAME") + envTest.RestoreEnv() + provider, err := NewDNSProvider() + require.NoError(t, err) + + time.Sleep(1 * time.Second) + + err = provider.CleanUp(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) } -func (s *OTCSuite) TestDNSProvider_Present() { - s.mock.HandleListZonesSuccessfully() - s.mock.HandleListRecordsetsSuccessfully() +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("GET /v2/zones", + responseFromFixture("zones_GET.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com.")). + Route("/", servermock.DumpRequest()). + Build(t) - provider, err := s.createDNSProvider() - s.Require().NoError(err) - - err = provider.Present("example.com", "", "foobar") - s.Require().NoError(err) + err := provider.Present("example.com", "", "123d==") + require.NoError(t, err) } -func (s *OTCSuite) TestDNSProvider_Present_EmptyZone() { - s.mock.HandleListZonesEmpty() - s.mock.HandleListRecordsetsSuccessfully() +func TestDNSProvider_Present_emptyZone(t *testing.T) { + provider := mockBuilder(). + Route("GET /v2/zones", + responseFromFixture("zones_GET_empty.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com.")). + Route("/", servermock.DumpRequest()). + Build(t) - provider, err := s.createDNSProvider() - s.Require().NoError(err) - - err = provider.Present("example.com", "", "foobar") - s.Error(err) + err := provider.Present("example.com", "", "123d==") + require.EqualError(t, err, "otc: unable to get zone: zone example.com. not found") } -func (s *OTCSuite) TestDNSProvider_CleanUp() { - s.mock.HandleListZonesSuccessfully() - s.mock.HandleListRecordsetsSuccessfully() - s.mock.HandleDeleteRecordsetsSuccessfully() +func TestDNSProvider_Cleanup(t *testing.T) { + provider := mockBuilder(). + Route("GET /v2/zones", + responseFromFixture("zones_GET.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com.")). + Route("GET /v2/zones/123123/recordsets", + responseFromFixture("zones-recordsets_GET.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "_acme-challenge.example.com."). + With("type", "TXT")). + Route("DELETE /v2/zones/123123/recordsets/321321", + responseFromFixture("zones-recordsets_DELETE.json")). + Build(t) - provider, err := s.createDNSProvider() - s.Require().NoError(err) - - err = provider.CleanUp("example.com", "", "foobar") - s.Require().NoError(err) + err := provider.CleanUp("example.com", "", "123d==") + require.NoError(t, err) } -func (s *OTCSuite) TestDNSProvider_CleanUp_EmptyRecordset() { - s.mock.HandleListZonesSuccessfully() - s.mock.HandleListRecordsetsEmpty() +func TestDNSProvider_Cleanup_emptyRecordset(t *testing.T) { + provider := mockBuilder(). + Route("GET /v2/zones", + responseFromFixture("zones_GET.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com.")). + Route("GET /v2/zones/123123/recordsets", + responseFromFixture("zones-recordsets_GET_empty.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "_acme-challenge.example.com."). + With("type", "TXT")). + Build(t) - provider, err := s.createDNSProvider() - s.Require().NoError(err) - - err = provider.CleanUp("example.com", "", "foobar") - s.Require().Error(err) + err := provider.CleanUp("example.com", "", "123d==") + require.EqualError(t, err, "otc: unable to get record _acme-challenge.example.com. for zone example.com: record not found") +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.UserName = "user" + config.Password = "secret" + config.DomainName = "example.com" + config.ProjectName = "test" + config.IdentityEndpoint = fmt.Sprintf("%s/v3/auth/token", server.URL) + + return NewDNSProviderConfig(config) + }, + servermock.CheckHeader().WithJSONHeaders(), + ). + Route("POST /v3/auth/token", internal.IdentityHandlerMock()) +} + +func responseFromFixture(filename string) *servermock.ResponseFromFileHandler { + return servermock.ResponseFromFile(path.Join("internal", "fixtures", filename)) } diff --git a/providers/dns/pdns/internal/client.go b/providers/dns/pdns/internal/client.go index bc525c578..f6b55d5de 100644 --- a/providers/dns/pdns/internal/client.go +++ b/providers/dns/pdns/internal/client.go @@ -18,6 +18,9 @@ import ( "github.com/miekg/dns" ) +// APIKeyHeader API key header. +const APIKeyHeader = "X-Api-Key" + // Client the PowerDNS API client. type Client struct { serverName string @@ -163,7 +166,7 @@ func (c *Client) joinPath(elem ...string) *url.URL { } func (c *Client) do(req *http.Request) (json.RawMessage, error) { - req.Header.Set("X-API-Key", c.apiKey) + req.Header.Set(APIKeyHeader, c.apiKey) resp, err := c.HTTPClient.Do(req) if err != nil { diff --git a/providers/dns/pdns/internal/client_test.go b/providers/dns/pdns/internal/client_test.go index d3919ace3..6d1c48852 100644 --- a/providers/dns/pdns/internal/client_test.go +++ b/providers/dns/pdns/internal/client_test.go @@ -1,65 +1,27 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, method, pattern string, status int, file string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + serverURL, _ := url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client := NewClient(serverURL, "server", 0, "secret") + client.HTTPClient = server.Client() - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusBadRequest) - return - } - - apiKey := req.Header.Get("X-API-Key") - if apiKey != "secret" { - http.Error(rw, fmt.Sprintf("invalid credentials: %s", apiKey), http.StatusBadRequest) - return - } - - if file == "" { - rw.WriteHeader(status) - return - } - - open, err := os.Open(filepath.Join("fixtures", file)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - serverURL, _ := url.Parse(server.URL) - - client := NewClient(serverURL, "server", 0, "secret") - client.HTTPClient = server.Client() - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders().With(APIKeyHeader, "secret")) } func TestClient_joinPath(t *testing.T) { @@ -159,7 +121,11 @@ func TestClient_joinPath(t *testing.T) { } func TestClient_GetHostedZone(t *testing.T) { - client := setupTest(t, http.MethodGet, "/api/v1/servers/server/zones/example.org.", http.StatusOK, "zone.json") + client := mockBuilder(). + Route("GET /api/v1/servers/server/zones/example.org.", + servermock.ResponseFromFixture("zone.json")). + Build(t) + client.apiVersion = 1 zone, err := client.GetHostedZone(t.Context(), "example.org.") @@ -202,7 +168,12 @@ func TestClient_GetHostedZone(t *testing.T) { } func TestClient_GetHostedZone_error(t *testing.T) { - client := setupTest(t, http.MethodGet, "/api/v1/servers/server/zones/example.org.", http.StatusUnprocessableEntity, "error.json") + client := mockBuilder(). + Route("GET /api/v1/servers/server/zones/example.org.", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnprocessableEntity)). + Build(t) + client.apiVersion = 1 _, err := client.GetHostedZone(t.Context(), "example.org.") @@ -210,7 +181,11 @@ func TestClient_GetHostedZone_error(t *testing.T) { } func TestClient_GetHostedZone_v0(t *testing.T) { - client := setupTest(t, http.MethodGet, "/servers/server/zones/example.org.", http.StatusOK, "zone.json") + client := mockBuilder(). + Route("GET /servers/server/zones/example.org.", + servermock.ResponseFromFixture("zone.json")). + Build(t) + client.apiVersion = 0 zone, err := client.GetHostedZone(t.Context(), "example.org.") @@ -253,7 +228,12 @@ func TestClient_GetHostedZone_v0(t *testing.T) { } func TestClient_UpdateRecords(t *testing.T) { - client := setupTest(t, http.MethodPatch, "/api/v1/servers/localhost/zones/example.org.", http.StatusOK, "zone.json") + client := mockBuilder(). + Route("PATCH /api/v1/servers/localhost/zones/example.org.", + servermock.ResponseFromFixture("zone.json"), + servermock.CheckRequestJSONBodyFromFile("zone-request.json")). + Build(t) + client.apiVersion = 1 client.serverName = "localhost" @@ -283,7 +263,12 @@ func TestClient_UpdateRecords(t *testing.T) { } func TestClient_UpdateRecords_NonRootApi(t *testing.T) { - client := setupTest(t, http.MethodPatch, "/some/path/api/v1/servers/localhost/zones/example.org.", http.StatusOK, "zone.json") + client := mockBuilder(). + Route("PATCH /some/path/api/v1/servers/localhost/zones/example.org.", + servermock.ResponseFromFixture("zone.json"), + servermock.CheckRequestJSONBodyFromFile("zone-request.json")). + Build(t) + client.Host = client.Host.JoinPath("some", "path") client.apiVersion = 1 client.serverName = "localhost" @@ -314,7 +299,12 @@ func TestClient_UpdateRecords_NonRootApi(t *testing.T) { } func TestClient_UpdateRecords_v0(t *testing.T) { - client := setupTest(t, http.MethodPatch, "/servers/localhost/zones/example.org.", http.StatusOK, "zone.json") + client := mockBuilder(). + Route("PATCH /servers/localhost/zones/example.org.", + servermock.ResponseFromFixture("zone.json"), + servermock.CheckRequestJSONBodyFromFile("zone-request.json")). + Build(t) + client.apiVersion = 0 client.serverName = "localhost" @@ -344,7 +334,10 @@ func TestClient_UpdateRecords_v0(t *testing.T) { } func TestClient_Notify(t *testing.T) { - client := setupTest(t, http.MethodPut, "/api/v1/servers/localhost/zones/example.org./notify", http.StatusOK, "") + client := mockBuilder(). + Route("PUT /api/v1/servers/localhost/zones/example.org./notify", nil). + Build(t) + client.apiVersion = 1 client.serverName = "localhost" @@ -360,7 +353,10 @@ func TestClient_Notify(t *testing.T) { } func TestClient_Notify_NonRootApi(t *testing.T) { - client := setupTest(t, http.MethodPut, "/some/path/api/v1/servers/localhost/zones/example.org./notify", http.StatusOK, "") + client := mockBuilder(). + Route("PUT /some/path/api/v1/servers/localhost/zones/example.org./notify", nil). + Build(t) + client.Host = client.Host.JoinPath("some", "path") client.apiVersion = 1 client.serverName = "localhost" @@ -377,7 +373,10 @@ func TestClient_Notify_NonRootApi(t *testing.T) { } func TestClient_Notify_v0(t *testing.T) { - client := setupTest(t, http.MethodPut, "/api/v1/servers/localhost/zones/example.org./notify", http.StatusOK, "") + client := mockBuilder(). + Route("PUT /some/path/api/v1/servers/localhost/zones/example.org./notify", nil). + Build(t) + client.apiVersion = 0 zone := &HostedZone{ @@ -392,7 +391,10 @@ func TestClient_Notify_v0(t *testing.T) { } func TestClient_getAPIVersion(t *testing.T) { - client := setupTest(t, http.MethodGet, "/api", http.StatusOK, "versions.json") + client := mockBuilder(). + Route("GET /api", + servermock.ResponseFromFixture("versions.json")). + Build(t) version, err := client.getAPIVersion(t.Context()) require.NoError(t, err) diff --git a/providers/dns/pdns/internal/fixtures/zone-request.json b/providers/dns/pdns/internal/fixtures/zone-request.json new file mode 100644 index 000000000..5e4a6d2b9 --- /dev/null +++ b/providers/dns/pdns/internal/fixtures/zone-request.json @@ -0,0 +1,19 @@ +{ + "rrsets": [ + { + "name": "example.org.", + "type": "NS", + "kind": "", + "changetype": "REPLACE", + "records": [ + { + "content": "192.0.2.5", + "disabled": false, + "name": "ns1.example.org.", + "type": "A", + "ttl": 86400 + } + ] + } + ] +} diff --git a/providers/dns/plesk/internal/client_test.go b/providers/dns/plesk/internal/client_test.go index b61bce4c2..14cadd0e0 100644 --- a/providers/dns/plesk/internal/client_test.go +++ b/providers/dns/plesk/internal/client_test.go @@ -1,69 +1,35 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, filename string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + serverURL, _ := url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client := NewClient(serverURL, "user", "secret") + client.HTTPClient = server.Client() - serverURL, err := url.Parse(server.URL) - require.NoError(t, err) - - client := NewClient(serverURL, "user", "secret") - client.HTTPClient = server.Client() - - mux.HandleFunc("/enterprise/control/agent.php", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - login := req.Header.Get("Http_auth_login") - if login != "user" { - http.Error(rw, fmt.Sprintf("invalid login: %s", login), http.StatusUnauthorized) - return - } - - password := req.Header.Get("Http_auth_passwd") - if password != "secret" { - http.Error(rw, fmt.Sprintf("invalid password: %s", password), http.StatusUnauthorized) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - return client + return client, nil + }, + servermock.CheckHeader().WithContentType("text/xml"). + With("Http_auth_login", "user"). + With("Http_auth_passwd", "secret"), + ) } func TestClient_GetSite(t *testing.T) { - client := setupTest(t, "get-site.xml") + client := mockBuilder(). + Route("POST /enterprise/control/agent.php", servermock.ResponseFromFixture("get-site.xml")). + Build(t) siteID, err := client.GetSite(t.Context(), "example.com") require.NoError(t, err) @@ -72,7 +38,9 @@ func TestClient_GetSite(t *testing.T) { } func TestClient_GetSite_error(t *testing.T) { - client := setupTest(t, "get-site-error.xml") + client := mockBuilder(). + Route("POST /enterprise/control/agent.php", servermock.ResponseFromFixture("get-site-error.xml")). + Build(t) siteID, err := client.GetSite(t.Context(), "example.com") require.Error(t, err) @@ -81,7 +49,9 @@ func TestClient_GetSite_error(t *testing.T) { } func TestClient_GetSite_system_error(t *testing.T) { - client := setupTest(t, "global-error.xml") + client := mockBuilder(). + Route("POST /enterprise/control/agent.php", servermock.ResponseFromFixture("global-error.xml")). + Build(t) siteID, err := client.GetSite(t.Context(), "example.com") require.Error(t, err) @@ -90,7 +60,9 @@ func TestClient_GetSite_system_error(t *testing.T) { } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, "add-record.xml") + client := mockBuilder(). + Route("POST /enterprise/control/agent.php", servermock.ResponseFromFixture("add-record.xml")). + Build(t) recordID, err := client.AddRecord(t.Context(), 123, "_acme-challenge.example.com", "txtTXTtxt") require.NoError(t, err) @@ -99,7 +71,9 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, "add-record-error.xml") + client := mockBuilder(). + Route("POST /enterprise/control/agent.php", servermock.ResponseFromFixture("add-record-error.xml")). + Build(t) recordID, err := client.AddRecord(t.Context(), 123, "_acme-challenge.example.com", "txtTXTtxt") require.ErrorAs(t, err, new(RecResult)) @@ -108,7 +82,9 @@ func TestClient_AddRecord_error(t *testing.T) { } func TestClient_AddRecord_system_error(t *testing.T) { - client := setupTest(t, "global-error.xml") + client := mockBuilder(). + Route("POST /enterprise/control/agent.php", servermock.ResponseFromFixture("global-error.xml")). + Build(t) recordID, err := client.AddRecord(t.Context(), 123, "_acme-challenge.example.com", "txtTXTtxt") require.ErrorAs(t, err, new(*System)) @@ -117,7 +93,9 @@ func TestClient_AddRecord_system_error(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, "delete-record.xml") + client := mockBuilder(). + Route("POST /enterprise/control/agent.php", servermock.ResponseFromFixture("delete-record.xml")). + Build(t) recordID, err := client.DeleteRecord(t.Context(), 4537) require.NoError(t, err) @@ -126,7 +104,9 @@ func TestClient_DeleteRecord(t *testing.T) { } func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, "delete-record-error.xml") + client := mockBuilder(). + Route("POST /enterprise/control/agent.php", servermock.ResponseFromFixture("delete-record-error.xml")). + Build(t) recordID, err := client.DeleteRecord(t.Context(), 4537) require.ErrorAs(t, err, new(RecResult)) @@ -135,7 +115,9 @@ func TestClient_DeleteRecord_error(t *testing.T) { } func TestClient_DeleteRecord_system_error(t *testing.T) { - client := setupTest(t, "global-error.xml") + client := mockBuilder(). + Route("POST /enterprise/control/agent.php", servermock.ResponseFromFixture("global-error.xml")). + Build(t) recordID, err := client.DeleteRecord(t.Context(), 4537) require.ErrorAs(t, err, new(*System)) diff --git a/providers/dns/rackspace/fixtures/delete.json b/providers/dns/rackspace/fixtures/delete.json new file mode 100644 index 000000000..7e2f2ac53 --- /dev/null +++ b/providers/dns/rackspace/fixtures/delete.json @@ -0,0 +1,7 @@ +{ + "status": "RUNNING", + "verb": "DELETE", + "jobId": "00000000-0000-0000-0000-0000000000", + "callbackUrl": "https://dns.api.rackspacecloud.com/v1.0/123456/status/00000000-0000-0000-0000-0000000000", + "requestUrl": "https://dns.api.rackspacecloud.com/v1.0/123456/domains/112233/recordsid=TXT-654321" +} diff --git a/providers/dns/rackspace/fixtures/identity.json b/providers/dns/rackspace/fixtures/identity.json new file mode 100644 index 000000000..5a459d13c --- /dev/null +++ b/providers/dns/rackspace/fixtures/identity.json @@ -0,0 +1,31 @@ +{ + "access": { + "token": { + "id": "testToken", + "expires": "1970-01-01T00:00:00.000Z", + "tenant": { + "id": "123456", + "name": "123456" + }, + "RAX-AUTH:authenticatedBy": [ + "APIKEY" + ] + }, + "serviceCatalog": [ + { + "type": "rax:dns", + "endpoints": [ + { + "publicURL": "https://dns.api.rackspacecloud.com/v1.0/123456", + "tenantId": "123456" + } + ], + "name": "cloudDNS" + } + ], + "user": { + "id": "fakeUseID", + "name": "testUser" + } + } +} diff --git a/providers/dns/rackspace/fixtures/record.json b/providers/dns/rackspace/fixtures/record.json new file mode 100644 index 000000000..4d76aa0c8 --- /dev/null +++ b/providers/dns/rackspace/fixtures/record.json @@ -0,0 +1,8 @@ +{ + "request": "{\"records\":[{\"name\":\"_acme-challenge.example.com\",\"type\":\"TXT\",\"data\":\"pW9ZKG0xz_PCriK-nCMOjADy9eJcgGWIzkkj2fN4uZM\",\"ttl\":300}]}", + "status": "RUNNING", + "verb": "POST", + "jobId": "00000000-0000-0000-0000-0000000000", + "callbackUrl": "https://dns.api.rackspacecloud.com/v1.0/123456/status/00000000-0000-0000-0000-0000000000", + "requestUrl": "https://dns.api.rackspacecloud.com/v1.0/123456/domains/112233/records" +} diff --git a/providers/dns/rackspace/fixtures/record_details.json b/providers/dns/rackspace/fixtures/record_details.json new file mode 100644 index 000000000..e53cf1330 --- /dev/null +++ b/providers/dns/rackspace/fixtures/record_details.json @@ -0,0 +1,13 @@ +{ + "records": [ + { + "name": "_acme-challenge.example.com", + "id": "TXT-654321", + "type": "TXT", + "data": "pW9ZKG0xz_PCriK-nCMOjADy9eJcgGWIzkkj2fN4uZM", + "ttl": 300, + "updated": "1970-01-01T00:00:00.000+0000", + "created": "1970-01-01T00:00:00.000+0000" + } + ] +} diff --git a/providers/dns/rackspace/fixtures/zone_details.json b/providers/dns/rackspace/fixtures/zone_details.json new file mode 100644 index 000000000..f68f23aa0 --- /dev/null +++ b/providers/dns/rackspace/fixtures/zone_details.json @@ -0,0 +1,12 @@ +{ + "domains": [ + { + "name": "example.com", + "id": "112233", + "emailAddress": "hostmaster@example.com", + "updated": "1970-01-01T00:00:00.000+0000", + "created": "1970-01-01T00:00:00.000+0000" + } + ], + "totalEntries": 1 +} diff --git a/providers/dns/rackspace/internal/client.go b/providers/dns/rackspace/internal/client.go index de25f8d0e..076409ebd 100644 --- a/providers/dns/rackspace/internal/client.go +++ b/providers/dns/rackspace/internal/client.go @@ -14,6 +14,8 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) +const AuthToken = "X-Auth-Token" + type Client struct { token string @@ -34,7 +36,7 @@ func NewClient(endpoint, token string) (*Client, error) { }, nil } -// AddRecord Adds one record to a specified domain. +// AddRecord Adds one record to a specified domain. // https://docs.rackspace.com/docs/cloud-dns/v1/api-reference/records#add-records func (c *Client) AddRecord(ctx context.Context, zoneID string, record Record) error { endpoint := c.baseURL.JoinPath("domains", zoneID, "records") @@ -161,7 +163,7 @@ func (c *Client) searchRecords(ctx context.Context, zoneID, recordName, recordTy } func (c *Client) do(req *http.Request, result any) error { - req.Header.Set("X-Auth-Token", c.token) + req.Header.Set(AuthToken, c.token) resp, err := c.HTTPClient.Do(req) if err != nil { diff --git a/providers/dns/rackspace/internal/client_test.go b/providers/dns/rackspace/internal/client_test.go index ce25d107c..c14c4d360 100644 --- a/providers/dns/rackspace/internal/client_test.go +++ b/providers/dns/rackspace/internal/client_test.go @@ -1,78 +1,62 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, handler http.HandlerFunc) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(server.URL, "secret") + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client.HTTPClient = server.Client() - client, err := NewClient(server.URL, "secret") - require.NoError(t, err) - - client.HTTPClient = server.Client() - - mux.HandleFunc(pattern, handler) - - return client -} - -func writeFixtureHandler(method, filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - if req.Header.Get("X-Auth-Token") != "secret" { - http.Error(rw, fmt.Sprintf("invalid token: %q", req.Header.Get("X-Auth-Token")), http.StatusUnauthorized) - return - } - - if filename == "" { - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, _ = io.Copy(rw, file) - } + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + With(AuthToken, "secret")) } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, "/domains/1234/records", writeFixtureHandler(http.MethodPost, "add-records.json")) + client := mockBuilder(). + Route("POST /domains/1234/records", + servermock.ResponseFromFixture("add-records.json"), + servermock.CheckRequestJSONBody(`{"records":[{"name":"exmaple.com","type":"TXT","data":"value1","ttl":120,"id":"abc"}]}`)). + Build(t) - err := client.AddRecord(t.Context(), "1234", Record{}) + record := Record{ + Name: "exmaple.com", + Type: "TXT", + Data: "value1", + TTL: 120, + ID: "abc", + } + + err := client.AddRecord(t.Context(), "1234", record) require.NoError(t, err) } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, "/domains/1234/records", writeFixtureHandler(http.MethodDelete, "")) + client := mockBuilder(). + Route("DELETE /domains/1234/records", nil). + Build(t) err := client.DeleteRecord(t.Context(), "1234", "2725233") require.NoError(t, err) } func TestClient_searchRecords(t *testing.T) { - client := setupTest(t, "/domains/1234/records", writeFixtureHandler(http.MethodGet, "search-records.json")) + client := mockBuilder(). + Route("GET /domains/1234/records", servermock.ResponseFromFixture("search-records.json")). + Build(t) records, err := client.searchRecords(t.Context(), "1234", "2725233", "A") require.NoError(t, err) @@ -93,7 +77,9 @@ func TestClient_searchRecords(t *testing.T) { } func TestClient_listDomainsByName(t *testing.T) { - client := setupTest(t, "/domains", writeFixtureHandler(http.MethodGet, "list-domains-by-name.json")) + client := mockBuilder(). + Route("GET /domains", servermock.ResponseFromFixture("list-domains-by-name.json")). + Build(t) domains, err := client.listDomainsByName(t.Context(), "1234") require.NoError(t, err) diff --git a/providers/dns/rackspace/internal/identity_test.go b/providers/dns/rackspace/internal/identity_test.go index b976fdd2f..44a8d75fc 100644 --- a/providers/dns/rackspace/internal/identity_test.go +++ b/providers/dns/rackspace/internal/identity_test.go @@ -1,48 +1,22 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func writeIdentityFixtureHandler(method, filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - if filename == "" { - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, _ = io.Copy(rw, file) - } +func setupIdentifier(server *httptest.Server) (*Identifier, error) { + return NewIdentifier(server.Client(), server.URL), nil } func TestIdentifier_Login(t *testing.T) { - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - identifier := NewIdentifier(server.Client(), server.URL) - - mux.HandleFunc("/", writeIdentityFixtureHandler(http.MethodPost, "tokens.json")) + identifier := servermock.NewBuilder[*Identifier](setupIdentifier, servermock.CheckHeader().WithJSONHeaders()). + Route("POST /", servermock.ResponseFromFixture("tokens.json")). + Build(t) identity, err := identifier.Login(t.Context(), "user", "secret") require.NoError(t, err) diff --git a/providers/dns/rackspace/rackspace_mock_test.go b/providers/dns/rackspace/rackspace_mock_test.go deleted file mode 100644 index 790d52498..000000000 --- a/providers/dns/rackspace/rackspace_mock_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package rackspace - -const recordDeleteMock = ` -{ - "status": "RUNNING", - "verb": "DELETE", - "jobId": "00000000-0000-0000-0000-0000000000", - "callbackUrl": "https://dns.api.rackspacecloud.com/v1.0/123456/status/00000000-0000-0000-0000-0000000000", - "requestUrl": "https://dns.api.rackspacecloud.com/v1.0/123456/domains/112233/recordsid=TXT-654321" -} -` - -const recordDetailsMock = ` -{ - "records": [ - { - "name": "_acme-challenge.example.com", - "id": "TXT-654321", - "type": "TXT", - "data": "pW9ZKG0xz_PCriK-nCMOjADy9eJcgGWIzkkj2fN4uZM", - "ttl": 300, - "updated": "1970-01-01T00:00:00.000+0000", - "created": "1970-01-01T00:00:00.000+0000" - } - ] -} -` - -const zoneDetailsMock = ` -{ - "domains": [ - { - "name": "example.com", - "id": "112233", - "emailAddress": "hostmaster@example.com", - "updated": "1970-01-01T00:00:00.000+0000", - "created": "1970-01-01T00:00:00.000+0000" - } - ], - "totalEntries": 1 -} -` - -const identityResponseMock = ` -{ - "access": { - "token": { - "id": "testToken", - "expires": "1970-01-01T00:00:00.000Z", - "tenant": { - "id": "123456", - "name": "123456" - }, - "RAX-AUTH:authenticatedBy": [ - "APIKEY" - ] - }, - "serviceCatalog": [ - { - "type": "rax:dns", - "endpoints": [ - { - "publicURL": "https://dns.api.rackspacecloud.com/v1.0/123456", - "tenantId": "123456" - } - ], - "name": "cloudDNS" - } - ], - "user": { - "id": "fakeUseID", - "name": "testUser" - } - } -} -` - -const recordResponseMock = ` -{ - "request": "{\"records\":[{\"name\":\"_acme-challenge.example.com\",\"type\":\"TXT\",\"data\":\"pW9ZKG0xz_PCriK-nCMOjADy9eJcgGWIzkkj2fN4uZM\",\"ttl\":300}]}", - "status": "RUNNING", - "verb": "POST", - "jobId": "00000000-0000-0000-0000-0000000000", - "callbackUrl": "https://dns.api.rackspacecloud.com/v1.0/123456/status/00000000-0000-0000-0000-0000000000", - "requestUrl": "https://dns.api.rackspacecloud.com/v1.0/123456/domains/112233/records" -} -` diff --git a/providers/dns/rackspace/rackspace_test.go b/providers/dns/rackspace/rackspace_test.go index cbc57b472..cefb46134 100644 --- a/providers/dns/rackspace/rackspace_test.go +++ b/providers/dns/rackspace/rackspace_test.go @@ -1,9 +1,7 @@ package rackspace import ( - "bytes" "fmt" - "io" "net/http" "net/http/httptest" "strings" @@ -11,6 +9,7 @@ import ( "time" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -23,11 +22,7 @@ var envTest = tester.NewEnvTest( WithDomain(envDomain) func TestNewDNSProviderConfig(t *testing.T) { - config := setupTest(t) - - provider, err := NewDNSProviderConfig(config) - require.NoError(t, err) - assert.NotNil(t, provider.config) + provider := mockBuilder().Build(t) assert.Equal(t, "testToken", provider.token, "The token should match") } @@ -38,25 +33,40 @@ func TestNewDNSProviderConfig_MissingCredErr(t *testing.T) { } func TestDNSProvider_Present(t *testing.T) { - config := setupTest(t) + provider := mockBuilder(). + Route("GET /123456/domains", + servermock.ResponseFromFixture("zone_details.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com")). + Route("POST /123456/domains/112233/records", + servermock.ResponseFromFixture("record.json"). + WithStatusCode(http.StatusAccepted), + servermock.CheckRequestJSONBody(`{"records":[{"name":"_acme-challenge.example.com","type":"TXT","data":"pW9ZKG0xz_PCriK-nCMOjADy9eJcgGWIzkkj2fN4uZM","ttl":300}]}`)). + Build(t) - provider, err := NewDNSProviderConfig(config) - - if assert.NoError(t, err) { - err = provider.Present("example.com", "token", "keyAuth") - require.NoError(t, err) - } + err := provider.Present("example.com", "token", "keyAuth") + require.NoError(t, err) } func TestDNSProvider_CleanUp(t *testing.T) { - config := setupTest(t) + provider := mockBuilder(). + Route("GET /123456/domains", + servermock.ResponseFromFixture("zone_details.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com")). + Route("GET /123456/domains/112233/records", + servermock.ResponseFromFixture("record_details.json"), + servermock.CheckQueryParameter().Strict(). + With("type", "TXT"). + With("name", "_acme-challenge.example.com")). + Route("DELETE /123456/domains/112233/records", + servermock.ResponseFromFixture("delete.json"), + servermock.CheckQueryParameter().Strict(). + With("id", "TXT-654321")). + Build(t) - provider, err := NewDNSProviderConfig(config) - - if assert.NoError(t, err) { - err = provider.CleanUp("example.com", "token", "keyAuth") - require.NoError(t, err) - } + err := provider.CleanUp("example.com", "token", "keyAuth") + require.NoError(t, err) } func TestLiveNewDNSProvider_ValidEnv(t *testing.T) { @@ -99,99 +109,59 @@ func TestLiveCleanUp(t *testing.T) { require.NoError(t, err) } -func setupTest(t *testing.T) *Config { - t.Helper() +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.APIUser = "testUser" + config.APIKey = "testKey" + config.HTTPClient = server.Client() + config.BaseURL = server.URL + "/v2.0/tokens" - dnsAPI := httptest.NewServer(dnsHandler()) - t.Cleanup(dnsAPI.Close) + return NewDNSProviderConfig(config) + }, + servermock.CheckHeader().WithJSONHeaders(), + ). + Route("POST /v2.0/tokens", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + apiURL := fmt.Sprintf("http://%s/123456", req.Context().Value(http.LocalAddrContextKey)) - identityAPI := httptest.NewServer(identityHandler(dnsAPI.URL + "/123456")) - t.Cleanup(identityAPI.Close) - - config := NewDefaultConfig() - config.APIUser = "testUser" - config.APIKey = "testKey" - config.HTTPClient = identityAPI.Client() - config.BaseURL = identityAPI.URL + "/" - - return config + resp := strings.Replace(` +{ + "access": { + "token": { + "id": "testToken", + "expires": "1970-01-01T00:00:00.000Z", + "tenant": { + "id": "123456", + "name": "123456" + }, + "RAX-AUTH:authenticatedBy": [ + "APIKEY" + ] + }, + "serviceCatalog": [ + { + "type": "rax:dns", + "endpoints": [ + { + "publicURL": "https://dns.api.rackspacecloud.com/v1.0/123456", + "tenantId": "123456" + } + ], + "name": "cloudDNS" + } + ], + "user": { + "id": "fakeUseID", + "name": "testUser" + } + } } +`, "https://dns.api.rackspacecloud.com/v1.0/123456", apiURL, 1) -func identityHandler(dnsEndpoint string) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - reqBody, err := io.ReadAll(r.Body) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - - if string(bytes.TrimSpace(reqBody)) != `{"auth":{"RAX-KSKEY:apiKeyCredentials":{"username":"testUser","apiKey":"testKey"}}}` { - http.Error(w, fmt.Sprintf("invalid body: %s", string(reqBody)), http.StatusBadRequest) - return - } - - resp := strings.Replace(identityResponseMock, "https://dns.api.rackspacecloud.com/v1.0/123456", dnsEndpoint, 1) - w.WriteHeader(http.StatusOK) - _, _ = fmt.Fprint(w, resp) - }) -} - -func dnsHandler() *http.ServeMux { - mux := http.NewServeMux() - - // Used by `getHostedZoneID()` finding `zoneID` "?name=example.com" - mux.HandleFunc("/123456/domains", func(w http.ResponseWriter, r *http.Request) { - if r.URL.Query().Get("name") == "example.com" { - w.WriteHeader(http.StatusOK) - _, _ = fmt.Fprint(w, zoneDetailsMock) - return - } - w.WriteHeader(http.StatusBadRequest) - }) - - mux.HandleFunc("/123456/domains/112233/records", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - // Used by `Present()` creating the TXT record - case http.MethodPost: - reqBody, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if string(bytes.TrimSpace(reqBody)) != `{"records":[{"name":"_acme-challenge.example.com","type":"TXT","data":"pW9ZKG0xz_PCriK-nCMOjADy9eJcgGWIzkkj2fN4uZM","ttl":300}]}` { - http.Error(w, fmt.Sprintf("invalid body: %s", string(reqBody)), http.StatusBadRequest) - return - } - - w.WriteHeader(http.StatusAccepted) - _, _ = fmt.Fprint(w, recordResponseMock) - - // Used by `findTxtRecord()` finding `record.ID` "?type=TXT&name=_acme-challenge.example.com" - case http.MethodGet: - if r.URL.Query().Get("type") == "TXT" && r.URL.Query().Get("name") == "_acme-challenge.example.com" { - w.WriteHeader(http.StatusOK) - _, _ = fmt.Fprint(w, recordDetailsMock) - return - } - - w.WriteHeader(http.StatusBadRequest) - return - - // Used by `CleanUp()` deleting the TXT record "?id=445566" - case http.MethodDelete: - if r.URL.Query().Get("id") == "TXT-654321" { - w.WriteHeader(http.StatusOK) - _, _ = fmt.Fprint(w, recordDeleteMock) - return - } - w.WriteHeader(http.StatusBadRequest) - } - }) - - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - http.Error(w, fmt.Sprintf("Not Found for Request: (%+v)", r), http.StatusNotFound) - }) - - return mux + rw.WriteHeader(http.StatusOK) + _, _ = fmt.Fprint(rw, resp) + }), + servermock.CheckRequestJSONBody(`{"auth":{"RAX-KSKEY:apiKeyCredentials":{"username":"testUser","apiKey":"testKey"}}}`)) } diff --git a/providers/dns/rainyun/internal/client_test.go b/providers/dns/rainyun/internal/client_test.go index 1652bba39..8246001af 100644 --- a/providers/dns/rainyun/internal/client_test.go +++ b/providers/dns/rainyun/internal/client_test.go @@ -1,58 +1,39 @@ package internal import ( - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, status int, filename string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("secret") + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if filename == "" { - rw.WriteHeader(status) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client, err := NewClient("secret") - require.NoError(t, err) - - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders()) } func TestClient_ListDomains(t *testing.T) { - client := setupTest(t, "GET /domain", http.StatusOK, "domains.json") + client := mockBuilder(). + Route("GET /domain", + servermock.ResponseFromFixture("domains.json"), + servermock.CheckQueryParameter().Strict(). + With("options", `{"columnFilters":{"domains.Domain":""},"sort":[],"page":1,"perPage":100}`)). + Build(t) domains, err := client.ListDomains(t.Context()) require.NoError(t, err) @@ -66,7 +47,11 @@ func TestClient_ListDomains(t *testing.T) { } func TestClient_ListDomains_error(t *testing.T) { - client := setupTest(t, "GET /domain", http.StatusForbidden, "error.json") + client := mockBuilder(). + Route("GET /domain", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusForbidden)). + Build(t) _, err := client.ListDomains(t.Context()) require.Error(t, err) @@ -75,7 +60,13 @@ func TestClient_ListDomains_error(t *testing.T) { } func TestClient_ListRecords(t *testing.T) { - client := setupTest(t, "GET /domain/123/dns", http.StatusOK, "records.json") + client := mockBuilder(). + Route("GET /domain/123/dns", + servermock.ResponseFromFixture("records.json"), + servermock.CheckQueryParameter().Strict(). + With("limit", "100"). + With("page_no", "1")). + Build(t) records, err := client.ListRecords(t.Context(), 123) require.NoError(t, err) @@ -103,7 +94,11 @@ func TestClient_ListRecords(t *testing.T) { } func TestClient_ListRecords_error(t *testing.T) { - client := setupTest(t, "GET /domain/123/dns", http.StatusForbidden, "error.json") + client := mockBuilder(). + Route("GET /domain/123/dns", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusForbidden)). + Build(t) _, err := client.ListRecords(t.Context(), 123) require.Error(t, err) @@ -112,7 +107,9 @@ func TestClient_ListRecords_error(t *testing.T) { } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, "POST /domain/123/dns", http.StatusOK, "") + client := mockBuilder(). + Route("POST /domain/123/dns", nil). + Build(t) record := Record{ Host: "_acme-challenge.foo.example.com", @@ -127,7 +124,11 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, "POST /domain/123/dns", http.StatusForbidden, "error.json") + client := mockBuilder(). + Route("POST /domain/123/dns", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusForbidden)). + Build(t) record := Record{ Host: "_acme-challenge.foo.example.com", @@ -144,14 +145,20 @@ func TestClient_AddRecord_error(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, "DELETE /domain/123/dns", http.StatusOK, "") + client := mockBuilder(). + Route("DELETE /domain/123/dns", nil). + Build(t) err := client.DeleteRecord(t.Context(), 123, 456) require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, "DELETE /domain/123/dns", http.StatusForbidden, "error.json") + client := mockBuilder(). + Route("DELETE /domain/123/dns", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusForbidden)). + Build(t) err := client.DeleteRecord(t.Context(), 123, 456) require.Error(t, err) diff --git a/providers/dns/rcodezero/internal/client_test.go b/providers/dns/rcodezero/internal/client_test.go index 0b54fa97f..b70107072 100644 --- a/providers/dns/rcodezero/internal/client_test.go +++ b/providers/dns/rcodezero/internal/client_test.go @@ -1,68 +1,30 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, method, pattern string, status int, file string) *Client { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusBadRequest) - return - } - - apiToken := req.Header.Get(authorizationHeader) - if apiToken != "Bearer secret" { - http.Error(rw, fmt.Sprintf("invalid credentials: %s", apiToken), http.StatusBadRequest) - return - } - - if file == "" { - rw.WriteHeader(status) - return - } - - open, err := os.Open(filepath.Join("fixtures", file)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - +func setupClient(server *httptest.Server) (*Client, error) { client := NewClient("secret") client.HTTPClient = server.Client() client.baseURL, _ = url.Parse(server.URL) - return client + return client, nil } func TestClient_UpdateRecords_error(t *testing.T) { - client := setupTest(t, http.MethodPatch, "/v1/acme/zones/example.org/rrsets", http.StatusUnprocessableEntity, "error.json") + client := servermock.NewBuilder[*Client](setupClient, servermock.CheckHeader().WithJSONHeaders()). + Route("PATCH /v1/acme/zones/example.org/rrsets", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnprocessableEntity)). + Build(t) rrSet := []UpdateRRSet{{ Name: "acme.example.org.", @@ -77,7 +39,10 @@ func TestClient_UpdateRecords_error(t *testing.T) { } func TestClient_UpdateRecords(t *testing.T) { - client := setupTest(t, http.MethodPatch, "/v1/acme/zones/example.org/rrsets", http.StatusOK, "rrsets-response.json") + client := servermock.NewBuilder[*Client](setupClient, servermock.CheckHeader().WithJSONHeaders()). + Route("PATCH /v1/acme/zones/example.org/rrsets", + servermock.ResponseFromFixture("rrsets-response.json")). + Build(t) rrSet := []UpdateRRSet{{ Name: "acme.example.org.", diff --git a/providers/dns/regru/internal/client_test.go b/providers/dns/regru/internal/client_test.go index 4b4a9c8f4..0779f0d5f 100644 --- a/providers/dns/regru/internal/client_test.go +++ b/providers/dns/regru/internal/client_test.go @@ -1,60 +1,59 @@ package internal import ( - "net/http" + "net/http/httptest" "net/url" - "os" "testing" - "time" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -const ( - noopBaseURL = "https://api.reg.ru/api/regru2/nop" - officialTestUser = "test" - officialTestPassword = "test" -) +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret") + client.baseURL, _ = url.Parse(server.URL) + + return client, nil + }, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded(), + ) +} func TestRemoveRecord(t *testing.T) { - // TODO(ldez): remove skip when the reg.ru API will be fixed. - t.Skip("there is a bug with the reg.ru API: INTERNAL_API_ERROR: Внутренняя ошибка, status code: 503") - - client := NewClient(officialTestUser, officialTestPassword) - client.HTTPClient = &http.Client{Timeout: 30 * time.Second} + client := mockBuilder(). + Route("POST /zone/remove_record", + servermock.ResponseFromFixture("remove_record.json"), + servermock.CheckForm().Strict(). + With("input_data", `{"domains":[{"dname":"test.ru"}],"subdomain":"_acme-challenge","content":"txttxttxt","record_type":"TXT","output_content_type":"plain"}`). + With("username", "user"). + With("password", "secret"). + With("input_format", "json")). + Build(t) err := client.RemoveTxtRecord(t.Context(), "test.ru", "_acme-challenge", "txttxttxt") require.NoError(t, err) } func TestRemoveRecord_errors(t *testing.T) { - // TODO(ldez): remove skip when the reg.ru API will be fixed. - if os.Getenv("CI") == "true" { - t.Skip("there is a bug with the reg.ru and GitHub action: dial tcp 194.58.116.30:443: i/o timeout") - } - testCases := []struct { desc string domain string - username string - password string - baseURL string + response string expected string }{ { desc: "authentication failed", domain: "test.ru", - username: "", - password: "", - baseURL: noopBaseURL, + response: "remove_record_error_auth.json", expected: "API error: NO_AUTH: No authorization mechanism selected", }, { desc: "domain error", domain: "", - username: officialTestUser, - password: officialTestPassword, - baseURL: defaultBaseURL, + response: "remove_record_error_domain.json", expected: "API error: NO_DOMAIN: domain_name not given or empty", }, } @@ -63,9 +62,9 @@ func TestRemoveRecord_errors(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - client := NewClient(test.username, test.username) - client.HTTPClient = &http.Client{Timeout: 30 * time.Second} - client.baseURL, _ = url.Parse(test.baseURL) + client := mockBuilder(). + Route("POST /zone/remove_record", servermock.ResponseFromFixture(test.response)). + Build(t) err := client.RemoveTxtRecord(t.Context(), test.domain, "_acme-challenge", "txttxttxt") require.EqualError(t, err, test.expected) @@ -74,44 +73,37 @@ func TestRemoveRecord_errors(t *testing.T) { } func TestAddTXTRecord(t *testing.T) { - // TODO(ldez): remove skip when the reg.ru API will be fixed. - t.Skip("there is a bug with the reg.ru API: INTERNAL_API_ERROR: Внутренняя ошибка, status code: 503") - - client := NewClient(officialTestUser, officialTestPassword) - client.HTTPClient = &http.Client{Timeout: 30 * time.Second} + client := mockBuilder(). + Route("POST /zone/add_txt", + servermock.ResponseFromFixture("add_txt_record.json"), + servermock.CheckForm().Strict(). + With("input_data", `{"domains":[{"dname":"test.ru"}],"subdomain":"_acme-challenge","text":"txttxttxt","output_content_type":"plain"}`). + With("username", "user"). + With("password", "secret"). + With("input_format", "json")). + Build(t) err := client.AddTXTRecord(t.Context(), "test.ru", "_acme-challenge", "txttxttxt") require.NoError(t, err) } func TestAddTXTRecord_errors(t *testing.T) { - // TODO(ldez): remove skip when the reg.ru API will be fixed. - if os.Getenv("CI") == "true" { - t.Skip("there is a bug with the reg.ru and GitHub action: dial tcp 194.58.116.30:443: i/o timeout") - } - testCases := []struct { desc string domain string - username string - password string - baseURL string + response string expected string }{ { desc: "authentication failed", domain: "test.ru", - username: "", - password: "", - baseURL: noopBaseURL, + response: "add_txt_record_error_auth.json", expected: "API error: NO_AUTH: No authorization mechanism selected", }, { desc: "domain error", domain: "", - username: officialTestUser, - password: officialTestPassword, - baseURL: defaultBaseURL, + response: "add_txt_record_error_domain.json", expected: "API error: NO_DOMAIN: domain_name not given or empty", }, } @@ -120,9 +112,9 @@ func TestAddTXTRecord_errors(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - client := NewClient(test.username, test.username) - client.HTTPClient = &http.Client{Timeout: 30 * time.Second} - client.baseURL, _ = url.Parse(test.baseURL) + client := mockBuilder(). + Route("POST /zone/add_txt", servermock.ResponseFromFixture(test.response)). + Build(t) err := client.AddTXTRecord(t.Context(), test.domain, "_acme-challenge", "txttxttxt") require.EqualError(t, err, test.expected) diff --git a/providers/dns/regru/internal/fixtures/add_txt_record.json b/providers/dns/regru/internal/fixtures/add_txt_record.json new file mode 100644 index 000000000..06306b4c4 --- /dev/null +++ b/providers/dns/regru/internal/fixtures/add_txt_record.json @@ -0,0 +1,14 @@ +{ + "answer": { + "domains": [ + { + "dname": "test.ru", + "result": "success", + "service_id": 12345 + } + ] + }, + "charset": "utf-8", + "messagestore": null, + "result": "success" +} diff --git a/providers/dns/regru/internal/fixtures/add_txt_record_error_auth.json b/providers/dns/regru/internal/fixtures/add_txt_record_error_auth.json new file mode 100644 index 000000000..2d5314bf3 --- /dev/null +++ b/providers/dns/regru/internal/fixtures/add_txt_record_error_auth.json @@ -0,0 +1,10 @@ +{ + "charset": "utf-8", + "error_code": "NO_AUTH", + "error_params": { + "command_name": "nop/zone/add_txt" + }, + "error_text": "No authorization mechanism selected", + "messagestore": null, + "result": "error" +} diff --git a/providers/dns/regru/internal/fixtures/add_txt_record_error_domain.json b/providers/dns/regru/internal/fixtures/add_txt_record_error_domain.json new file mode 100644 index 000000000..305846ed1 --- /dev/null +++ b/providers/dns/regru/internal/fixtures/add_txt_record_error_domain.json @@ -0,0 +1,14 @@ +{ + "answer": { + "domains": [ + { + "error_code": "NO_DOMAIN", + "error_text": "domain_name not given or empty", + "result": "error" + } + ] + }, + "charset": "utf-8", + "messagestore": null, + "result": "success" +} diff --git a/providers/dns/regru/internal/fixtures/remove_record.json b/providers/dns/regru/internal/fixtures/remove_record.json new file mode 100644 index 000000000..06306b4c4 --- /dev/null +++ b/providers/dns/regru/internal/fixtures/remove_record.json @@ -0,0 +1,14 @@ +{ + "answer": { + "domains": [ + { + "dname": "test.ru", + "result": "success", + "service_id": 12345 + } + ] + }, + "charset": "utf-8", + "messagestore": null, + "result": "success" +} diff --git a/providers/dns/regru/internal/fixtures/remove_record_error_auth.json b/providers/dns/regru/internal/fixtures/remove_record_error_auth.json new file mode 100644 index 000000000..98c429c53 --- /dev/null +++ b/providers/dns/regru/internal/fixtures/remove_record_error_auth.json @@ -0,0 +1,10 @@ +{ + "charset" : "utf-8", + "error_code" : "NO_AUTH", + "error_params" : { + "command_name" : "nop/zone/remove_record" + }, + "error_text" : "No authorization mechanism selected", + "messagestore" : null, + "result" : "error" +} diff --git a/providers/dns/regru/internal/fixtures/remove_record_error_domain.json b/providers/dns/regru/internal/fixtures/remove_record_error_domain.json new file mode 100644 index 000000000..a9ca88ff7 --- /dev/null +++ b/providers/dns/regru/internal/fixtures/remove_record_error_domain.json @@ -0,0 +1,14 @@ +{ + "answer" : { + "domains" : [ + { + "error_code" : "NO_DOMAIN", + "error_text" : "domain_name not given or empty", + "result" : "error" + } + ] + }, + "charset" : "utf-8", + "messagestore" : null, + "result" : "success" +} diff --git a/providers/dns/regru/internal/readme.md b/providers/dns/regru/internal/readme.md new file mode 100644 index 000000000..5f13012d2 --- /dev/null +++ b/providers/dns/regru/internal/readme.md @@ -0,0 +1,6 @@ +Test account (with the default endpoint): +- user: `test` +- password: `test` + +Noop endpoint: +- https://api.reg.ru/api/regru2/nop diff --git a/providers/dns/route53/fixtures/changeResourceRecordSetsResponse.xml b/providers/dns/route53/fixtures/changeResourceRecordSetsResponse.xml new file mode 100644 index 000000000..68dba580f --- /dev/null +++ b/providers/dns/route53/fixtures/changeResourceRecordSetsResponse.xml @@ -0,0 +1,8 @@ + + + + /change/123456 + PENDING + 2016-02-10T01:36:41.958Z + + diff --git a/providers/dns/route53/fixtures/getChangeResponse.xml b/providers/dns/route53/fixtures/getChangeResponse.xml new file mode 100644 index 000000000..f22c09460 --- /dev/null +++ b/providers/dns/route53/fixtures/getChangeResponse.xml @@ -0,0 +1,8 @@ + + + + 123456 + INSYNC + 2016-02-10T01:36:41.958Z + + diff --git a/providers/dns/route53/fixtures/listHostedZonesByNameResponse.xml b/providers/dns/route53/fixtures/listHostedZonesByNameResponse.xml new file mode 100644 index 000000000..db47ba1e1 --- /dev/null +++ b/providers/dns/route53/fixtures/listHostedZonesByNameResponse.xml @@ -0,0 +1,19 @@ + + + + + /hostedzone/ABCDEFG + example.com. + D2224C5B-684A-DB4A-BB9A-E09E3BAFEA7A + + Test comment + false + + 10 + + + true + example2.com + ZLT12321321124 + 1 + diff --git a/providers/dns/route53/fixtures_test.go b/providers/dns/route53/fixtures_test.go deleted file mode 100644 index 444a88003..000000000 --- a/providers/dns/route53/fixtures_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package route53 - -const ChangeResourceRecordSetsResponse = ` - - - /change/123456 - PENDING - 2016-02-10T01:36:41.958Z - -` - -const ListHostedZonesByNameResponse = ` - - - - /hostedzone/ABCDEFG - example.com. - D2224C5B-684A-DB4A-BB9A-E09E3BAFEA7A - - Test comment - false - - 10 - - - true - example2.com - ZLT12321321124 - 1 -` - -const GetChangeResponse = ` - - - 123456 - INSYNC - 2016-02-10T01:36:41.958Z - -` diff --git a/providers/dns/route53/mock_test.go b/providers/dns/route53/mock_test.go deleted file mode 100644 index 022767385..000000000 --- a/providers/dns/route53/mock_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package route53 - -import ( - "fmt" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -// MockResponse represents a predefined response used by a mock server. -type MockResponse struct { - StatusCode int - Body string -} - -// MockResponseMap maps request paths to responses. -type MockResponseMap map[string]MockResponse - -func setupTest(t *testing.T, responses MockResponseMap) string { - t.Helper() - - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - path := r.URL.Path - resp, ok := responses[path] - if !ok { - resp, ok = responses[r.RequestURI] - if !ok { - msg := fmt.Sprintf("Requested path not found in response map: %s", path) - require.FailNow(t, msg) - } - } - - w.Header().Set("Content-Type", "application/xml") - w.WriteHeader(resp.StatusCode) - _, err := w.Write([]byte(resp.Body)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - server := httptest.NewServer(handler) - t.Cleanup(server.Close) - - time.Sleep(100 * time.Millisecond) - - return server.URL -} diff --git a/providers/dns/route53/route53_test.go b/providers/dns/route53/route53_test.go index 60901de6d..6079bb4e6 100644 --- a/providers/dns/route53/route53_test.go +++ b/providers/dns/route53/route53_test.go @@ -1,6 +1,7 @@ package route53 import ( + "net/http/httptest" "os" "testing" "time" @@ -10,6 +11,7 @@ import ( "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/route53" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -30,22 +32,6 @@ var envTest = tester.NewEnvTest( WithDomain(envDomain). WithLiveTestRequirements(EnvAccessKeyID, EnvSecretAccessKey, EnvRegion, envDomain) -func makeTestProvider(t *testing.T, serverURL string) *DNSProvider { - t.Helper() - - cfg := aws.Config{ - Credentials: credentials.NewStaticCredentialsProvider("abc", "123", " "), - Region: "mock-region", - BaseEndpoint: aws.String(serverURL), - RetryMaxAttempts: 1, - } - - return &DNSProvider{ - client: route53.NewFromConfig(cfg), - config: NewDefaultConfig(), - } -} - func Test_loadCredentials_FromEnv(t *testing.T) { defer envTest.RestoreEnv() envTest.ClearEnv() @@ -154,21 +140,42 @@ func TestNewDefaultConfig(t *testing.T) { } func TestDNSProvider_Present(t *testing.T) { - mockResponses := MockResponseMap{ - "/2013-04-01/hostedzonesbyname": {StatusCode: 200, Body: ListHostedZonesByNameResponse}, - "/2013-04-01/hostedzone/ABCDEFG/rrset": {StatusCode: 200, Body: ChangeResourceRecordSetsResponse}, - "/2013-04-01/change/123456": {StatusCode: 200, Body: GetChangeResponse}, - "/2013-04-01/hostedzone/ABCDEFG/rrset?name=_acme-challenge.example.com.&type=TXT": { - StatusCode: 200, - Body: "", - }, - } - - serverURL := setupTest(t, mockResponses) - defer envTest.RestoreEnv() envTest.ClearEnv() - provider := makeTestProvider(t, serverURL) + + provider := servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + cfg := aws.Config{ + Credentials: credentials.NewStaticCredentialsProvider("abc", "123", " "), + Region: "mock-region", + BaseEndpoint: aws.String(server.URL), + RetryMaxAttempts: 1, + } + + return &DNSProvider{ + client: route53.NewFromConfig(cfg), + config: NewDefaultConfig(), + }, nil + }, + ). + Route("GET /2013-04-01/hostedzonesbyname", + servermock.ResponseFromFixture("listHostedZonesByNameResponse.xml"). + WithHeader("Content-Type", "application/xml"), + servermock.CheckQueryParameter().Strict(). + With("dnsname", "example.com")). + Route("POST /2013-04-01/hostedzone/ABCDEFG/rrset", + servermock.ResponseFromFixture("changeResourceRecordSetsResponse.xml"). + WithHeader("Content-Type", "application/xml")). + Route("GET /2013-04-01/change/123456", + servermock.ResponseFromFixture("getChangeResponse.xml"). + WithHeader("Content-Type", "application/xml")). + Route("GET /2013-04-01/hostedzone/ABCDEFG/rrset", + servermock.Noop(). + WithHeader("Content-Type", "application/xml"), + servermock.CheckQueryParameter().Strict(). + With("name", "_acme-challenge.example.com."). + With("type", "TXT")). + Build(t) domain := "example.com" keyAuth := "123456d==" diff --git a/providers/dns/safedns/internal/client_test.go b/providers/dns/safedns/internal/client_test.go index c00a8b5a7..117a85a9f 100644 --- a/providers/dns/safedns/internal/client_test.go +++ b/providers/dns/safedns/internal/client_test.go @@ -1,74 +1,36 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "strings" "testing" "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("secret") + client.baseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient("secret") - client.baseURL, _ = url.Parse(server.URL) - - return client, mux + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(), + ) } func TestClient_AddRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/zones/example.com/records", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - if req.Header.Get(authorizationHeader) != "secret" { - http.Error(rw, `{"message":"Unauthenticated"}`, http.StatusUnauthorized) - return - } - - reqBody, err := io.ReadAll(req.Body) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - expectedReqBody := `{"name":"_acme-challenge.example.com","type":"TXT","content":"\"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI\"","ttl":120}` - if strings.TrimSpace(string(reqBody)) != expectedReqBody { - http.Error(rw, `{"message":"invalid request"}`, http.StatusBadRequest) - return - } - - resp := `{ - "data": { - "id": 1234567 - }, - "meta": { - "location": "https://api.ukfast.io/safedns/v1/zones/example.com/records/1234567" - } - }` - - rw.WriteHeader(http.StatusCreated) - _, err = fmt.Fprint(rw, resp) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("POST /zones/example.com/records", + servermock.ResponseFromFixture("add_record.json"). + WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBodyFromFile("add_record-request.json")). + Build(t) record := Record{ Name: "_acme-challenge.example.com", @@ -96,23 +58,42 @@ func TestClient_AddRecord(t *testing.T) { assert.Equal(t, expected, response) } +func TestClient_AddRecord_error(t *testing.T) { + client := mockBuilder(). + Route("POST /zones/example.com/records", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + record := Record{ + Name: "_acme-challenge.example.com", + Type: "TXT", + Content: `"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI"`, + TTL: dns01.DefaultTTL, + } + + _, err := client.AddRecord(t.Context(), "example.com", record) + require.EqualError(t, err, "add record: [status code: 401] Unauthenticated") +} + func TestClient_RemoveRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/zones/example.com/records/1234567", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - if req.Header.Get(authorizationHeader) != "secret" { - http.Error(rw, `{"message":"Unauthenticated"}`, http.StatusUnauthorized) - return - } - - rw.WriteHeader(http.StatusNoContent) - }) + client := mockBuilder(). + Route("DELETE /zones/example.com/records/1234567", + servermock.Noop(). + WithStatusCode(http.StatusNoContent)). + Build(t) err := client.RemoveRecord(t.Context(), "example.com", 1234567) require.NoError(t, err) } + +func TestClient_RemoveRecord_error(t *testing.T) { + client := mockBuilder(). + Route("DELETE /zones/example.com/records/1234567", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + err := client.RemoveRecord(t.Context(), "example.com", 1234567) + require.EqualError(t, err, "remove record: [status code: 401] Unauthenticated") +} diff --git a/providers/dns/safedns/internal/fixtures/add_record-request.json b/providers/dns/safedns/internal/fixtures/add_record-request.json new file mode 100644 index 000000000..71c8813f2 --- /dev/null +++ b/providers/dns/safedns/internal/fixtures/add_record-request.json @@ -0,0 +1,6 @@ +{ + "name": "_acme-challenge.example.com", + "type": "TXT", + "content": "\"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI\"", + "ttl": 120 +} diff --git a/providers/dns/safedns/internal/fixtures/add_record.json b/providers/dns/safedns/internal/fixtures/add_record.json new file mode 100644 index 000000000..f3c4ad883 --- /dev/null +++ b/providers/dns/safedns/internal/fixtures/add_record.json @@ -0,0 +1,8 @@ +{ + "data": { + "id": 1234567 + }, + "meta": { + "location": "https://api.ukfast.io/safedns/v1/zones/example.com/records/1234567" + } +} diff --git a/providers/dns/safedns/internal/fixtures/error.json b/providers/dns/safedns/internal/fixtures/error.json new file mode 100644 index 000000000..47fb5916c --- /dev/null +++ b/providers/dns/safedns/internal/fixtures/error.json @@ -0,0 +1,3 @@ +{ + "message": "Unauthenticated" +} diff --git a/providers/dns/selectelv2/selectelv2.go b/providers/dns/selectelv2/selectelv2.go index 19e352d7f..ca0a9107d 100644 --- a/providers/dns/selectelv2/selectelv2.go +++ b/providers/dns/selectelv2/selectelv2.go @@ -124,15 +124,15 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Timeout returns the Timeout and interval to use when checking for DNS propagation. // Adjusting here to cope with spikes in propagation times. -func (p *DNSProvider) Timeout() (timeout, interval time.Duration) { - return p.config.PropagationTimeout, p.config.PollingInterval +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval } // Present creates a TXT record to fulfill DNS-01 challenge. -func (p *DNSProvider) Present(domain, _, keyAuth string) error { +func (d *DNSProvider) Present(domain, _, keyAuth string) error { ctx := context.Background() - client, err := p.authorize() + client, err := d.authorize() if err != nil { return fmt.Errorf("selectelv2: authorize: %w", err) } @@ -153,7 +153,7 @@ func (p *DNSProvider) Present(domain, _, keyAuth string) error { newRRSet := &selectelapi.RRSet{ Name: info.EffectiveFQDN, Type: selectelapi.TXT, - TTL: p.config.TTL, + TTL: d.config.TTL, Records: []selectelapi.RecordItem{{Content: fmt.Sprintf("%q", info.Value)}}, } @@ -176,10 +176,10 @@ func (p *DNSProvider) Present(domain, _, keyAuth string) error { } // CleanUp removes a TXT record used for DNS-01 challenge. -func (p *DNSProvider) CleanUp(domain, _, keyAuth string) error { +func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { ctx := context.Background() - client, err := p.authorize() + client, err := d.authorize() if err != nil { return fmt.Errorf("selectelv2: authorize: %w", err) } @@ -220,8 +220,8 @@ func (p *DNSProvider) CleanUp(domain, _, keyAuth string) error { return nil } -func (p *DNSProvider) authorize() (*clientWrapper, error) { - token, err := obtainOpenstackToken(p.config) +func (d *DNSProvider) authorize() (*clientWrapper, error) { + token, err := obtainOpenstackToken(d.config) if err != nil { return nil, err } @@ -230,7 +230,7 @@ func (p *DNSProvider) authorize() (*clientWrapper, error) { extraHeaders.Set(tokenHeader, token) return &clientWrapper{ - DNSClient: p.baseClient.WithHeaders(extraHeaders), + DNSClient: d.baseClient.WithHeaders(extraHeaders), }, nil } diff --git a/providers/dns/selfhostde/internal/client_test.go b/providers/dns/selfhostde/internal/client_test.go index 88f627b02..22949728c 100644 --- a/providers/dns/selfhostde/internal/client_test.go +++ b/providers/dns/selfhostde/internal/client_test.go @@ -1,64 +1,41 @@ package internal import ( - "fmt" "net/http" "net/http/httptest" - "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - +func setupClient(server *httptest.Server) (*Client, error) { client := NewClient("user", "secret") - serverURL, err := url.Parse(server.URL) - require.NoError(t, err) + client.baseURL = server.URL + client.HTTPClient = server.Client() - client.baseURL = serverURL.String() - - return client, mux + return client, nil } func TestClient_UpdateTXTRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("GET /", func(rw http.ResponseWriter, req *http.Request) { - query := req.URL.Query() - - fields := map[string]string{ - "username": "user", - "password": "secret", - "rid": "123456", - "content": "txt", - } - - for k, v := range fields { - value := query.Get(k) - if value != v { - http.Error(rw, fmt.Sprintf("%s: unexpected value: %s (%s)", k, value, v), http.StatusBadRequest) - return - } - } - }) + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /", nil, servermock.CheckQueryParameter().Strict(). + With("rid", "123456"). + With("content", "txt"). + With("username", "user"). + With("password", "secret"), + ). + Build(t) err := client.UpdateTXTRecord(t.Context(), "123456", "txt") require.NoError(t, err) } func TestClient_UpdateTXTRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("GET /", func(rw http.ResponseWriter, _ *http.Request) { - http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - }) + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /", servermock.Noop().WithStatusCode(http.StatusBadRequest)). + Build(t) err := client.UpdateTXTRecord(t.Context(), "123456", "txt") - require.Error(t, err) + require.EqualError(t, err, "unexpected status code: [status code: 400] body: ") } diff --git a/providers/dns/servercow/internal/client_test.go b/providers/dns/servercow/internal/client_test.go index b171b6408..2092bf907 100644 --- a/providers/dns/servercow/internal/client_test.go +++ b/providers/dns/servercow/internal/client_test.go @@ -2,53 +2,35 @@ package internal import ( "encoding/json" - "io" - "net/http" "net/http/httptest" "net/url" "os" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret") + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient("", "") - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client, mux + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + With("X-Auth-Username", "user"). + With("X-Auth-Password", "secret"), + ) } func TestClient_GetRecords(t *testing.T) { - client, handler := setupTest(t) - - handler.HandleFunc("/lego.wtf", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - file, err := os.Open("./fixtures/records-01.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("GET /lego.wtf", servermock.ResponseFromFixture("records-01.json")). + Build(t) records, err := client.GetRecords(t.Context(), "lego.wtf") require.NoError(t, err) @@ -63,20 +45,9 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_GetRecords_error(t *testing.T) { - client, handler := setupTest(t) - - handler.HandleFunc("/lego.wtf", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - err := json.NewEncoder(rw).Encode(Message{ErrorMsg: "authentication failed"}) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("GET /lego.wtf", servermock.JSONEncode(Message{ErrorMsg: "authentication failed"})). + Build(t) records, err := client.GetRecords(t.Context(), "lego.wtf") require.Error(t, err) @@ -85,33 +56,11 @@ func TestClient_GetRecords_error(t *testing.T) { } func TestClient_CreateUpdateRecord(t *testing.T) { - client, handler := setupTest(t) - - handler.HandleFunc("/lego.wtf", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - content, err := io.ReadAll(req.Body) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - expectedRequest := `{"name":"_acme-challenge.www","type":"TXT","ttl":30,"content":["aaa","bbb"]}` - - if !assert.JSONEq(t, expectedRequest, string(content)) { - http.Error(rw, "invalid content", http.StatusBadRequest) - return - } - - err = json.NewEncoder(rw).Encode(Message{Message: "ok"}) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("POST /lego.wtf", + servermock.JSONEncode(Message{Message: "ok"}), + servermock.CheckRequestJSONBody(`{"name":"_acme-challenge.www","type":"TXT","ttl":30,"content":["aaa","bbb"]}`)). + Build(t) record := Record{ Name: "_acme-challenge.www", @@ -128,20 +77,10 @@ func TestClient_CreateUpdateRecord(t *testing.T) { } func TestClient_CreateUpdateRecord_error(t *testing.T) { - client, handler := setupTest(t) - - handler.HandleFunc("/lego.wtf", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - err := json.NewEncoder(rw).Encode(Message{ErrorMsg: "parameter type must be cname, txt, tlsa, caa, a or aaaa"}) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("POST /lego.wtf", + servermock.JSONEncode(Message{ErrorMsg: "parameter type must be cname, txt, tlsa, caa, a or aaaa"})). + Build(t) record := Record{ Name: "_acme-challenge.www", @@ -154,33 +93,11 @@ func TestClient_CreateUpdateRecord_error(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client, handler := setupTest(t) - - handler.HandleFunc("/lego.wtf", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - content, err := io.ReadAll(req.Body) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - expectedRequest := `{"name":"_acme-challenge.www","type":"TXT"}` - - if !assert.JSONEq(t, expectedRequest, string(content)) { - http.Error(rw, "invalid content", http.StatusBadRequest) - return - } - - err = json.NewEncoder(rw).Encode(Message{Message: "ok"}) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("DELETE /lego.wtf", + servermock.JSONEncode(Message{Message: "ok"}), + servermock.CheckRequestJSONBody(`{"name":"_acme-challenge.www","type":"TXT"}`)). + Build(t) record := Record{ Name: "_acme-challenge.www", @@ -195,20 +112,10 @@ func TestClient_DeleteRecord(t *testing.T) { } func TestClient_DeleteRecord_error(t *testing.T) { - client, handler := setupTest(t) - - handler.HandleFunc("/lego.wtf", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - err := json.NewEncoder(rw).Encode(Message{ErrorMsg: "parameter type must be cname, txt, tlsa, caa, a or aaaa"}) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("DELETE /lego.wtf", + servermock.JSONEncode(Message{ErrorMsg: "parameter type must be cname, txt, tlsa, caa, a or aaaa"})). + Build(t) record := Record{ Name: "_acme-challenge.www", diff --git a/providers/dns/shellrent/internal/client_test.go b/providers/dns/shellrent/internal/client_test.go index c160ddf56..7047ce835 100644 --- a/providers/dns/shellrent/internal/client_test.go +++ b/providers/dns/shellrent/internal/client_test.go @@ -1,68 +1,33 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, method, pattern string, status int, file string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret") + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusBadRequest) - return - } - - auth := req.Header.Get(authorizationHeader) - if auth != "user.secret" { - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - if file == "" { - rw.WriteHeader(status) - return - } - - open, err := os.Open(filepath.Join("fixtures", file)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client := NewClient("user", "secret") - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("user.secret")) } func TestClient_ListServices(t *testing.T) { - client := setupTest(t, http.MethodGet, "/purchase", http.StatusOK, "purchase.json") + client := mockBuilder(). + Route("GET /purchase", servermock.ResponseFromFixture("purchase.json")). + Build(t) services, err := client.ListServices(t.Context()) require.NoError(t, err) @@ -73,21 +38,29 @@ func TestClient_ListServices(t *testing.T) { } func TestClient_ListServices_error(t *testing.T) { - client := setupTest(t, http.MethodGet, "/purchase", http.StatusOK, "error.json") + client := mockBuilder(). + Route("GET /purchase", servermock.ResponseFromFixture("error.json")). + Build(t) _, err := client.ListServices(t.Context()) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_ListServices_error_status(t *testing.T) { - client := setupTest(t, http.MethodGet, "/purchase", http.StatusUnauthorized, "error.json") + client := mockBuilder(). + Route("GET /purchase", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.ListServices(t.Context()) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_GetServiceDetails(t *testing.T) { - client := setupTest(t, http.MethodGet, "/purchase/details/123", http.StatusOK, "purchase-details.json") + client := mockBuilder(). + Route("GET /purchase/details/123", servermock.ResponseFromFixture("purchase-details.json")). + Build(t) services, err := client.GetServiceDetails(t.Context(), 123) require.NoError(t, err) @@ -98,21 +71,29 @@ func TestClient_GetServiceDetails(t *testing.T) { } func TestClient_GetServiceDetails_error(t *testing.T) { - client := setupTest(t, http.MethodGet, "/purchase/details/123", http.StatusOK, "error.json") + client := mockBuilder(). + Route("GET /purchase/details/123", servermock.ResponseFromFixture("error.json")). + Build(t) _, err := client.GetServiceDetails(t.Context(), 123) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_GetServiceDetails_error_status(t *testing.T) { - client := setupTest(t, http.MethodGet, "/purchase/details/123", http.StatusUnauthorized, "error.json") + client := mockBuilder(). + Route("GET /purchase/details/123", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.GetServiceDetails(t.Context(), 123) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_GetDomainDetails(t *testing.T) { - client := setupTest(t, http.MethodGet, "/domain/details/123", http.StatusOK, "domain-details.json") + client := mockBuilder(). + Route("GET /domain/details/123", servermock.ResponseFromFixture("domain-details.json")). + Build(t) services, err := client.GetDomainDetails(t.Context(), 123) require.NoError(t, err) @@ -123,21 +104,29 @@ func TestClient_GetDomainDetails(t *testing.T) { } func TestClient_GetDomainDetails_error(t *testing.T) { - client := setupTest(t, http.MethodGet, "/domain/details/123", http.StatusOK, "error.json") + client := mockBuilder(). + Route("GET /domain/details/123", servermock.ResponseFromFixture("error.json")). + Build(t) _, err := client.GetDomainDetails(t.Context(), 123) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_GetDomainDetails_error_status(t *testing.T) { - client := setupTest(t, http.MethodGet, "/domain/details/123", http.StatusUnauthorized, "error.json") + client := mockBuilder(). + Route("GET /domain/details/123", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.GetDomainDetails(t.Context(), 123) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_CreateRecord(t *testing.T) { - client := setupTest(t, http.MethodPost, "/dns_record/store/123", http.StatusOK, "dns_record-store.json") + client := mockBuilder(). + Route("POST /dns_record/store/123", servermock.ResponseFromFixture("dns_record-store.json")). + Build(t) services, err := client.CreateRecord(t.Context(), 123, Record{}) require.NoError(t, err) @@ -148,35 +137,49 @@ func TestClient_CreateRecord(t *testing.T) { } func TestClient_CreateRecord_error(t *testing.T) { - client := setupTest(t, http.MethodPost, "/dns_record/store/123", http.StatusOK, "error.json") + client := mockBuilder(). + Route("POST /dns_record/store/123", servermock.ResponseFromFixture("error.json")). + Build(t) _, err := client.CreateRecord(t.Context(), 123, Record{}) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_CreateRecord_error_status(t *testing.T) { - client := setupTest(t, http.MethodPost, "/dns_record/store/123", http.StatusUnauthorized, "error.json") + client := mockBuilder(). + Route("POST /dns_record/store/123", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.CreateRecord(t.Context(), 123, Record{}) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, http.MethodDelete, "/dns_record/remove/123/456", http.StatusOK, "dns_record-remove.json") + client := mockBuilder(). + Route("DELETE /dns_record/remove/123/456", servermock.ResponseFromFixture("dns_record-remove.json")). + Build(t) err := client.DeleteRecord(t.Context(), 123, 456) require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, http.MethodDelete, "/dns_record/remove/123/456", http.StatusOK, "error.json") + client := mockBuilder(). + Route("DELETE /dns_record/remove/123/456", servermock.ResponseFromFixture("error.json")). + Build(t) err := client.DeleteRecord(t.Context(), 123, 456) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") } func TestClient_DeleteRecord_error_status(t *testing.T) { - client := setupTest(t, http.MethodDelete, "/dns_record/remove/123/456", http.StatusUnauthorized, "error.json") + client := mockBuilder(). + Route("DELETE /dns_record/remove/123/456", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) err := client.DeleteRecord(t.Context(), 123, 456) require.EqualError(t, err, "code 2: Token di autorizzazione non valido") diff --git a/providers/dns/simply/internal/client_test.go b/providers/dns/simply/internal/client_test.go index e822b03cf..83aa714bf 100644 --- a/providers/dns/simply/internal/client_test.go +++ b/providers/dns/simply/internal/client_test.go @@ -1,24 +1,37 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestClient_GetRecords(t *testing.T) { - client, mux := setupTest(t) +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("accountname", "apikey") + if err != nil { + return nil, err + } - mux.HandleFunc("/accountname/apikey/my/products/azone01/dns/records", mockHandler(http.MethodGet, http.StatusOK, "get_records.json")) + client.baseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders()) +} + +func TestClient_GetRecords(t *testing.T) { + client := mockBuilder(). + Route("GET /accountname/apikey/my/products/azone01/dns/records", + servermock.ResponseFromFixture("get_records.json")). + Build(t) records, err := client.GetRecords(t.Context(), "azone01") require.NoError(t, err) @@ -62,9 +75,11 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_GetRecords_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/accountname/apikey/my/products/azone01/dns/records", mockHandler(http.MethodGet, http.StatusBadRequest, "bad_auth_error.json")) + client := mockBuilder(). + Route("GET /accountname/apikey/my/products/azone01/dns/records", + servermock.ResponseFromFixture("bad_auth_error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) records, err := client.GetRecords(t.Context(), "azone01") require.Error(t, err) @@ -73,9 +88,10 @@ func TestClient_GetRecords_error(t *testing.T) { } func TestClient_AddRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/accountname/apikey/my/products/azone01/dns/records", mockHandler(http.MethodPost, http.StatusOK, "add_record.json")) + client := mockBuilder(). + Route("POST /accountname/apikey/my/products/azone01/dns/records", + servermock.ResponseFromFixture("add_record.json")). + Build(t) record := Record{ Name: "arecord01", @@ -92,9 +108,11 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/accountname/apikey/my/products/azone01/dns/records", mockHandler(http.MethodPost, http.StatusNotFound, "bad_zone_error.json")) + client := mockBuilder(). + Route("POST /accountname/apikey/my/products/azone01/dns/records", + servermock.ResponseFromFixture("bad_zone_error.json"). + WithStatusCode(http.StatusNotFound)). + Build(t) record := Record{ Name: "arecord01", @@ -111,9 +129,10 @@ func TestClient_AddRecord_error(t *testing.T) { } func TestClient_EditRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/accountname/apikey/my/products/azone01/dns/records/123456789", mockHandler(http.MethodPut, http.StatusOK, "success.json")) + client := mockBuilder(). + Route("PUT /accountname/apikey/my/products/azone01/dns/records/123456789", + servermock.ResponseFromFixture("success.json")). + Build(t) record := Record{ Name: "arecord01", @@ -128,9 +147,11 @@ func TestClient_EditRecord(t *testing.T) { } func TestClient_EditRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/accountname/apikey/my/products/azone01/dns/records/123456789", mockHandler(http.MethodPut, http.StatusNotFound, "invalid_record_id.json")) + client := mockBuilder(). + Route("PUT /accountname/apikey/my/products/azone01/dns/records/123456789", + servermock.ResponseFromFixture("invalid_record_id.json"). + WithStatusCode(http.StatusNotFound)). + Build(t) record := Record{ Name: "arecord01", @@ -145,63 +166,22 @@ func TestClient_EditRecord_error(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/accountname/apikey/my/products/azone01/dns/records/123456789", mockHandler(http.MethodDelete, http.StatusOK, "success.json")) + client := mockBuilder(). + Route("DELETE /accountname/apikey/my/products/azone01/dns/records/123456789", + servermock.ResponseFromFixture("success.json")). + Build(t) err := client.DeleteRecord(t.Context(), "azone01", 123456789) require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/accountname/apikey/my/products/azone01/dns/records/123456789", mockHandler(http.MethodDelete, http.StatusNotFound, "invalid_record_id.json")) + client := mockBuilder(). + Route("DELETE /accountname/apikey/my/products/azone01/dns/records/123456789", + servermock.ResponseFromFixture("invalid_record_id.json"). + WithStatusCode(http.StatusNotFound)). + Build(t) err := client.DeleteRecord(t.Context(), "azone01", 123456789) require.Error(t, err) } - -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client, err := NewClient("accountname", "apikey") - require.NoError(t, err) - - client.baseURL, _ = url.Parse(server.URL) - - return client, mux -} - -func mockHandler(method string, statusCode int, filename string) func(http.ResponseWriter, *http.Request) { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) - return - } - - if filename == "" { - rw.WriteHeader(statusCode) - return - } - - file, err := os.Open(filepath.FromSlash(path.Join("./fixtures", filename))) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - rw.WriteHeader(statusCode) - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } -} diff --git a/providers/dns/sonic/internal/client_test.go b/providers/dns/sonic/internal/client_test.go index 618538780..751ccee8f 100644 --- a/providers/dns/sonic/internal/client_test.go +++ b/providers/dns/sonic/internal/client_test.go @@ -1,31 +1,23 @@ package internal import ( - "fmt" - "net/http" "net/http/httptest" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, body string) *Client { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/host", func(rw http.ResponseWriter, req *http.Request) { - _, _ = fmt.Fprintln(rw, body) - }) - +func setupClient(server *httptest.Server) (*Client, error) { client, err := NewClient("foo", "secret") - require.NoError(t, err) + if err != nil { + return nil, err + } client.baseURL = server.URL + client.HTTPClient = server.Client() - return client + return client, nil } func TestClient_SetRecord(t *testing.T) { @@ -50,7 +42,11 @@ func TestClient_SetRecord(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - client := setupTest(t, test.response) + client := servermock.NewBuilder[*Client](setupClient, servermock.CheckHeader().WithJSONHeaders()). + Route("PUT /host", + servermock.RawStringResponse(test.response), + servermock.CheckRequestJSONBody(`{"userid":"foo","apikey":"secret","hostname":"example.com","value":"txttxttxt","ttl":10,"type":"TXT"}`)). + Build(t) err := client.SetRecord(t.Context(), "example.com", "txttxttxt", 10) test.assert(t, err) diff --git a/providers/dns/spaceship/internal/client_test.go b/providers/dns/spaceship/internal/client_test.go index ec6787f8e..f32843652 100644 --- a/providers/dns/spaceship/internal/client_test.go +++ b/providers/dns/spaceship/internal/client_test.go @@ -1,58 +1,40 @@ package internal import ( - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, status int, filename string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("key", "secret") + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if filename == "" { - rw.WriteHeader(status) - return - } - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client, err := NewClient("key", "secret") - require.NoError(t, err) - - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + With("X-Api-Key", "key"). + With("X-Api-Secret", "secret"), + ) } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, "PUT /dns/records/example.com", http.StatusOK, "") + client := mockBuilder(). + Route("PUT /dns/records/example.com", nil, + servermock.CheckRequestJSONBody(`{"items":[{"type":"TXT","name":"@","ttl":60}]}`)). + Build(t) record := Record{ Type: "TXT", @@ -65,7 +47,11 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, "PUT /dns/records/example.com", http.StatusUnprocessableEntity, "error.json") + client := mockBuilder(). + Route("PUT /dns/records/example.com", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnprocessableEntity)). + Build(t) record := Record{ Type: "TXT", @@ -78,7 +64,10 @@ func TestClient_AddRecord_error(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, "DELETE /dns/records/example.com", http.StatusOK, "") + client := mockBuilder(). + Route("DELETE /dns/records/example.com", nil, + servermock.CheckRequestJSONBody(`[{"type":"TXT","name":"@","ttl":60}]`)). + Build(t) record := Record{ Type: "TXT", @@ -91,7 +80,11 @@ func TestClient_DeleteRecord(t *testing.T) { } func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, "DELETE /dns/records/example.com", http.StatusUnprocessableEntity, "error.json") + client := mockBuilder(). + Route("DELETE /dns/records/example.com", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnprocessableEntity)). + Build(t) record := Record{ Type: "TXT", @@ -104,7 +97,10 @@ func TestClient_DeleteRecord_error(t *testing.T) { } func TestClient_GetRecords(t *testing.T) { - client := setupTest(t, "GET /dns/records/example.com", http.StatusOK, "get-records.json") + client := mockBuilder(). + Route("GET /dns/records/example.com", + servermock.ResponseFromFixture("get-records.json")). + Build(t) records, err := client.GetRecords(t.Context(), "example.com") require.NoError(t, err) @@ -117,7 +113,11 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_GetRecords_error(t *testing.T) { - client := setupTest(t, "GET /dns/records/example.com", http.StatusUnprocessableEntity, "error.json") + client := mockBuilder(). + Route("GET /dns/records/example.com", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnprocessableEntity)). + Build(t) _, err := client.GetRecords(t.Context(), "example.com") require.EqualError(t, err, "^$, name: The domain name contains invalid characters") diff --git a/providers/dns/stackpath/internal/client_test.go b/providers/dns/stackpath/internal/client_test.go index cb56ef728..5195aa973 100644 --- a/providers/dns/stackpath/internal/client_test.go +++ b/providers/dns/stackpath/internal/client_test.go @@ -1,47 +1,37 @@ package internal import ( + "context" "net/http" "net/http/httptest" "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(context.Background(), "STACK_ID", "CLIENT_ID", "CLIENT_SECRET") + client.httpClient = server.Client() + client.baseURL, _ = url.Parse(server.URL + "/") - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient(t.Context(), "STACK_ID", "CLIENT_ID", "CLIENT_SECRET") - client.httpClient = server.Client() - client.baseURL, _ = url.Parse(server.URL + "/") - - return client, mux + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(), + ) } func TestClient_GetZoneRecords(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/STACK_ID/zones/A/records", func(w http.ResponseWriter, _ *http.Request) { - content := ` - { - "records": [ - {"id":"1","name":"foo1","type":"TXT","ttl":120,"data":"txtTXTtxt"}, - {"id":"2","name":"foo2","type":"TXT","ttl":121,"data":"TXTtxtTXT"} - ] - }` - - _, err := w.Write([]byte(content)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("GET /STACK_ID/zones/A/records", + servermock.ResponseFromFixture("get_zone_records.json"), + servermock.CheckQueryParameter().Strict(). + With("page_request.filter", "name='foo1' and type='TXT'")). + Build(t) records, err := client.GetZoneRecords(t.Context(), "foo1", &Zone{ID: "A", Domain: "test"}) require.NoError(t, err) @@ -55,22 +45,14 @@ func TestClient_GetZoneRecords(t *testing.T) { } func TestClient_GetZoneRecords_apiError(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/STACK_ID/zones/A/records", func(w http.ResponseWriter, _ *http.Request) { - content := ` + client := mockBuilder(). + Route("GET /STACK_ID/zones/A/records", + servermock.RawStringResponse(` { "code": 401, "error": "an unauthorized request is attempted." -}` - - w.WriteHeader(http.StatusUnauthorized) - _, err := w.Write([]byte(content)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) +}`).WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.GetZoneRecords(t.Context(), "foo1", &Zone{ID: "A", Domain: "test"}) @@ -79,47 +61,12 @@ func TestClient_GetZoneRecords_apiError(t *testing.T) { } func TestClient_GetZones(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/STACK_ID/zones", func(w http.ResponseWriter, _ *http.Request) { - content := ` -{ - "pageInfo": { - "totalCount": "5", - "hasPreviousPage": false, - "hasNextPage": false, - "startCursor": "1", - "endCursor": "1" - }, - "zones": [ - { - "stackId": "my_stack", - "accountId": "my_account", - "id": "A", - "domain": "foo.com", - "version": "1", - "labels": { - "property1": "val1", - "property2": "val2" - }, - "created": "2018-10-07T02:31:49Z", - "updated": "2018-10-07T02:31:49Z", - "nameservers": [ - "1.1.1.1" - ], - "verified": "2018-10-07T02:31:49Z", - "status": "ACTIVE", - "disabled": false - } - ] -}` - - _, err := w.Write([]byte(content)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("GET /STACK_ID/zones", + servermock.ResponseFromFixture("get_zones.json"), + servermock.CheckQueryParameter().Strict(). + With("page_request.filter", "domain='foo.com'")). + Build(t) zone, err := client.GetZones(t.Context(), "sub.foo.com") require.NoError(t, err) diff --git a/providers/dns/stackpath/internal/fixtures/get_zone_records.json b/providers/dns/stackpath/internal/fixtures/get_zone_records.json new file mode 100644 index 000000000..1556d08fe --- /dev/null +++ b/providers/dns/stackpath/internal/fixtures/get_zone_records.json @@ -0,0 +1,6 @@ +{ + "records": [ + {"id":"1","name":"foo1","type":"TXT","ttl":120,"data":"txtTXTtxt"}, + {"id":"2","name":"foo2","type":"TXT","ttl":121,"data":"TXTtxtTXT"} + ] +} diff --git a/providers/dns/stackpath/internal/fixtures/get_zones.json b/providers/dns/stackpath/internal/fixtures/get_zones.json new file mode 100644 index 000000000..7630ef4fe --- /dev/null +++ b/providers/dns/stackpath/internal/fixtures/get_zones.json @@ -0,0 +1,30 @@ +{ + "pageInfo": { + "totalCount": "5", + "hasPreviousPage": false, + "hasNextPage": false, + "startCursor": "1", + "endCursor": "1" + }, + "zones": [ + { + "stackId": "my_stack", + "accountId": "my_account", + "id": "A", + "domain": "foo.com", + "version": "1", + "labels": { + "property1": "val1", + "property2": "val2" + }, + "created": "2018-10-07T02:31:49Z", + "updated": "2018-10-07T02:31:49Z", + "nameservers": [ + "1.1.1.1" + ], + "verified": "2018-10-07T02:31:49Z", + "status": "ACTIVE", + "disabled": false + } + ] +} diff --git a/providers/dns/technitium/internal/client_test.go b/providers/dns/technitium/internal/client_test.go index f8b0d049b..cd6914918 100644 --- a/providers/dns/technitium/internal/client_test.go +++ b/providers/dns/technitium/internal/client_test.go @@ -1,50 +1,39 @@ package internal import ( - "io" - "net/http" "net/http/httptest" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern, filename string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(server.URL, "secret") + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client.HTTPClient = server.Client() - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client, err := NewClient(server.URL, "secret") - require.NoError(t, err) - - client.HTTPClient = server.Client() - - return client + return client, nil + }, + servermock.CheckHeader().WithContentTypeFromURLEncoded()) } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, "POST /api/zones/records/add", "add-record.json") + client := mockBuilder(). + Route("POST /api/zones/records/add", + servermock.ResponseFromFixture("add-record.json"), + servermock.CheckForm().Strict(). + With("domain", "_acme-challenge.example.com"). + With("text", "txtTXTtxt"). + With("type", "TXT"). + With("token", "secret")). + Build(t) record := Record{ Domain: "_acme-challenge.example.com", @@ -61,7 +50,10 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, "POST /api/zones/records/add", "error.json") + client := mockBuilder(). + Route("POST /api/zones/records/add", + servermock.ResponseFromFixture("error.json")). + Build(t) record := Record{ Domain: "_acme-challenge.example.com", @@ -76,7 +68,15 @@ func TestClient_AddRecord_error(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, "POST /api/zones/records/delete", "delete-record.json") + client := mockBuilder(). + Route("POST /api/zones/records/delete", + servermock.ResponseFromFixture("delete-record.json"), + servermock.CheckForm().Strict(). + With("domain", "_acme-challenge.example.com"). + With("text", "txtTXTtxt"). + With("type", "TXT"). + With("token", "secret")). + Build(t) record := Record{ Domain: "_acme-challenge.example.com", @@ -89,7 +89,10 @@ func TestClient_DeleteRecord(t *testing.T) { } func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, "POST /api/zones/records/delete", "error.json") + client := mockBuilder(). + Route("POST /api/zones/records/delete", + servermock.ResponseFromFixture("error.json")). + Build(t) record := Record{ Domain: "_acme-challenge.example.com", diff --git a/providers/dns/timewebcloud/internal/client_test.go b/providers/dns/timewebcloud/internal/client_test.go index c5a861f68..9d16ba4c5 100644 --- a/providers/dns/timewebcloud/internal/client_test.go +++ b/providers/dns/timewebcloud/internal/client_test.go @@ -1,86 +1,35 @@ package internal import ( - "bytes" - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(OAuthStaticAccessToken(server.Client(), "secret")) + client.baseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient(OAuthStaticAccessToken(server.Client(), "secret")) - client.baseURL, _ = url.Parse(server.URL) - - return client, mux -} - -func checkAuthorizationHeader(req *http.Request) error { - val := req.Header.Get("Authorization") - if val != "Bearer secret" { - return fmt.Errorf("invalid header value, got: %s want %s", val, "Bearer secret") - } - return nil -} - -func writeResponse(rw http.ResponseWriter, statusCode int, filename string) error { - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - return err - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(statusCode) - - _, err = io.Copy(rw, file) - if err != nil { - return err - } - - return nil + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Bearer secret"), + ) } func TestClient_CreateRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("POST /v1/domains/example.com/dns-records", func(rw http.ResponseWriter, req *http.Request) { - err := checkAuthorizationHeader(req) - if err != nil { - http.Error(rw, err.Error(), http.StatusUnauthorized) - return - } - - content, err := io.ReadAll(req.Body) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - if string(bytes.TrimSpace(content)) != `{"type":"TXT","value":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI","subdomain":"_acme-challenge"}` { - http.Error(rw, "invalid request body: "+string(content), http.StatusBadRequest) - return - } - - err = writeResponse(rw, http.StatusOK, "createDomainDNSRecord.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("POST /v1/domains/example.com/dns-records", + servermock.ResponseFromFixture("createDomainDNSRecord.json"), + servermock.CheckRequestJSONBody(`{"type":"TXT","value":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI","subdomain":"_acme-challenge"}`)). + Build(t) payload := DNSRecord{ Type: "TXT", @@ -100,15 +49,11 @@ func TestClient_CreateRecord(t *testing.T) { } func TestClient_CreateRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("POST /v1/domains/example.com/dns-records", func(rw http.ResponseWriter, _ *http.Request) { - err := writeResponse(rw, http.StatusBadRequest, "error_bad_request.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("POST /v1/domains/example.com/dns-records", + servermock.ResponseFromFixture("error_bad_request.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) _, err := client.CreateRecord(t.Context(), "example.com.", DNSRecord{}) require.Error(t, err) @@ -117,32 +62,22 @@ func TestClient_CreateRecord_error(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("DELETE /v1/domains/example.com/dns-records/123", func(rw http.ResponseWriter, req *http.Request) { - err := checkAuthorizationHeader(req) - if err != nil { - http.Error(rw, err.Error(), http.StatusUnauthorized) - return - } - - rw.WriteHeader(http.StatusNoContent) - }) + client := mockBuilder(). + Route("DELETE /v1/domains/example.com/dns-records/123", + servermock.Noop(). + WithStatusCode(http.StatusNoContent)). + Build(t) err := client.DeleteRecord(t.Context(), "example.com.", 123) require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("DELETE /v1/domains/example.com/dns-records/123", func(rw http.ResponseWriter, _ *http.Request) { - err := writeResponse(rw, http.StatusBadRequest, "error_unauthorized.json") - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + client := mockBuilder(). + Route("DELETE /v1/domains/example.com/dns-records/123", + servermock.ResponseFromFixture("error_unauthorized.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) err := client.DeleteRecord(t.Context(), "example.com.", 123) require.Error(t, err) diff --git a/providers/dns/variomedia/internal/client_test.go b/providers/dns/variomedia/internal/client_test.go index 0daa64f7a..24778bdaf 100644 --- a/providers/dns/variomedia/internal/client_test.go +++ b/providers/dns/variomedia/internal/client_test.go @@ -1,67 +1,37 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" - "os" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("secret") + client.baseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient("secret") - client.baseURL, _ = url.Parse(server.URL) - - return client, mux -} - -func mockHandler(method, filename string) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("invalid method, got %s want %s", req.Method, method), http.StatusBadRequest) - return - } - - filename = "./fixtures/" + filename - statusCode := http.StatusOK - - if req.Header.Get(authorizationHeader) != "token secret" { - statusCode = http.StatusUnauthorized - filename = "./fixtures/error.json" - } - - rw.WriteHeader(statusCode) - - file, err := os.Open(filename) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - } + return client, nil + }, + servermock.CheckHeader(). + WithAccept("application/vnd.variomedia.v1+json"). + WithAuthorization("token secret")) } func TestClient_CreateDNSRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/dns-records", mockHandler(http.MethodPost, "POST_dns-records.json")) + client := mockBuilder(). + Route("POST /dns-records", + servermock.ResponseFromFixture("POST_dns-records.json"), + servermock.CheckHeader(). + WithContentType("application/vnd.api+json"), + servermock.CheckRequestJSONBody(`{"data":{"type":"dns-record","attributes":{"record_type":"TXT","name":"_acme-challenge","domain":"example.com","data":"test","ttl":300}}}`)). + Build(t) record := DNSRecord{ RecordType: "TXT", @@ -107,9 +77,10 @@ func TestClient_CreateDNSRecord(t *testing.T) { } func TestClient_DeleteDNSRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/dns-records/test", mockHandler(http.MethodDelete, "DELETE_dns-records_pending.json")) + client := mockBuilder(). + Route("DELETE /dns-records/test", + servermock.ResponseFromFixture("DELETE_dns-records_pending.json")). + Build(t) resp, err := client.DeleteDNSRecord(t.Context(), "test") require.NoError(t, err) @@ -142,9 +113,10 @@ func TestClient_DeleteDNSRecord(t *testing.T) { } func TestClient_GetJob(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/queue-jobs/test", mockHandler(http.MethodGet, "GET_queue-jobs.json")) + client := mockBuilder(). + Route("GET /queue-jobs/test", + servermock.ResponseFromFixture("GET_queue-jobs.json")). + Build(t) resp, err := client.GetJob(t.Context(), "test") require.NoError(t, err) diff --git a/providers/dns/vegadns/fixtures/create_record.json b/providers/dns/vegadns/fixtures/create_record.json new file mode 100644 index 000000000..2199130b9 --- /dev/null +++ b/providers/dns/vegadns/fixtures/create_record.json @@ -0,0 +1,12 @@ +{ + "status": "ok", + "record": { + "name": "_acme-challenge.example.com", + "value": "my_challenge", + "record_type": "TXT", + "ttl": 3600, + "record_id": 3, + "location_id": null, + "domain_id": 1 + } +} diff --git a/providers/dns/vegadns/fixtures/record_delete.json b/providers/dns/vegadns/fixtures/record_delete.json new file mode 100644 index 000000000..bc4e01029 --- /dev/null +++ b/providers/dns/vegadns/fixtures/record_delete.json @@ -0,0 +1,3 @@ +{ + "status": "ok" +} diff --git a/providers/dns/vegadns/fixtures/records.json b/providers/dns/vegadns/fixtures/records.json new file mode 100644 index 000000000..9fa41ce7a --- /dev/null +++ b/providers/dns/vegadns/fixtures/records.json @@ -0,0 +1,43 @@ +{ + "status": "ok", + "total_records": 2, + "domain": { + "status": "active", + "domain": "example.com", + "owner_id": 0, + "domain_id": 1 + }, + "records": [ + { + "retry": "2048", + "minimum": "2560", + "refresh": "16384", + "email": "hostmaster.example.com", + "record_type": "SOA", + "expire": "1048576", + "ttl": 86400, + "record_id": 1, + "nameserver": "ns1.example.com", + "domain_id": 1, + "serial": "" + }, + { + "name": "example.com", + "value": "ns1.example.com", + "record_type": "NS", + "ttl": 3600, + "record_id": 2, + "location_id": null, + "domain_id": 1 + }, + { + "name": "_acme-challenge.example.com", + "value": "my_challenge", + "record_type": "TXT", + "ttl": 3600, + "record_id": 3, + "location_id": null, + "domain_id": 1 + } + ] +} diff --git a/providers/dns/vegadns/fixtures/token.json b/providers/dns/vegadns/fixtures/token.json new file mode 100644 index 000000000..39ab1a4a9 --- /dev/null +++ b/providers/dns/vegadns/fixtures/token.json @@ -0,0 +1,5 @@ +{ + "access_token": "699dd4ff-e381-46b8-8bf8-5de49dd56c1f", + "token_type": "bearer", + "expires_in": 3600 +} diff --git a/providers/dns/vegadns/vegadns_mock_test.go b/providers/dns/vegadns/vegadns_mock_test.go deleted file mode 100644 index 5a705e092..000000000 --- a/providers/dns/vegadns/vegadns_mock_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package vegadns - -const tokenResponseMock = ` -{ - "access_token":"699dd4ff-e381-46b8-8bf8-5de49dd56c1f", - "token_type":"bearer", - "expires_in":3600 -} -` - -const domainsResponseMock = ` -{ - "domains":[ - { - "domain_id":1, - "domain":"example.com", - "status":"active", - "owner_id":0 - } - ] -} -` - -const recordsResponseMock = ` -{ - "status":"ok", - "total_records":2, - "domain":{ - "status":"active", - "domain":"example.com", - "owner_id":0, - "domain_id":1 - }, - "records":[ - { - "retry":"2048", - "minimum":"2560", - "refresh":"16384", - "email":"hostmaster.example.com", - "record_type":"SOA", - "expire":"1048576", - "ttl":86400, - "record_id":1, - "nameserver":"ns1.example.com", - "domain_id":1, - "serial":"" - }, - { - "name":"example.com", - "value":"ns1.example.com", - "record_type":"NS", - "ttl":3600, - "record_id":2, - "location_id":null, - "domain_id":1 - }, - { - "name":"_acme-challenge.example.com", - "value":"my_challenge", - "record_type":"TXT", - "ttl":3600, - "record_id":3, - "location_id":null, - "domain_id":1 - } - ] -} -` - -const recordCreatedResponseMock = ` -{ - "status":"ok", - "record":{ - "name":"_acme-challenge.example.com", - "value":"my_challenge", - "record_type":"TXT", - "ttl":3600, - "record_id":3, - "location_id":null, - "domain_id":1 - } -} -` - -const recordDeletedResponseMock = `{"status": "ok"}` diff --git a/providers/dns/vegadns/vegadns_test.go b/providers/dns/vegadns/vegadns_test.go index 60f614c3b..48f54faab 100644 --- a/providers/dns/vegadns/vegadns_test.go +++ b/providers/dns/vegadns/vegadns_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -28,10 +29,7 @@ func TestDNSProvider_TimeoutSuccess(t *testing.T) { defer envTest.RestoreEnv() envTest.ClearEnv() - setupTest(t, muxSuccess()) - - provider, err := NewDNSProvider() - require.NoError(t, err) + provider := mockBuilder().Build(t) timeout, interval := provider.Timeout() assert.Equal(t, 12*time.Minute, timeout) @@ -42,20 +40,38 @@ func TestDNSProvider_Present(t *testing.T) { testCases := []struct { desc string handler http.Handler + builder *servermock.Builder[*DNSProvider] expectedError string }{ { - desc: "Success", - handler: muxSuccess(), + desc: "Success", + builder: mockBuilder(). + Route("POST /1.0/token", + servermock.ResponseFromFixture("token.json")). + Route("GET /1.0/domains", getDomainHandler()). + Route("POST /1.0/records", + servermock.ResponseFromFixture("create_record.json"). + WithStatusCode(http.StatusCreated)), }, { - desc: "FailToFindZone", - handler: muxFailToFindZone(), + desc: "FailToFindZone", + builder: mockBuilder(). + Route("POST /1.0/token", + servermock.ResponseFromFixture("token.json")). + Route("GET /1.0/domains", + servermock.Noop(). + WithStatusCode(http.StatusNotFound)), expectedError: "vegadns: can't find Authoritative Zone for _acme-challenge.example.com. in Present: Unable to find auth zone for fqdn _acme-challenge.example.com", }, { - desc: "FailToCreateTXT", - handler: muxFailToCreateTXT(), + desc: "FailToCreateTXT", + builder: mockBuilder(). + Route("POST /1.0/token", + servermock.ResponseFromFixture("token.json")). + Route("GET /1.0/domains", getDomainHandler()). + Route("POST /1.0/records", + servermock.Noop(). + WithStatusCode(http.StatusBadRequest)), expectedError: "vegadns: Got bad answer from VegaDNS on CreateTXT. Code: 400. Message: ", }, } @@ -65,12 +81,9 @@ func TestDNSProvider_Present(t *testing.T) { defer envTest.RestoreEnv() envTest.ClearEnv() - setupTest(t, test.handler) + provider := test.builder.Build(t) - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(testDomain, "token", "keyAuth") + err := provider.Present(testDomain, "token", "keyAuth") if test.expectedError == "" { assert.NoError(t, err) } else { @@ -83,21 +96,41 @@ func TestDNSProvider_Present(t *testing.T) { func TestDNSProvider_CleanUp(t *testing.T) { testCases := []struct { desc string - handler http.Handler + builder *servermock.Builder[*DNSProvider] expectedError string }{ { - desc: "Success", - handler: muxSuccess(), + desc: "Success", + builder: mockBuilder(). + Route("POST /1.0/token", + servermock.ResponseFromFixture("token.json")). + Route("GET /1.0/domains", getDomainHandler()). + Route("GET /1.0/records", + servermock.ResponseFromFixture("records.json"), + servermock.CheckQueryParameter().With("domain_id", "1")). + Route("DELETE /1.0/records/3", + servermock.ResponseFromFixture("record_delete.json")), }, { - desc: "FailToFindZone", - handler: muxFailToFindZone(), + desc: "FailToFindZone", + builder: mockBuilder(). + Route("POST /1.0/token", + servermock.ResponseFromFixture("token.json")). + Route("GET /1.0/domains", + servermock.Noop(). + WithStatusCode(http.StatusNotFound)), expectedError: "vegadns: can't find Authoritative Zone for _acme-challenge.example.com. in CleanUp: Unable to find auth zone for fqdn _acme-challenge.example.com", }, { - desc: "FailToGetRecordID", - handler: muxFailToGetRecordID(), + desc: "FailToGetRecordID", + builder: mockBuilder(). + Route("POST /1.0/token", + servermock.ResponseFromFixture("token.json")). + Route("GET /1.0/domains", getDomainHandler()). + Route("GET /1.0/records", + servermock.Noop(). + WithStatusCode(http.StatusNotFound), + servermock.CheckQueryParameter().With("domain_id", "1")), expectedError: "vegadns: couldn't get Record ID in CleanUp: Got bad answer from VegaDNS on GetRecordID. Code: 404. Message: ", }, } @@ -107,12 +140,9 @@ func TestDNSProvider_CleanUp(t *testing.T) { defer envTest.RestoreEnv() envTest.ClearEnv() - setupTest(t, test.handler) + provider := test.builder.Build(t) - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(testDomain, "token", "keyAuth") + err := provider.CleanUp(testDomain, "token", "keyAuth") if test.expectedError == "" { assert.NoError(t, err) } else { @@ -122,163 +152,36 @@ func TestDNSProvider_CleanUp(t *testing.T) { } } -func muxSuccess() *http.ServeMux { - mux := http.NewServeMux() - - mux.HandleFunc("/1.0/token", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, tokenResponseMock) +func getDomainHandler() http.HandlerFunc { + return func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Query().Get("search") == testDomain { + fmt.Fprint(rw, ` +{ + "domains":[ + { + "domain_id":1, + "domain":"example.com", + "status":"active", + "owner_id":0 + } + ] +} +`) return } - w.WriteHeader(http.StatusBadRequest) - }) - mux.HandleFunc("/1.0/domains", func(w http.ResponseWriter, r *http.Request) { - if r.URL.Query().Get("search") == "example.com" { - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, domainsResponseMock) - return - } - w.WriteHeader(http.StatusNotFound) - }) - - mux.HandleFunc("/1.0/records", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - if r.URL.Query().Get("domain_id") == "1" { - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, recordsResponseMock) - return - } - w.WriteHeader(http.StatusNotFound) - return - case http.MethodPost: - w.WriteHeader(http.StatusCreated) - fmt.Fprint(w, recordCreatedResponseMock) - return - } - w.WriteHeader(http.StatusBadRequest) - }) - - mux.HandleFunc("/1.0/records/3", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodDelete { - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, recordDeletedResponseMock) - return - } - w.WriteHeader(http.StatusBadRequest) - }) - - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - fmt.Printf("Not Found for Request: (%+v)\n\n", r) - }) - - return mux + rw.WriteHeader(http.StatusNotFound) + } } -func muxFailToFindZone() *http.ServeMux { - mux := http.NewServeMux() +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { + envTest.Apply(map[string]string{ + EnvKey: "key", + EnvSecret: "secret", + EnvURL: server.URL, + }) - mux.HandleFunc("/1.0/token", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, tokenResponseMock) - return - } - w.WriteHeader(http.StatusBadRequest) - }) - - mux.HandleFunc("/1.0/domains", func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - }) - - return mux -} - -func muxFailToCreateTXT() *http.ServeMux { - mux := http.NewServeMux() - - mux.HandleFunc("/1.0/token", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, tokenResponseMock) - return - } - w.WriteHeader(http.StatusBadRequest) - }) - - mux.HandleFunc("/1.0/domains", func(w http.ResponseWriter, r *http.Request) { - if r.URL.Query().Get("search") == testDomain { - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, domainsResponseMock) - return - } - w.WriteHeader(http.StatusNotFound) - }) - - mux.HandleFunc("/1.0/records", func(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - if r.URL.Query().Get("domain_id") == "1" { - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, recordsResponseMock) - return - } - w.WriteHeader(http.StatusNotFound) - return - case http.MethodPost: - w.WriteHeader(http.StatusBadRequest) - return - } - w.WriteHeader(http.StatusBadRequest) - }) - - return mux -} - -func muxFailToGetRecordID() *http.ServeMux { - mux := http.NewServeMux() - - mux.HandleFunc("/1.0/token", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, tokenResponseMock) - return - } - w.WriteHeader(http.StatusBadRequest) - }) - - mux.HandleFunc("/1.0/domains", func(w http.ResponseWriter, r *http.Request) { - if r.URL.Query().Get("search") == testDomain { - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, domainsResponseMock) - return - } - w.WriteHeader(http.StatusNotFound) - }) - - mux.HandleFunc("/1.0/records", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet { - w.WriteHeader(http.StatusNotFound) - return - } - w.WriteHeader(http.StatusBadRequest) - }) - - return mux -} - -func setupTest(t *testing.T, mux http.Handler) { - t.Helper() - - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - envTest.Apply(map[string]string{ - EnvKey: "key", - EnvSecret: "secret", - EnvURL: server.URL, + return NewDNSProvider() }) } diff --git a/providers/dns/vercel/internal/client_test.go b/providers/dns/vercel/internal/client_test.go index 2a8b4eaea..eb5ee501d 100644 --- a/providers/dns/vercel/internal/client_test.go +++ b/providers/dns/vercel/internal/client_test.go @@ -1,71 +1,38 @@ package internal import ( - "bytes" - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient(OAuthStaticAccessToken(server.Client(), "secret"), "123") + client.baseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := NewClient(OAuthStaticAccessToken(server.Client(), "secret"), "123") - client.baseURL, _ = url.Parse(server.URL) - - return client, mux + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Bearer secret")) } func TestClient_CreateRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v2/domains/example.com/records", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - - auth := req.Header.Get("Authorization") - if auth != "Bearer secret" { - http.Error(rw, fmt.Sprintf("invalid API token: %s", auth), http.StatusUnauthorized) - return - } - - teamID := req.URL.Query().Get("teamId") - if teamID != "123" { - http.Error(rw, fmt.Sprintf("invalid team ID: %s", teamID), http.StatusUnauthorized) - return - } - - reqBody, err := io.ReadAll(req.Body) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - expectedReqBody := `{"name":"_acme-challenge.example.com.","type":"TXT","value":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI","ttl":60}` - assert.Equal(t, expectedReqBody, string(bytes.TrimSpace(reqBody))) - - rw.WriteHeader(http.StatusOK) - _, err = fmt.Fprintf(rw, `{ + client := mockBuilder(). + Route("POST /v2/domains/example.com/records", + servermock.RawStringResponse(`{ "uid": "9e2eab60-0ba5-4dff-b481-2999c9764b84", "updated": 1 - }`) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + }`), + servermock.CheckRequestJSONBody(`{"name":"_acme-challenge.example.com.","type":"TXT","value":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI","ttl":60}`), + servermock.CheckQueryParameter().Strict(). + With("teamId", "123")). + Build(t) record := Record{ Name: "_acme-challenge.example.com.", @@ -86,27 +53,11 @@ func TestClient_CreateRecord(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client, mux := setupTest(t) - - mux.HandleFunc("/v2/domains/example.com/records/1234567", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, "invalid method: "+req.Method, http.StatusBadRequest) - return - } - auth := req.Header.Get("Authorization") - if auth != "Bearer secret" { - http.Error(rw, fmt.Sprintf("invalid API token: %s", auth), http.StatusUnauthorized) - return - } - - teamID := req.URL.Query().Get("teamId") - if teamID != "123" { - http.Error(rw, fmt.Sprintf("invalid team ID: %s", teamID), http.StatusUnauthorized) - return - } - - rw.WriteHeader(http.StatusOK) - }) + client := mockBuilder(). + Route("DELETE /v2/domains/example.com/records/1234567", nil, + servermock.CheckQueryParameter().Strict(). + With("teamId", "123")). + Build(t) err := client.DeleteRecord(t.Context(), "example.com.", "1234567") require.NoError(t, err) diff --git a/providers/dns/versio/fixtures/error_failToCreateTXT.json b/providers/dns/versio/fixtures/error_failToCreateTXT.json new file mode 100644 index 000000000..1e1784517 --- /dev/null +++ b/providers/dns/versio/fixtures/error_failToCreateTXT.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": 400, + "message": "ProcessError|DNS record invalid type _acme-challenge.example.eu. TST" + } +} diff --git a/providers/dns/versio/fixtures/error_failToFindZone.json b/providers/dns/versio/fixtures/error_failToFindZone.json new file mode 100644 index 000000000..635b2bda1 --- /dev/null +++ b/providers/dns/versio/fixtures/error_failToFindZone.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": 401, + "message": "ObjectDoesNotExist|Domain not found" + } +} diff --git a/providers/dns/versio/fixtures/token.json b/providers/dns/versio/fixtures/token.json new file mode 100644 index 000000000..0dc0dda25 --- /dev/null +++ b/providers/dns/versio/fixtures/token.json @@ -0,0 +1,5 @@ +{ + "access_token":"699dd4ff-e381-46b8-8bf8-5de49dd56c1f", + "token_type":"bearer", + "expires_in":3600 +} diff --git a/providers/dns/versio/internal/client_test.go b/providers/dns/versio/internal/client_test.go index 63b80ce4a..f3bf68c6d 100644 --- a/providers/dns/versio/internal/client_test.go +++ b/providers/dns/versio/internal/client_test.go @@ -1,61 +1,36 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern string, h http.HandlerFunc) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret") + client.HTTPClient = server.Client() + client.BaseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(pattern, h) - - client := NewClient("user", "secret") - client.HTTPClient = server.Client() - client.BaseURL, _ = url.Parse(server.URL) - - return client -} - -func writeFixture(rw http.ResponseWriter, filename string) { - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, _ = io.Copy(rw, file) + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithBasicAuth("user", "secret")) } func TestClient_GetDomain(t *testing.T) { - client := setupTest(t, "/domains/example.com", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - auth := req.Header.Get("Authorization") - if auth != "Basic dXNlcjpzZWNyZXQ=" { - http.Error(rw, "invalid credentials: "+auth, http.StatusUnauthorized) - return - } - - writeFixture(rw, "get-domain.json") - }) + client := mockBuilder(). + Route("GET /domains/example.com", + servermock.ResponseFromFixture("get-domain.json"), + servermock.CheckQueryParameter().Strict(). + With("show_dns_records", "true")). + Build(t) records, err := client.GetDomain(t.Context(), "example.com") require.NoError(t, err) @@ -79,36 +54,22 @@ func TestClient_GetDomain(t *testing.T) { } func TestClient_GetDomain_error(t *testing.T) { - client := setupTest(t, "/domains/example.com", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - rw.WriteHeader(http.StatusUnauthorized) - - writeFixture(rw, "get-domain-error.json") - }) + client := mockBuilder(). + Route("GET /domains/example.com", + servermock.ResponseFromFixture("get-domain-error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) _, err := client.GetDomain(t.Context(), "example.com") require.ErrorAs(t, err, &ErrorMessage{}) } func TestClient_UpdateDomain(t *testing.T) { - client := setupTest(t, "/domains/example.com/update", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - auth := req.Header.Get("Authorization") - if auth != "Basic dXNlcjpzZWNyZXQ=" { - http.Error(rw, "invalid credentials: "+auth, http.StatusUnauthorized) - return - } - - writeFixture(rw, "update-domain.json") - }) + client := mockBuilder(). + Route("POST /domains/example.com/update", + servermock.ResponseFromFixture("update-domain.json"), + servermock.CheckRequestJSONBodyFromFile("update-domain-request.json")). + Build(t) msg := &DomainInfo{DNSRecords: []Record{ {Type: "MX", Name: "example.com", Value: "fallback.axc.eu", Priority: 20, TTL: 3600}, @@ -147,16 +108,11 @@ func TestClient_UpdateDomain(t *testing.T) { } func TestClient_UpdateDomain_error(t *testing.T) { - client := setupTest(t, "/domains/example.com/update", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - rw.WriteHeader(http.StatusUnauthorized) - - writeFixture(rw, "update-domain.json") - }) + client := mockBuilder(). + Route("POST /domains/example.com/update", + servermock.ResponseFromFixture("update-domain-error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) msg := &DomainInfo{DNSRecords: []Record{ {Type: "MX", Name: "example.com", Value: "fallback.axc.eu", Priority: 20, TTL: 3600}, diff --git a/providers/dns/versio/internal/fixtures/update-domain-request.json b/providers/dns/versio/internal/fixtures/update-domain-request.json new file mode 100644 index 000000000..f351678fc --- /dev/null +++ b/providers/dns/versio/internal/fixtures/update-domain-request.json @@ -0,0 +1,78 @@ +{ + "dns_records": [ + { + "type": "MX", + "name": "example.com", + "value": "fallback.axc.eu", + "prio": 20, + "ttl": 3600 + }, + { + "type": "TXT", + "name": "example.com", + "value": "\"v=spf1 a mx ip4:127.0.0.1 a:spf.spamexperts.axc.nl ~all\"", + "ttl": 3600 + }, + { + "type": "A", + "name": "example.com", + "value": "185.13.227.159", + "ttl": 14400 + }, + { + "type": "A", + "name": "ftp.example.com", + "value": "185.13.227.159", + "ttl": 14400 + }, + { + "type": "A", + "name": "localhost.example.com", + "value": "185.13.227.159", + "ttl": 14400 + }, + { + "type": "A", + "name": "pop.example.com", + "value": "185.13.227.159", + "ttl": 14400 + }, + { + "type": "A", + "name": "smtp.example.com", + "value": "185.13.227.159", + "ttl": 14400 + }, + { + "type": "A", + "name": "www.example.com", + "value": "185.13.227.159", + "ttl": 14400 + }, + { + "type": "A", + "name": "dev.example.com", + "value": "185.13.227.159", + "ttl": 14400 + }, + { + "type": "A", + "name": "_domainkey.domain.com.example.com", + "value": "185.13.227.159", + "ttl": 14400 + }, + { + "type": "MX", + "name": "example.com", + "value": "spamfilter2.axc.eu", + "ttl": 3600 + }, + { + "type": "A", + "name": "redirect.example.com", + "value": "localhost", + "prio": 10, + "ttl": 14400 + } + ] +} diff --git a/providers/dns/versio/versio_mock_test.go b/providers/dns/versio/versio_mock_test.go deleted file mode 100644 index 07dc74e83..000000000 --- a/providers/dns/versio/versio_mock_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package versio - -const tokenResponseMock = ` -{ - "access_token":"699dd4ff-e381-46b8-8bf8-5de49dd56c1f", - "token_type":"bearer", - "expires_in":3600 -} -` - -const tokenFailToFindZoneMock = `{"error":{"code":401,"message":"ObjectDoesNotExist|Domain not found"}}` - -const tokenFailToCreateTXTMock = `{"error":{"code":400,"message":"ProcessError|DNS record invalid type _acme-challenge.example.eu. TST"}}` diff --git a/providers/dns/versio/versio_test.go b/providers/dns/versio/versio_test.go index 09040ab4c..ea1ccc221 100644 --- a/providers/dns/versio/versio_test.go +++ b/providers/dns/versio/versio_test.go @@ -1,14 +1,12 @@ package versio import ( - "fmt" - "io" "net/http" "net/http/httptest" "testing" - "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -125,21 +123,37 @@ func TestNewDNSProviderConfig(t *testing.T) { func TestDNSProvider_Present(t *testing.T) { testCases := []struct { desc string - handler http.Handler + builder *servermock.Builder[*DNSProvider] expectedError string }{ { - desc: "Success", - handler: muxSuccess(), + desc: "Success", + builder: mockBuilder(). + Route("GET /domains/example.com", + servermock.ResponseFromFixture("token.json"), + servermock.CheckQueryParameter().Strict(). + With("show_dns_records", "true")). + Route("POST /domains/example.com/update", + servermock.ResponseFromFixture("token.json")), }, { - desc: "FailToFindZone", - handler: muxFailToFindZone(), + desc: "FailToFindZone", + builder: mockBuilder(). + Route("GET /domains/example.com", + servermock.ResponseFromFixture("error_failToFindZone.json"). + WithStatusCode(http.StatusUnauthorized)), expectedError: `versio: [status code: 401] 401: ObjectDoesNotExist|Domain not found`, }, { - desc: "FailToCreateTXT", - handler: muxFailToCreateTXT(), + desc: "FailToCreateTXT", + builder: mockBuilder(). + Route("GET /domains/example.com", + servermock.ResponseFromFixture("token.json"), + servermock.CheckQueryParameter().Strict(). + With("show_dns_records", "true")). + Route("POST /domains/example.com/update", + servermock.ResponseFromFixture("error_failToCreateTXT.json"). + WithStatusCode(http.StatusBadRequest)), expectedError: `versio: [status code: 400] 400: ProcessError|DNS record invalid type _acme-challenge.example.eu. TST`, }, } @@ -149,17 +163,9 @@ func TestDNSProvider_Present(t *testing.T) { defer envTest.RestoreEnv() envTest.ClearEnv() - baseURL := setupTest(t, test.handler) + provider := test.builder.Build(t) - envTest.Apply(map[string]string{ - EnvUsername: "me@example.com", - EnvPassword: "secret", - EnvEndpoint: baseURL, - }) - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.Present(testDomain, "token", "keyAuth") + err := provider.Present(testDomain, "token", "keyAuth") if test.expectedError == "" { require.NoError(t, err) } else { @@ -172,16 +178,25 @@ func TestDNSProvider_Present(t *testing.T) { func TestDNSProvider_CleanUp(t *testing.T) { testCases := []struct { desc string - handler http.Handler + builder *servermock.Builder[*DNSProvider] expectedError string }{ { - desc: "Success", - handler: muxSuccess(), + desc: "Success", + builder: mockBuilder(). + Route("GET /domains/example.com", + servermock.ResponseFromFixture("token.json"), + servermock.CheckQueryParameter().Strict(). + With("show_dns_records", "true")). + Route("POST /domains/example.com/update", + servermock.ResponseFromFixture("token.json")), }, { - desc: "FailToFindZone", - handler: muxFailToFindZone(), + desc: "FailToFindZone", + builder: mockBuilder(). + Route("GET /domains/example.com", + servermock.ResponseFromFixture("error_failToFindZone.json"). + WithStatusCode(http.StatusUnauthorized)), expectedError: `versio: [status code: 401] 401: ObjectDoesNotExist|Domain not found`, }, } @@ -191,18 +206,9 @@ func TestDNSProvider_CleanUp(t *testing.T) { defer envTest.RestoreEnv() envTest.ClearEnv() - baseURL := setupTest(t, test.handler) + provider := test.builder.Build(t) - envTest.Apply(map[string]string{ - EnvUsername: "me@example.com", - EnvPassword: "secret", - EnvEndpoint: baseURL, - }) - - provider, err := NewDNSProvider() - require.NoError(t, err) - - err = provider.CleanUp(testDomain, "token", "keyAuth") + err := provider.CleanUp(testDomain, "token", "keyAuth") if test.expectedError == "" { require.NoError(t, err) } else { @@ -212,85 +218,6 @@ func TestDNSProvider_CleanUp(t *testing.T) { } } -func muxSuccess() *http.ServeMux { - mux := http.NewServeMux() - - mux.HandleFunc("/domains/example.com", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet && r.URL.Query().Get("show_dns_records") == "true" { - fmt.Fprint(w, tokenResponseMock) - return - } - w.WriteHeader(http.StatusBadRequest) - }) - - mux.HandleFunc("/domains/example.com/update", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { - fmt.Fprint(w, tokenResponseMock) - return - } - w.WriteHeader(http.StatusBadRequest) - }) - - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - log.Printf("unexpected request: %+v\n\n", r) - data, _ := io.ReadAll(r.Body) - defer func() { _ = r.Body.Close() }() - log.Println(string(data)) - http.NotFound(w, r) - }) - - return mux -} - -func muxFailToFindZone() *http.ServeMux { - mux := http.NewServeMux() - - mux.HandleFunc("/domains/example.com", func(w http.ResponseWriter, _ *http.Request) { - http.Error(w, tokenFailToFindZoneMock, http.StatusUnauthorized) - }) - - return mux -} - -func muxFailToCreateTXT() *http.ServeMux { - mux := http.NewServeMux() - - mux.HandleFunc("/domains/example.com", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet && r.URL.Query().Get("show_dns_records") == "true" { - fmt.Fprint(w, tokenResponseMock) - return - } - w.WriteHeader(http.StatusBadRequest) - }) - - mux.HandleFunc("/domains/example.com/update", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPost { - http.Error(w, tokenFailToCreateTXTMock, http.StatusBadRequest) - return - } - w.WriteHeader(http.StatusBadRequest) - }) - - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - log.Printf("unexpected request: %+v\n\n", r) - data, _ := io.ReadAll(r.Body) - defer func() { _ = r.Body.Close() }() - log.Println(string(data)) - http.NotFound(w, r) - }) - - return mux -} - -func setupTest(t *testing.T, handler http.Handler) string { - t.Helper() - - server := httptest.NewServer(handler) - t.Cleanup(server.Close) - - return server.URL -} - func TestLivePresent(t *testing.T) { if !envTest.IsLiveTest() { t.Skip("skipping live test") @@ -316,3 +243,15 @@ func TestLiveCleanUp(t *testing.T) { err = provider.CleanUp(envTest.GetDomain(), "", "123d==") require.NoError(t, err) } + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { + envTest.Apply(map[string]string{ + EnvUsername: "me@example.com", + EnvPassword: "secret", + EnvEndpoint: server.URL, + }) + + return NewDNSProvider() + }) +} diff --git a/providers/dns/vinyldns/mock_test.go b/providers/dns/vinyldns/mock_test.go deleted file mode 100644 index 54fd8e214..000000000 --- a/providers/dns/vinyldns/mock_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package vinyldns - -import ( - "fmt" - "net/http" - "net/http/httptest" - "os" - "sync" - "testing" - - "github.com/stretchr/testify/require" -) - -func setupTest(t *testing.T) (*http.ServeMux, *DNSProvider) { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - config := NewDefaultConfig() - config.AccessKey = "foo" - config.SecretKey = "bar" - config.Host = server.URL - - p, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - return mux, p -} - -type mockRouter struct { - debug bool - - mu sync.Mutex - routes map[string]map[string]http.HandlerFunc -} - -func newMockRouter() *mockRouter { - routes := map[string]map[string]http.HandlerFunc{ - http.MethodGet: {}, - http.MethodPost: {}, - http.MethodPut: {}, - http.MethodDelete: {}, - } - - return &mockRouter{ - routes: routes, - } -} - -func (h *mockRouter) Debug() *mockRouter { - h.debug = true - - return h -} - -func (h *mockRouter) Get(path string, statusCode int, filename string) *mockRouter { - h.add(http.MethodGet, path, statusCode, filename) - return h -} - -func (h *mockRouter) Post(path string, statusCode int, filename string) *mockRouter { - h.add(http.MethodPost, path, statusCode, filename) - return h -} - -func (h *mockRouter) Put(path string, statusCode int, filename string) *mockRouter { - h.add(http.MethodPut, path, statusCode, filename) - return h -} - -func (h *mockRouter) Delete(path string, statusCode int, filename string) *mockRouter { - h.add(http.MethodDelete, path, statusCode, filename) - return h -} - -func (h *mockRouter) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - h.mu.Lock() - defer h.mu.Unlock() - - if h.debug { - fmt.Println(req) - } - - rt := h.routes[req.Method] - if rt == nil { - http.NotFound(rw, req) - return - } - - hdl := rt[req.URL.Path] - if hdl == nil { - http.NotFound(rw, req) - return - } - - hdl(rw, req) -} - -func (h *mockRouter) add(method, path string, statusCode int, filename string) { - h.routes[method][path] = func(rw http.ResponseWriter, req *http.Request) { - rw.WriteHeader(statusCode) - - data, err := os.ReadFile(fmt.Sprintf("./fixtures/%s.json", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - rw.Header().Set("Content-Type", "application/json") - _, _ = rw.Write(data) - } -} diff --git a/providers/dns/vinyldns/vinyldns_test.go b/providers/dns/vinyldns/vinyldns_test.go index 8bfb192c8..6f5b9b328 100644 --- a/providers/dns/vinyldns/vinyldns_test.go +++ b/providers/dns/vinyldns/vinyldns_test.go @@ -2,10 +2,12 @@ package vinyldns import ( "net/http" + "net/http/httptest" "testing" "time" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) @@ -154,63 +156,86 @@ func TestNewDNSProviderConfig(t *testing.T) { } } +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.AccessKey = "foo" + config.SecretKey = "bar" + config.Host = server.URL + + return NewDNSProviderConfig(config) + }) +} + func TestDNSProvider_Present(t *testing.T) { testCases := []struct { desc string keyAuth string - handler http.Handler + builder *servermock.Builder[*DNSProvider] }{ { desc: "new record", keyAuth: "123456d==", - handler: newMockRouter(). - Get("/zones/name/"+targetRootDomain+".", http.StatusOK, "zoneByName"). - Get("/zones/"+zoneID+"/recordsets", http.StatusOK, "recordSetsListAll-empty"). - Post("/zones/"+zoneID+"/recordsets", http.StatusAccepted, "recordSetUpdate-create"). - Get("/zones/"+zoneID+"/recordsets/"+newRecordSetID+"/changes/"+newCreateChangeID, http.StatusOK, "recordSetChange-create"), + builder: mockBuilder(). + Route("GET /zones/name/"+targetRootDomain+".", + servermock.ResponseFromFixture("zoneByName.json")). + Route("GET /zones/"+zoneID+"/recordsets", + servermock.ResponseFromFixture("recordSetsListAll-empty.json")). + Route("POST /zones/"+zoneID+"/recordsets", + servermock.ResponseFromFixture("recordSetUpdate-create.json"). + WithStatusCode(http.StatusAccepted)). + Route("GET /zones/"+zoneID+"/recordsets/"+newRecordSetID+"/changes/"+newCreateChangeID, + servermock.ResponseFromFixture("recordSetChange-create.json")), }, { desc: "existing record", keyAuth: "123456d==", - handler: newMockRouter(). - Get("/zones/name/"+targetRootDomain+".", http.StatusOK, "zoneByName"). - Get("/zones/"+zoneID+"/recordsets", http.StatusOK, "recordSetsListAll"), + builder: mockBuilder(). + Route("GET /zones/name/"+targetRootDomain+".", + servermock.ResponseFromFixture("zoneByName.json")). + Route("GET /zones/"+zoneID+"/recordsets", + servermock.ResponseFromFixture("recordSetsListAll.json")), }, { desc: "duplicate key", keyAuth: "abc123!!", - handler: newMockRouter(). - Get("/zones/name/"+targetRootDomain+".", http.StatusOK, "zoneByName"). - Get("/zones/"+zoneID+"/recordsets", http.StatusOK, "recordSetsListAll"). - Put("/zones/"+zoneID+"/recordsets/"+recordID, http.StatusAccepted, "recordSetUpdate-create"). - Get("/zones/"+zoneID+"/recordsets/"+newRecordSetID+"/changes/"+newCreateChangeID, http.StatusOK, "recordSetChange-create"), + builder: mockBuilder(). + Route("GET /zones/name/"+targetRootDomain+".", + servermock.ResponseFromFixture("zoneByName.json")). + Route("GET /zones/"+zoneID+"/recordsets", + servermock.ResponseFromFixture("recordSetsListAll.json")). + Route("PUT /zones/"+zoneID+"/recordsets/"+recordID, + servermock.ResponseFromFixture("recordSetUpdate-create.json"). + WithStatusCode(http.StatusAccepted)). + Route("GET /zones/"+zoneID+"/recordsets/"+newRecordSetID+"/changes/"+newCreateChangeID, + servermock.ResponseFromFixture("recordSetChange-create.json")), }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - t.Parallel() + provider := test.builder.Build(t) - mux, p := setupTest(t) - mux.Handle("/", test.handler) - - err := p.Present(targetDomain, "token"+test.keyAuth, test.keyAuth) + err := provider.Present(targetDomain, "token"+test.keyAuth, test.keyAuth) require.NoError(t, err) }) } } func TestDNSProvider_CleanUp(t *testing.T) { - mux, p := setupTest(t) + provider := mockBuilder(). + Route("GET /zones/name/"+targetRootDomain+".", + servermock.ResponseFromFixture("zoneByName.json")). + Route("GET /zones/"+zoneID+"/recordsets", + servermock.ResponseFromFixture("recordSetsListAll.json")). + Route("DELETE /zones/"+zoneID+"/recordsets/"+recordID, + servermock.ResponseFromFixture("recordSetDelete.json"). + WithStatusCode(http.StatusAccepted)). + Route("GET /zones/"+zoneID+"/recordsets/"+newRecordSetID+"/changes/"+newCreateChangeID, + servermock.ResponseFromFixture("recordSetChange-delete.json")). + Build(t) - mux.Handle("/", newMockRouter(). - Get("/zones/name/"+targetRootDomain+".", http.StatusOK, "zoneByName"). - Get("/zones/"+zoneID+"/recordsets", http.StatusOK, "recordSetsListAll"). - Delete("/zones/"+zoneID+"/recordsets/"+recordID, http.StatusAccepted, "recordSetDelete"). - Get("/zones/"+zoneID+"/recordsets/"+newRecordSetID+"/changes/"+newCreateChangeID, http.StatusOK, "recordSetChange-delete"), - ) - - err := p.CleanUp(targetDomain, "123456d==", "123456d==") + err := provider.CleanUp(targetDomain, "123456d==", "123456d==") require.NoError(t, err) } diff --git a/providers/dns/vkcloud/vkcloud.go b/providers/dns/vkcloud/vkcloud.go index e76e87137..2aea7838c 100644 --- a/providers/dns/vkcloud/vkcloud.go +++ b/providers/dns/vkcloud/vkcloud.go @@ -119,7 +119,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } // Present creates a TXT record to fulfill the dns-01 challenge. -func (r *DNSProvider) Present(domain, _, keyAuth string) error { +func (d *DNSProvider) Present(domain, _, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) @@ -129,7 +129,7 @@ func (r *DNSProvider) Present(domain, _, keyAuth string) error { authZone = dns01.UnFqdn(authZone) - zones, err := r.client.ListZones() + zones, err := d.client.ListZones() if err != nil { return fmt.Errorf("vkcloud: unable to fetch dns zones: %w", err) } @@ -150,7 +150,7 @@ func (r *DNSProvider) Present(domain, _, keyAuth string) error { return fmt.Errorf("vkcloud: %w", err) } - err = r.upsertTXTRecord(zoneUUID, subDomain, info.Value) + err = d.upsertTXTRecord(zoneUUID, subDomain, info.Value) if err != nil { return fmt.Errorf("vkcloud: %w", err) } @@ -159,7 +159,7 @@ func (r *DNSProvider) Present(domain, _, keyAuth string) error { } // CleanUp removes the TXT record matching the specified parameters. -func (r *DNSProvider) CleanUp(domain, _, keyAuth string) error { +func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) @@ -169,7 +169,7 @@ func (r *DNSProvider) CleanUp(domain, _, keyAuth string) error { authZone = dns01.UnFqdn(authZone) - zones, err := r.client.ListZones() + zones, err := d.client.ListZones() if err != nil { return fmt.Errorf("vkcloud: unable to fetch dns zones: %w", err) } @@ -191,7 +191,7 @@ func (r *DNSProvider) CleanUp(domain, _, keyAuth string) error { return fmt.Errorf("vkcloud: %w", err) } - err = r.removeTXTRecord(zoneUUID, subDomain, info.Value) + err = d.removeTXTRecord(zoneUUID, subDomain, info.Value) if err != nil { return fmt.Errorf("vkcloud: %w", err) } @@ -201,12 +201,12 @@ func (r *DNSProvider) CleanUp(domain, _, keyAuth string) error { // Timeout returns the timeout and interval to use when checking for DNS propagation. // Adjusting here to cope with spikes in propagation times. -func (r *DNSProvider) Timeout() (timeout, interval time.Duration) { - return r.config.PropagationTimeout, r.config.PollingInterval +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval } -func (r *DNSProvider) upsertTXTRecord(zoneUUID, name, value string) error { - records, err := r.client.ListTXTRecords(zoneUUID) +func (d *DNSProvider) upsertTXTRecord(zoneUUID, name, value string) error { + records, err := d.client.ListTXTRecords(zoneUUID) if err != nil { return err } @@ -218,15 +218,15 @@ func (r *DNSProvider) upsertTXTRecord(zoneUUID, name, value string) error { } } - return r.client.CreateTXTRecord(zoneUUID, &internal.DNSTXTRecord{ + return d.client.CreateTXTRecord(zoneUUID, &internal.DNSTXTRecord{ Name: name, Content: value, - TTL: r.config.TTL, + TTL: d.config.TTL, }) } -func (r *DNSProvider) removeTXTRecord(zoneUUID, name, value string) error { - records, err := r.client.ListTXTRecords(zoneUUID) +func (d *DNSProvider) removeTXTRecord(zoneUUID, name, value string) error { + records, err := d.client.ListTXTRecords(zoneUUID) if err != nil { return err } @@ -234,7 +234,7 @@ func (r *DNSProvider) removeTXTRecord(zoneUUID, name, value string) error { name = dns01.UnFqdn(name) for _, record := range records { if record.Name == name && record.Content == value { - return r.client.DeleteTXTRecord(zoneUUID, record.UUID) + return d.client.DeleteTXTRecord(zoneUUID, record.UUID) } } diff --git a/providers/dns/vultr/vultr_test.go b/providers/dns/vultr/vultr_test.go index aed891628..9be1a19b0 100644 --- a/providers/dns/vultr/vultr_test.go +++ b/providers/dns/vultr/vultr_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/vultr/govultr/v3" @@ -159,53 +160,53 @@ func TestDNSProvider_getHostedZone(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - client := govultr.NewClient(nil) - err := client.SetBaseURL(server.URL) - require.NoError(t, err) - - p := &DNSProvider{client: client} - var pageCount int - mux.HandleFunc("/v2/domains", func(rw http.ResponseWriter, req *http.Request) { - pageCount++ + provider := servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + client := govultr.NewClient(nil) + err := client.SetBaseURL(server.URL) + require.NoError(t, err) - query := req.URL.Query() - cursor, _ := strconv.Atoi(query.Get("cursor")) - perPage, _ := strconv.Atoi(query.Get("per_page")) + return &DNSProvider{client: client}, nil + }, + ). + Route("GET /v2/domains", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + pageCount++ - var next string - if len(domains)/perPage > cursor { - next = strconv.Itoa(cursor + 1) - } + query := req.URL.Query() + cursor, _ := strconv.Atoi(query.Get("cursor")) + perPage, _ := strconv.Atoi(query.Get("per_page")) - start := cursor * perPage - if len(domains) < start { - start = cursor * len(domains) - } + var next string + if len(domains)/perPage > cursor { + next = strconv.Itoa(cursor + 1) + } - end := min(len(domains), (cursor+1)*perPage) + start := cursor * perPage + if len(domains) < start { + start = cursor * len(domains) + } - db := domainsBase{ - Domains: domains[start:end], - Meta: &govultr.Meta{ - Total: len(domains), - Links: &govultr.Links{Next: next}, - }, - } + end := min(len(domains), (cursor+1)*perPage) - err = json.NewEncoder(rw).Encode(db) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) + db := domainsBase{ + Domains: domains[start:end], + Meta: &govultr.Meta{ + Total: len(domains), + Links: &govultr.Links{Next: next}, + }, + } - zone, err := p.getHostedZone(t.Context(), test.domain) + err := json.NewEncoder(rw).Encode(db) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + })). + Build(t) + + zone, err := provider.getHostedZone(t.Context(), test.domain) require.NoError(t, err) assert.Equal(t, test.expected, zone) diff --git a/providers/dns/webnames/internal/client_test.go b/providers/dns/webnames/internal/client_test.go index ae14829a6..9507b6f98 100644 --- a/providers/dns/webnames/internal/client_test.go +++ b/providers/dns/webnames/internal/client_test.go @@ -1,74 +1,25 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" - "net/url" - "os" - "path" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, filename string, expectedParams url.Values) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("secret") + client.baseURL = server.URL + client.HTTPClient = server.Client() - mux := http.NewServeMux() - - mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - if req.Header.Get("Content-Type") != "application/x-www-form-urlencoded" { - http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } - - err := req.ParseForm() - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - for k, v := range expectedParams { - val := req.PostForm.Get(k) - if len(v) == 0 { - http.Error(rw, fmt.Sprintf("%s: no value", k), http.StatusBadRequest) - return - } - - if val != v[0] { - http.Error(rw, fmt.Sprintf("%s: invalid value: %s != %s", k, val, v[0]), http.StatusBadRequest) - return - } - } - - file, err := os.Open(path.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - defer func() { _ = file.Close() }() - - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - server := httptest.NewServer(mux) - - client := NewClient("secret") - client.baseURL = server.URL - client.HTTPClient = server.Client() - - return client + return client, nil + }, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded(), + ) } func TestClient_AddTXTRecord(t *testing.T) { @@ -93,13 +44,17 @@ func TestClient_AddTXTRecord(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - data := url.Values{} - data.Set("domain", "example.com") - data.Set("type", "TXT") - data.Set("record", "foo:txtTXTtxt") - data.Set("action", "add") - - client := setupTest(t, test.filename, data) + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture(test.filename), + servermock.CheckForm().Strict(). + With("domain", "example.com"). + With("type", "TXT"). + With("record", "foo:txtTXTtxt"). + With("action", "add"). + With("apikey", "secret"), + ). + Build(t) domain := "example.com" subDomain := "foo" @@ -133,13 +88,17 @@ func TestClient_RemoveTxtRecord(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - data := url.Values{} - data.Set("domain", "example.com") - data.Set("type", "TXT") - data.Set("record", "foo:txtTXTtxt") - data.Set("action", "delete") - - client := setupTest(t, test.filename, data) + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture(test.filename), + servermock.CheckForm().Strict(). + With("domain", "example.com"). + With("type", "TXT"). + With("record", "foo:txtTXTtxt"). + With("action", "delete"). + With("apikey", "secret"), + ). + Build(t) domain := "example.com" subDomain := "foo" diff --git a/providers/dns/wedos/internal/client_test.go b/providers/dns/wedos/internal/client_test.go index 4e011816b..f2515618a 100644 --- a/providers/dns/wedos/internal/client_test.go +++ b/providers/dns/wedos/internal/client_test.go @@ -4,58 +4,33 @@ import ( "fmt" "net/http" "net/http/httptest" - "os" "regexp" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupNew(t *testing.T, expectedForm, filename string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret") + client.baseURL = server.URL + client.HTTPClient = server.Client() - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { - err := req.ParseForm() - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - exp := regexp.MustCompile(`"auth":"\w+",`) - - form := req.PostForm.Get("request") - form = exp.ReplaceAllString(form, `"auth":"xxx",`) - - if form != expectedForm { - t.Logf("invalid form data: %s", req.PostForm.Get("request")) - http.Error(rw, fmt.Sprintf("invalid form data: %s", req.PostForm.Get("request")), http.StatusBadRequest) - return - } - - data, err := os.ReadFile(fmt.Sprintf("./fixtures/%s.json", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - rw.Header().Set("Content-Type", "application/json") - _, _ = rw.Write(data) - }) - - client := NewClient("user", "secret") - client.baseURL = server.URL - - return client + return client, nil + }, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded()) } func TestClient_GetRecords(t *testing.T) { - expectedForm := `{"request":{"user":"user","auth":"xxx","command":"dns-rows-list","data":{"domain":"example.com"}}}` - client := setupNew(t, expectedForm, commandDNSRowsList) + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture(commandDNSRowsList+".json"), + checkFormRequest(`{"request":{"user":"user","auth":"xxx","command":"dns-rows-list","data":{"domain":"example.com"}}}`)). + Build(t) records, err := client.GetRecords(t.Context(), "example.com.") require.NoError(t, err) @@ -94,9 +69,11 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_AddRecord(t *testing.T) { - expectedForm := `{"request":{"user":"user","auth":"xxx","command":"dns-row-add","data":{"domain":"example.com","name":"foo","ttl":1800,"type":"TXT","rdata":"foobar"}}}` - - client := setupNew(t, expectedForm, commandDNSRowAdd) + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture(commandDNSRowAdd+".json"), + checkFormRequest(`{"request":{"user":"user","auth":"xxx","command":"dns-row-add","data":{"domain":"example.com","name":"foo","ttl":1800,"type":"TXT","rdata":"foobar"}}}`)). + Build(t) record := DNSRow{ ID: "", @@ -111,9 +88,11 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_update(t *testing.T) { - expectedForm := `{"request":{"user":"user","auth":"xxx","command":"dns-row-update","data":{"row_id":"1","domain":"example.com","ttl":1800,"type":"TXT","rdata":"foobar"}}}` - - client := setupNew(t, expectedForm, commandDNSRowUpdate) + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture(commandDNSRowUpdate+".json"), + checkFormRequest(`{"request":{"user":"user","auth":"xxx","command":"dns-row-update","data":{"row_id":"1","domain":"example.com","ttl":1800,"type":"TXT","rdata":"foobar"}}}`)). + Build(t) record := DNSRow{ ID: "1", @@ -128,19 +107,45 @@ func TestClient_AddRecord_update(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - expectedForm := `{"request":{"user":"user","auth":"xxx","command":"dns-row-delete","data":{"row_id":"1","domain":"example.com","rdata":""}}}` - - client := setupNew(t, expectedForm, commandDNSRowDelete) + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture(commandDNSRowDelete+".json"), + checkFormRequest(`{"request":{"user":"user","auth":"xxx","command":"dns-row-delete","data":{"row_id":"1","domain":"example.com","rdata":""}}}`)). + Build(t) err := client.DeleteRecord(t.Context(), "example.com.", "1") require.NoError(t, err) } func TestClient_Commit(t *testing.T) { - expectedForm := `{"request":{"user":"user","auth":"xxx","command":"dns-domain-commit","data":{"name":"example.com"}}}` - - client := setupNew(t, expectedForm, commandDNSDomainCommit) + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture(commandDNSDomainCommit+".json"), + checkFormRequest(`{"request":{"user":"user","auth":"xxx","command":"dns-domain-commit","data":{"name":"example.com"}}}`)). + Build(t) err := client.Commit(t.Context(), "example.com.") require.NoError(t, err) } + +func checkFormRequest(data string) servermock.LinkFunc { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + err := req.ParseForm() + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + form := regexp.MustCompile(`"auth":"\w+",`). + ReplaceAllString(req.PostForm.Get("request"), `"auth":"xxx",`) + + if form != data { + http.Error(rw, fmt.Sprintf("invalid form data: %s", req.PostForm.Get("request")), http.StatusBadRequest) + return + } + + next.ServeHTTP(rw, req) + }) + } +} diff --git a/providers/dns/westcn/internal/client_test.go b/providers/dns/westcn/internal/client_test.go index 6e21d7f61..f7bdac5c0 100644 --- a/providers/dns/westcn/internal/client_test.go +++ b/providers/dns/westcn/internal/client_test.go @@ -1,123 +1,53 @@ package internal import ( - "fmt" - "io" - "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" "time" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/text/encoding/simplifiedchinese" ) -type formExpectation func(values url.Values) error - -func setupTest(t *testing.T, filename string, expectations ...formExpectation) *Client { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("POST /", func(rw http.ResponseWriter, req *http.Request) { - err := req.ParseForm() - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - commons := []formExpectation{ - expectValue("username", "user"), - expectNotEmpty("time"), - expectNotEmpty("token"), - } - - for _, common := range commons { - err = common(req.Form) +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("user", "secret") if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return + return nil, err } - } - for _, expectation := range expectations { - err = expectation(req.Form) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - } + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - rw.Header().Set("Content-Type", "application/json; Charset=gb2312") - - file, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = file.Close() }() - - rw.WriteHeader(http.StatusOK) - _, err = io.Copy(rw, file) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client, err := NewClient("user", "secret") - require.NoError(t, err) - - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client -} - -func expectValue(key, value string) formExpectation { - return func(values url.Values) error { - if values.Get(key) != value { - return fmt.Errorf("expected %s, got %s", value, values.Get(key)) - } - - return nil - } -} - -func expectNotEmpty(key string) formExpectation { - return func(values url.Values) error { - if values.Get(key) == "" { - return fmt.Errorf("%s missing", key) - } - - return nil - } -} - -func noop() formExpectation { - return func(_ url.Values) error { - return nil - } + return client, nil + }, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded()) } func TestClientAddRecord(t *testing.T) { - expectValue("act", "adddnsrecord") - - client := setupTest(t, "adddnsrecord.json", - expectValue("act", "adddnsrecord"), - expectValue("domain", "example.com"), - expectValue("host", "@"), - expectValue("type", "TXT"), - expectValue("value", "txtTXTtxt"), - expectValue("ttl", "60"), - ) + client := mockBuilder(). + Route("POST /domain/", + servermock.ResponseFromFixture("adddnsrecord.json"). + WithHeader("Content-Type", "application/json", "Charset=gb2312"), + servermock.CheckQueryParameter().Strict(). + With("act", "adddnsrecord"), + servermock.CheckForm().UsePostForm().Strict(). + With("domain", "example.com"). + With("host", "@"). + With("ttl", "60"). + With("type", "TXT"). + With("value", "txtTXTtxt"). + // With("act", "adddnsrecord"). + With("username", "user"). + WithRegexp("time", `\d+`). + WithRegexp("token", `[a-z0-9]{32}`), + ). + Build(t) record := Record{ Domain: "example.com", @@ -134,7 +64,13 @@ func TestClientAddRecord(t *testing.T) { } func TestClientAddRecord_error(t *testing.T) { - client := setupTest(t, "error.json", noop()) + client := mockBuilder(). + Route("POST /domain/", + servermock.ResponseFromFixture("error.json"). + WithHeader("Content-Type", "application/json", "Charset=gb2312"), + servermock.CheckQueryParameter().Strict(). + With("act", "adddnsrecord")). + Build(t) record := Record{ Domain: "example.com", @@ -151,18 +87,34 @@ func TestClientAddRecord_error(t *testing.T) { } func TestClientDeleteRecord(t *testing.T) { - client := setupTest(t, "deldnsrecord.json", - expectValue("act", "deldnsrecord"), - expectValue("domain", "example.com"), - ) + client := mockBuilder(). + Route("POST /domain/", + servermock.ResponseFromFixture("deldnsrecord.json"). + WithHeader("Content-Type", "application/json", "Charset=gb2312"), + servermock.CheckQueryParameter().Strict(). + With("act", "deldnsrecord"), + servermock.CheckForm().UsePostForm().Strict(). + With("id", "123"). + With("domain", "example.com"). + With("username", "user"). + WithRegexp("time", `\d+`). + WithRegexp("token", `[a-z0-9]{32}`), + ). + Build(t) err := client.DeleteRecord(t.Context(), "example.com", 123) require.NoError(t, err) } func TestClientDeleteRecord_error(t *testing.T) { - client := setupTest(t, "error.json", noop()) - + client := mockBuilder(). + Route("POST /domain/", + servermock.ResponseFromFixture("error.json"). + WithHeader("Content-Type", "application/json", "Charset=gb2312"), + servermock.CheckQueryParameter().Strict(). + With("act", "deldnsrecord"), + ). + Build(t) err := client.DeleteRecord(t.Context(), "example.com", 123) require.Error(t, err) diff --git a/providers/dns/yandex/internal/client_test.go b/providers/dns/yandex/internal/client_test.go index 55de81bc7..4bb3357a6 100644 --- a/providers/dns/yandex/internal/client_test.go +++ b/providers/dns/yandex/internal/client_test.go @@ -1,327 +1,133 @@ package internal import ( - "encoding/json" - "net/http" "net/http/httptest" "net/url" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T) (*Client, *http.ServeMux) { - t.Helper() - - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - +func setupClient(server *httptest.Server) (*Client, error) { client, err := NewClient("lego") - require.NoError(t, err) + if err != nil { + return nil, err + } client.HTTPClient = server.Client() client.baseURL, _ = url.Parse(server.URL) - return client, mux + return client, nil } func TestAddRecord(t *testing.T) { - testCases := []struct { - desc string - handler http.HandlerFunc - data Record - expectError bool - }{ - { - desc: "success", - handler: func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, http.MethodPost, r.Method) - assert.Equal(t, "lego", r.Header.Get(pddTokenHeader)) + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /add", + servermock.ResponseFromFixture("add_record.json"), + servermock.CheckHeader(). + WithContentTypeFromURLEncoded(), + servermock.CheckForm().Strict(). + With("domain", "example.com"). + With("subdomain", "foo"). + With("ttl", "300"). + With("content", "txtTXTtxtTXTtxtTXT"). + With("type", "TXT")). + Build(t) - err := r.ParseForm() - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - assert.Equal(t, `content=txtTXTtxtTXTtxtTXT&domain=example.com&subdomain=foo&ttl=300&type=TXT`, r.PostForm.Encode()) - - response := AddResponse{ - Domain: "example.com", - Record: &Record{ - ID: 1, - Type: "TXT", - Domain: "example.com", - SubDomain: "foo", - FQDN: "foo.example.com.", - Content: "txtTXTtxtTXTtxtTXT", - TTL: 300, - }, - BaseResponse: BaseResponse{ - Success: "ok", - }, - } - - err = json.NewEncoder(w).Encode(response) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }, - data: Record{ - Domain: "example.com", - Type: "TXT", - Content: "txtTXTtxtTXTtxtTXT", - SubDomain: "foo", - TTL: 300, - }, - }, - { - desc: "error", - handler: func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, http.MethodPost, r.Method) - assert.Equal(t, "lego", r.Header.Get(pddTokenHeader)) - - err := r.ParseForm() - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - assert.Equal(t, `content=txtTXTtxtTXTtxtTXT&domain=example.com&subdomain=foo&ttl=300&type=TXT`, r.PostForm.Encode()) - - response := AddResponse{ - Domain: "example.com", - BaseResponse: BaseResponse{ - Success: "error", - Error: "bad things", - }, - } - - err = json.NewEncoder(w).Encode(response) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }, - data: Record{ - Domain: "example.com", - Type: "TXT", - Content: "txtTXTtxtTXTtxtTXT", - SubDomain: "foo", - TTL: 300, - }, - expectError: true, - }, + data := Record{ + Domain: "example.com", + Type: "TXT", + Content: "txtTXTtxtTXTtxtTXT", + SubDomain: "foo", + TTL: 300, } - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - client, mux := setupTest(t) + record, err := client.AddRecord(t.Context(), data) + require.NoError(t, err) + require.NotNil(t, record) +} - mux.HandleFunc("/add", test.handler) +func TestAddRecord_error(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /add", + servermock.ResponseFromFixture("add_record_error.json"), + servermock.CheckHeader(). + WithContentTypeFromURLEncoded()). + Build(t) - record, err := client.AddRecord(t.Context(), test.data) - if test.expectError { - require.Error(t, err) - require.Nil(t, record) - } else { - require.NoError(t, err) - require.NotNil(t, record) - } - }) + data := Record{ + Domain: "example.com", + Type: "TXT", + Content: "txtTXTtxtTXTtxtTXT", + SubDomain: "foo", + TTL: 300, } + + _, err := client.AddRecord(t.Context(), data) + require.EqualError(t, err, "error during operation: error bad things") } func TestRemoveRecord(t *testing.T) { - testCases := []struct { - desc string - handler http.HandlerFunc - data Record - expectError bool - }{ - { - desc: "success", - handler: func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, http.MethodPost, r.Method) - assert.Equal(t, "lego", r.Header.Get(pddTokenHeader)) + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /del", + servermock.ResponseFromFixture("remove_record.json"), + servermock.CheckHeader(). + WithContentTypeFromURLEncoded(), + servermock.CheckForm().Strict(). + With("domain", "example.com"). + With("record_id", "6")). + Build(t) - err := r.ParseForm() - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - assert.Equal(t, `domain=example.com&record_id=6`, r.PostForm.Encode()) - - response := RemoveResponse{ - Domain: "example.com", - RecordID: 6, - BaseResponse: BaseResponse{ - Success: "ok", - }, - } - - err = json.NewEncoder(w).Encode(response) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }, - data: Record{ - ID: 6, - Domain: "example.com", - }, - }, - { - desc: "error", - handler: func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, http.MethodPost, r.Method) - assert.Equal(t, "lego", r.Header.Get(pddTokenHeader)) - - err := r.ParseForm() - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - assert.Equal(t, `domain=example.com&record_id=6`, r.PostForm.Encode()) - - response := RemoveResponse{ - Domain: "example.com", - RecordID: 6, - BaseResponse: BaseResponse{ - Success: "error", - Error: "bad things", - }, - } - - err = json.NewEncoder(w).Encode(response) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }, - data: Record{ - ID: 6, - Domain: "example.com", - }, - expectError: true, - }, + data := Record{ + ID: 6, + Domain: "example.com", } - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - client, mux := setupTest(t) + id, err := client.RemoveRecord(t.Context(), data) + require.NoError(t, err) - mux.HandleFunc("/del", test.handler) + assert.Equal(t, 6, id) +} - id, err := client.RemoveRecord(t.Context(), test.data) - if test.expectError { - require.Error(t, err) - require.Equal(t, 0, id) - } else { - require.NoError(t, err) - require.Equal(t, 6, id) - } - }) +func TestRemoveRecord_error(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /del", + servermock.ResponseFromFixture("remove_record_error.json"), + servermock.CheckHeader(). + WithContentTypeFromURLEncoded()). + Build(t) + + data := Record{ + ID: 6, + Domain: "example.com", } + + _, err := client.RemoveRecord(t.Context(), data) + require.EqualError(t, err, "error during operation: error bad things") } func TestGetRecords(t *testing.T) { - testCases := []struct { - desc string - handler http.HandlerFunc - domain string - expectError bool - }{ - { - desc: "success", - handler: func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, http.MethodGet, r.Method) - assert.Equal(t, "lego", r.Header.Get(pddTokenHeader)) + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /list", + servermock.ResponseFromFixture("get_records.json"), + servermock.CheckForm().Strict(). + With("domain", "example.com")). + Build(t) - assert.Equal(t, "domain=example.com", r.URL.RawQuery) + records, err := client.GetRecords(t.Context(), "example.com") + require.NoError(t, err) - response := ListResponse{ - Domain: "example.com", - Records: []Record{ - { - ID: 1, - Type: "TXT", - Domain: "example.com", - SubDomain: "foo", - FQDN: "foo.example.com.", - Content: "txtTXTtxtTXTtxtTXT", - TTL: 300, - }, - { - ID: 2, - Type: "NS", - Domain: "example.com", - SubDomain: "foo", - FQDN: "foo.example.com.", - Content: "bar", - TTL: 300, - }, - }, - BaseResponse: BaseResponse{ - Success: "ok", - }, - } - - err := json.NewEncoder(w).Encode(response) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }, - domain: "example.com", - }, - { - desc: "error", - handler: func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, http.MethodGet, r.Method) - assert.Equal(t, "lego", r.Header.Get(pddTokenHeader)) - - assert.Equal(t, "domain=example.com", r.URL.RawQuery) - - response := ListResponse{ - Domain: "example.com", - BaseResponse: BaseResponse{ - Success: "error", - Error: "bad things", - }, - } - - err := json.NewEncoder(w).Encode(response) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }, - domain: "example.com", - expectError: true, - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - client, mux := setupTest(t) - - mux.HandleFunc("/list", test.handler) - - records, err := client.GetRecords(t.Context(), test.domain) - if test.expectError { - require.Error(t, err) - require.Empty(t, records) - } else { - require.NoError(t, err) - require.Len(t, records, 2) - } - }) - } + require.Len(t, records, 2) +} + +func TestGetRecords_error(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("GET /list", + servermock.ResponseFromFixture("get_records_error.json")). + Build(t) + + _, err := client.GetRecords(t.Context(), "example.com") + require.EqualError(t, err, "error during operation: error bad things") } diff --git a/providers/dns/yandex/internal/fixtures/add_record.json b/providers/dns/yandex/internal/fixtures/add_record.json new file mode 100644 index 000000000..1e4452d1d --- /dev/null +++ b/providers/dns/yandex/internal/fixtures/add_record.json @@ -0,0 +1,13 @@ +{ + "success": "ok", + "domain": "example.com", + "record": { + "record_id": 1, + "domain": "example.com", + "subdomain": "foo", + "fqdn": "foo.example.com.", + "ttl": 300, + "type": "TXT", + "content": "txtTXTtxtTXTtxtTXT" + } +} diff --git a/providers/dns/yandex/internal/fixtures/add_record_error.json b/providers/dns/yandex/internal/fixtures/add_record_error.json new file mode 100644 index 000000000..932ccd674 --- /dev/null +++ b/providers/dns/yandex/internal/fixtures/add_record_error.json @@ -0,0 +1,5 @@ +{ + "success": "error", + "error": "bad things", + "domain": "example.com" +} diff --git a/providers/dns/yandex/internal/fixtures/get_records.json b/providers/dns/yandex/internal/fixtures/get_records.json new file mode 100644 index 000000000..e538834b4 --- /dev/null +++ b/providers/dns/yandex/internal/fixtures/get_records.json @@ -0,0 +1,24 @@ +{ + "success": "ok", + "domain": "example.com", + "records": [ + { + "record_id": 1, + "domain": "example.com", + "subdomain": "foo", + "fqdn": "foo.example.com.", + "ttl": 300, + "type": "TXT", + "content": "txtTXTtxtTXTtxtTXT" + }, + { + "record_id": 2, + "domain": "example.com", + "subdomain": "foo", + "fqdn": "foo.example.com.", + "ttl": 300, + "type": "NS", + "content": "bar" + } + ] +} diff --git a/providers/dns/yandex/internal/fixtures/get_records_error.json b/providers/dns/yandex/internal/fixtures/get_records_error.json new file mode 100644 index 000000000..932ccd674 --- /dev/null +++ b/providers/dns/yandex/internal/fixtures/get_records_error.json @@ -0,0 +1,5 @@ +{ + "success": "error", + "error": "bad things", + "domain": "example.com" +} diff --git a/providers/dns/yandex/internal/fixtures/remove_record.json b/providers/dns/yandex/internal/fixtures/remove_record.json new file mode 100644 index 000000000..3241ba9dc --- /dev/null +++ b/providers/dns/yandex/internal/fixtures/remove_record.json @@ -0,0 +1,5 @@ +{ + "success": "ok", + "domain": "example.com", + "record_id": 6 +} diff --git a/providers/dns/yandex/internal/fixtures/remove_record_error.json b/providers/dns/yandex/internal/fixtures/remove_record_error.json new file mode 100644 index 000000000..cd1471c9d --- /dev/null +++ b/providers/dns/yandex/internal/fixtures/remove_record_error.json @@ -0,0 +1,6 @@ +{ + "success": "error", + "error": "bad things", + "domain": "example.com", + "record_id": 6 +} diff --git a/providers/dns/yandex360/internal/client_test.go b/providers/dns/yandex360/internal/client_test.go index 83f66800f..aa21672e4 100644 --- a/providers/dns/yandex360/internal/client_test.go +++ b/providers/dns/yandex360/internal/client_test.go @@ -1,59 +1,39 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, pattern, method string, status int, filename string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("secret", 123456) + if err != nil { + return nil, err + } - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) + client.HTTPClient = server.Client() + client.baseURL, _ = url.Parse(server.URL) - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - open, err := os.Open(filepath.Join("fixtures", filename)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client, err := NewClient("secret", 123456) - require.NoError(t, err) - - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("OAuth secret")) } func TestClient_AddRecord(t *testing.T) { - client := setupTest(t, "/directory/v1/org/123456/domains/example.com/dns", http.MethodPost, http.StatusOK, "add-record.json") + client := mockBuilder(). + Route("POST /directory/v1/org/123456/domains/example.com/dns", + servermock.ResponseFromFixture("add-record.json"), + servermock.CheckRequestJSONBody(`{"name":"_acme-challenge","text":"txtxtxt","ttl":60,"type":"TXT"}`)). + Build(t) record := Record{ Name: "_acme-challenge", @@ -77,7 +57,11 @@ func TestClient_AddRecord(t *testing.T) { } func TestClient_AddRecord_error(t *testing.T) { - client := setupTest(t, "/directory/v1/org/123456/domains/example.com/dns", http.MethodGet, http.StatusUnauthorized, "error.json") + client := mockBuilder(). + Route("POST /directory/v1/org/123456/domains/example.com/dns", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) record := Record{ Name: "_acme-challenge", @@ -93,14 +77,21 @@ func TestClient_AddRecord_error(t *testing.T) { } func TestClient_DeleteRecord(t *testing.T) { - client := setupTest(t, "/directory/v1/org/123456/domains/example.com/dns/789456", http.MethodDelete, http.StatusOK, "delete-record.json") + client := mockBuilder(). + Route("DELETE /directory/v1/org/123456/domains/example.com/dns/789456", + servermock.ResponseFromFixture("delete-record.json")). + Build(t) err := client.DeleteRecord(t.Context(), "example.com", 789456) require.NoError(t, err) } func TestClient_DeleteRecord_error(t *testing.T) { - client := setupTest(t, "/directory/v1/org/123456/domains/example.com/dns/789456", http.MethodDelete, http.StatusUnauthorized, "error.json") + client := mockBuilder(). + Route("DELETE /directory/v1/org/123456/domains/example.com/dns/789456", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) err := client.DeleteRecord(t.Context(), "example.com", 789456) require.Error(t, err) diff --git a/providers/dns/yandexcloud/yandexcloud.go b/providers/dns/yandexcloud/yandexcloud.go index 22da14404..ca44ab82b 100644 --- a/providers/dns/yandexcloud/yandexcloud.go +++ b/providers/dns/yandexcloud/yandexcloud.go @@ -103,7 +103,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } // Present creates a TXT record to fulfill the dns-01 challenge. -func (r *DNSProvider) Present(domain, _, keyAuth string) error { +func (d *DNSProvider) Present(domain, _, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) @@ -113,7 +113,7 @@ func (r *DNSProvider) Present(domain, _, keyAuth string) error { ctx := context.Background() - zones, err := r.getZones(ctx) + zones, err := d.getZones(ctx) if err != nil { return fmt.Errorf("yandexcloud: %w", err) } @@ -135,7 +135,7 @@ func (r *DNSProvider) Present(domain, _, keyAuth string) error { return fmt.Errorf("yandexcloud: %w", err) } - err = r.upsertRecordSetData(ctx, zoneID, subDomain, info.Value) + err = d.upsertRecordSetData(ctx, zoneID, subDomain, info.Value) if err != nil { return fmt.Errorf("yandexcloud: %w", err) } @@ -144,7 +144,7 @@ func (r *DNSProvider) Present(domain, _, keyAuth string) error { } // CleanUp removes the TXT record matching the specified parameters. -func (r *DNSProvider) CleanUp(domain, _, keyAuth string) error { +func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) @@ -154,7 +154,7 @@ func (r *DNSProvider) CleanUp(domain, _, keyAuth string) error { ctx := context.Background() - zones, err := r.getZones(ctx) + zones, err := d.getZones(ctx) if err != nil { return fmt.Errorf("yandexcloud: %w", err) } @@ -176,7 +176,7 @@ func (r *DNSProvider) CleanUp(domain, _, keyAuth string) error { return fmt.Errorf("yandexcloud: %w", err) } - err = r.removeRecordSetData(ctx, zoneID, subDomain, info.Value) + err = d.removeRecordSetData(ctx, zoneID, subDomain, info.Value) if err != nil { return fmt.Errorf("yandexcloud: %w", err) } @@ -186,17 +186,17 @@ func (r *DNSProvider) CleanUp(domain, _, keyAuth string) error { // Timeout returns the timeout and interval to use when checking for DNS propagation. // Adjusting here to cope with spikes in propagation times. -func (r *DNSProvider) Timeout() (timeout, interval time.Duration) { - return r.config.PropagationTimeout, r.config.PollingInterval +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval } // getZones retrieves available zones from yandex cloud. -func (r *DNSProvider) getZones(ctx context.Context) ([]*ycdns.DnsZone, error) { +func (d *DNSProvider) getZones(ctx context.Context) ([]*ycdns.DnsZone, error) { list := &ycdns.ListDnsZonesRequest{ - FolderId: r.config.FolderID, + FolderId: d.config.FolderID, } - response, err := r.client.DNS().DnsZone().List(ctx, list) + response, err := d.client.DNS().DnsZone().List(ctx, list) if err != nil { return nil, errors.New("unable to fetch dns zones") } @@ -204,14 +204,14 @@ func (r *DNSProvider) getZones(ctx context.Context) ([]*ycdns.DnsZone, error) { return response.GetDnsZones(), nil } -func (r *DNSProvider) upsertRecordSetData(ctx context.Context, zoneID, name, value string) error { +func (d *DNSProvider) upsertRecordSetData(ctx context.Context, zoneID, name, value string) error { get := &ycdns.GetDnsZoneRecordSetRequest{ DnsZoneId: zoneID, Name: name, Type: "TXT", } - exist, err := r.client.DNS().DnsZone().GetRecordSet(ctx, get) + exist, err := d.client.DNS().DnsZone().GetRecordSet(ctx, get) if err != nil { if !strings.Contains(err.Error(), "RecordSet not found") { return err @@ -221,7 +221,7 @@ func (r *DNSProvider) upsertRecordSetData(ctx context.Context, zoneID, name, val record := &ycdns.RecordSet{ Name: name, Type: "TXT", - Ttl: int64(r.config.TTL), + Ttl: int64(d.config.TTL), Data: []string{}, } @@ -243,19 +243,19 @@ func (r *DNSProvider) upsertRecordSetData(ctx context.Context, zoneID, name, val Additions: []*ycdns.RecordSet{record}, } - _, err = r.client.DNS().DnsZone().UpdateRecordSets(ctx, update) + _, err = d.client.DNS().DnsZone().UpdateRecordSets(ctx, update) return err } -func (r *DNSProvider) removeRecordSetData(ctx context.Context, zoneID, name, value string) error { +func (d *DNSProvider) removeRecordSetData(ctx context.Context, zoneID, name, value string) error { get := &ycdns.GetDnsZoneRecordSetRequest{ DnsZoneId: zoneID, Name: name, Type: "TXT", } - previousRecord, err := r.client.DNS().DnsZone().GetRecordSet(ctx, get) + previousRecord, err := d.client.DNS().DnsZone().GetRecordSet(ctx, get) if err != nil { if strings.Contains(err.Error(), "RecordSet not found") { // RecordSet is not present, nothing to do @@ -272,7 +272,7 @@ func (r *DNSProvider) removeRecordSetData(ctx context.Context, zoneID, name, val record := &ycdns.RecordSet{ Name: name, Type: "TXT", - Ttl: int64(r.config.TTL), + Ttl: int64(d.config.TTL), Data: []string{}, } @@ -291,7 +291,7 @@ func (r *DNSProvider) removeRecordSetData(ctx context.Context, zoneID, name, val Additions: additions, } - _, err = r.client.DNS().DnsZone().UpdateRecordSets(ctx, update) + _, err = d.client.DNS().DnsZone().UpdateRecordSets(ctx, update) return err } diff --git a/providers/dns/zoneee/internal/client_test.go b/providers/dns/zoneee/internal/client_test.go index 04676877f..c2f0e781e 100644 --- a/providers/dns/zoneee/internal/client_test.go +++ b/providers/dns/zoneee/internal/client_test.go @@ -1,62 +1,34 @@ package internal import ( - "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "os" - "path/filepath" "testing" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func setupTest(t *testing.T, method, pattern string, status int, file string) *Client { - t.Helper() +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret") + client.HTTPClient = server.Client() + client.BaseURL, _ = url.Parse(server.URL) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc(pattern, func(rw http.ResponseWriter, req *http.Request) { - if req.Method != method { - http.Error(rw, fmt.Sprintf("unsupported method %s", req.Method), http.StatusBadRequest) - return - } - - if file == "" { - rw.WriteHeader(status) - return - } - - open, err := os.Open(filepath.Join("fixtures", file)) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - defer func() { _ = open.Close() }() - - rw.WriteHeader(status) - _, err = io.Copy(rw, open) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - }) - - client := NewClient("user", "secret") - client.HTTPClient = server.Client() - client.BaseURL, _ = url.Parse(server.URL) - - return client + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithBasicAuth("user", "secret"), + ) } func TestClient_GetTxtRecords(t *testing.T) { - client := setupTest(t, http.MethodGet, "/dns/example.com/txt", http.StatusOK, "get-txt-records.json") + client := mockBuilder(). + Route("GET /dns/example.com/txt", servermock.ResponseFromFixture("get-txt-records.json")). + Build(t) records, err := client.GetTxtRecords(t.Context(), "example.com") require.NoError(t, err) @@ -69,7 +41,12 @@ func TestClient_GetTxtRecords(t *testing.T) { } func TestClient_AddTxtRecord(t *testing.T) { - client := setupTest(t, http.MethodPost, "/dns/example.com/txt", http.StatusCreated, "create-txt-record.json") + client := mockBuilder(). + Route("POST /dns/example.com/txt", + servermock.ResponseFromFixture("create-txt-record.json"). + WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBody(`{"name":"prefix.example.com","destination":"server.example.com"}`)). + Build(t) records, err := client.AddTxtRecord(t.Context(), "example.com", TXTRecord{Name: "prefix.example.com", Destination: "server.example.com"}) require.NoError(t, err) @@ -82,7 +59,11 @@ func TestClient_AddTxtRecord(t *testing.T) { } func TestClient_RemoveTxtRecord(t *testing.T) { - client := setupTest(t, http.MethodDelete, "/dns/example.com/txt/123", http.StatusNoContent, "") + client := mockBuilder(). + Route("DELETE /dns/example.com/txt/123", + servermock.Noop(). + WithStatusCode(http.StatusNoContent)). + Build(t) err := client.RemoveTxtRecord(t.Context(), "example.com", "123") require.NoError(t, err) diff --git a/providers/dns/zoneee/zoneee_test.go b/providers/dns/zoneee/zoneee_test.go index 1f2909fa7..6f50cf36e 100644 --- a/providers/dns/zoneee/zoneee_test.go +++ b/providers/dns/zoneee/zoneee_test.go @@ -6,17 +6,22 @@ import ( "net/http" "net/http/httptest" "net/url" - "path" "testing" "time" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/go-acme/lego/v4/providers/dns/zoneee/internal" "github.com/stretchr/testify/require" ) const envDomain = envNamespace + "DOMAIN" +const ( + fakeUsername = "user" + fakeAPIKey = "secret" +) + var envTest = tester.NewEnvTest(EnvEndpoint, EnvAPIUser, EnvAPIKey). WithLiveTestRequirements(EnvAPIUser, EnvAPIKey). WithDomain(envDomain) @@ -94,7 +99,6 @@ func TestNewDNSProviderConfig(t *testing.T) { desc string apiUser string apiKey string - endpoint string expected string }{ { @@ -124,10 +128,6 @@ func TestNewDNSProviderConfig(t *testing.T) { config.APIKey = test.apiKey config.Username = test.apiUser - if test.endpoint != "" { - config.Endpoint = mustParse(test.endpoint) - } - p, err := NewDNSProviderConfig(config) if test.expected == "" { @@ -147,57 +147,33 @@ func TestDNSProvider_Present(t *testing.T) { testCases := []struct { desc string - username string - apiKey string - handlers map[string]http.HandlerFunc + builder *servermock.Builder[*DNSProvider] expectedError string }{ { - desc: "success", - username: "bar", - apiKey: "foo", - handlers: map[string]http.HandlerFunc{ - path.Join("/", "dns", hostedZone, "txt"): mockHandlerCreateRecord, - }, + desc: "success", + builder: mockBuilder(fakeUsername, fakeAPIKey). + Route("POST /dns/"+hostedZone+"/txt", + mockHandlerCreateRecord()), }, { - desc: "invalid auth", - username: "nope", - apiKey: "foo", - handlers: map[string]http.HandlerFunc{ - path.Join("/", "dns", hostedZone, "txt"): mockHandlerCreateRecord, - }, + desc: "invalid auth", + builder: mockBuilder("nope", "nope"). + Route("POST /dns/"+hostedZone+"/txt", nil), expectedError: "zoneee: unexpected status code: [status code: 401] body: Unauthorized", }, { desc: "error", - username: "bar", - apiKey: "foo", + builder: mockBuilder(fakeUsername, fakeAPIKey), expectedError: "zoneee: unexpected status code: [status code: 404] body: 404 page not found", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - t.Parallel() + provider := test.builder.Build(t) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - for uri, handler := range test.handlers { - mux.HandleFunc(uri, handler) - } - - config := NewDefaultConfig() - config.Endpoint = mustParse(server.URL) - config.Username = test.username - config.APIKey = test.apiKey - - p, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - err = p.Present(domain, "token", "key") + err := provider.Present(domain, "token", "key") if test.expectedError == "" { require.NoError(t, err) } else { @@ -213,81 +189,49 @@ func TestDNSProvider_Cleanup(t *testing.T) { testCases := []struct { desc string - username string - apiKey string - handlers map[string]http.HandlerFunc + builder *servermock.Builder[*DNSProvider] expectedError string }{ { - desc: "success", - username: "bar", - apiKey: "foo", - handlers: map[string]http.HandlerFunc{ - path.Join("/", "dns", hostedZone, "txt"): mockHandlerGetRecords([]internal.TXTRecord{{ - ID: "1234", - Name: domain, - Destination: "LHDhK3oGRvkiefQnx7OOczTY5Tic_xZ6HcMOc_gmtoM", - Delete: true, - Modify: true, - }}), - path.Join("/", "dns", hostedZone, "txt", "1234"): mockHandlerDeleteRecord, - }, + desc: "success", + builder: mockBuilder(fakeUsername, fakeAPIKey). + Route("GET /dns/"+hostedZone+"/txt", + mockHandlerGetRecords([]internal.TXTRecord{{ + ID: "1234", + Name: domain, + Destination: "LHDhK3oGRvkiefQnx7OOczTY5Tic_xZ6HcMOc_gmtoM", + Delete: true, + Modify: true, + }})). + Route("DELETE /dns/"+hostedZone+"/txt/1234", + servermock.Noop(). + WithStatusCode(http.StatusNoContent)), }, { - desc: "no txt records", - username: "bar", - apiKey: "foo", - handlers: map[string]http.HandlerFunc{ - path.Join("/", "dns", hostedZone, "txt"): mockHandlerGetRecords([]internal.TXTRecord{}), - path.Join("/", "dns", hostedZone, "txt", "1234"): mockHandlerDeleteRecord, - }, + desc: "no txt records", + builder: mockBuilder(fakeUsername, fakeAPIKey). + Route("GET /dns/"+hostedZone+"/txt", + mockHandlerGetRecords([]internal.TXTRecord{})), expectedError: "zoneee: txt record does not exist for LHDhK3oGRvkiefQnx7OOczTY5Tic_xZ6HcMOc_gmtoM", }, { - desc: "invalid auth", - username: "nope", - apiKey: "foo", - handlers: map[string]http.HandlerFunc{ - path.Join("/", "dns", hostedZone, "txt"): mockHandlerGetRecords([]internal.TXTRecord{{ - ID: "1234", - Name: domain, - Destination: "LHDhK3oGRvkiefQnx7OOczTY5Tic_xZ6HcMOc_gmtoM", - Delete: true, - Modify: true, - }}), - path.Join("/", "dns", hostedZone, "txt", "1234"): mockHandlerDeleteRecord, - }, + desc: "invalid auth", + builder: mockBuilder("nope", "nope"). + Route("GET /dns/"+hostedZone+"/txt", nil), expectedError: "zoneee: unexpected status code: [status code: 401] body: Unauthorized", }, { desc: "error", - username: "bar", - apiKey: "foo", + builder: mockBuilder(fakeUsername, fakeAPIKey), expectedError: "zoneee: unexpected status code: [status code: 404] body: 404 page not found", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - t.Parallel() + provider := test.builder.Build(t) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - for uri, handler := range test.handlers { - mux.HandleFunc(uri, handler) - } - - config := NewDefaultConfig() - config.Endpoint = mustParse(server.URL) - config.Username = test.username - config.APIKey = test.apiKey - - p, err := NewDNSProviderConfig(config) - require.NoError(t, err) - - err = p.CleanUp(domain, "token", "key") + err := provider.CleanUp(domain, "token", "key") if test.expectedError == "" { require.NoError(t, err) } else { @@ -325,72 +269,57 @@ func TestLiveCleanUp(t *testing.T) { require.NoError(t, err) } -func mustParse(rawURL string) *url.URL { - uri, err := url.Parse(rawURL) - if err != nil { - panic(err) - } - return uri +func mockBuilder(username, apiKey string) *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.Endpoint, _ = url.Parse(server.URL) + config.Username = username + config.APIKey = apiKey + + return NewDNSProviderConfig(config) + }, + checkBasicAuth()) } -func mockHandlerCreateRecord(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodPost { - http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } +func mockHandlerCreateRecord() http.HandlerFunc { + return encodeJSONHandler(func(req *http.Request, rw http.ResponseWriter) (any, error) { + record := internal.TXTRecord{} + err := json.NewDecoder(req.Body).Decode(&record) + if err != nil { + return nil, err + } - username, apiKey, ok := req.BasicAuth() - if username != "bar" || apiKey != "foo" || !ok { - rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, "Please enter your username and API key.")) - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } + record.ID = "1234" + record.Delete = true + record.Modify = true + record.ResourceURL = req.URL.String() + "/1234" - record := internal.TXTRecord{} - err := json.NewDecoder(req.Body).Decode(&record) - if err != nil { - http.Error(rw, err.Error(), http.StatusBadRequest) - return - } - - record.ID = "1234" - record.Delete = true - record.Modify = true - record.ResourceURL = req.URL.String() + "/1234" - - bytes, err := json.Marshal([]internal.TXTRecord{record}) - if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } - - if _, err = rw.Write(bytes); err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return - } + return []internal.TXTRecord{record}, nil + }) } func mockHandlerGetRecords(records []internal.TXTRecord) http.HandlerFunc { - return func(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet { - http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - username, apiKey, ok := req.BasicAuth() - if username != "bar" || apiKey != "foo" || !ok { - rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, "Please enter your username and API key.")) - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return - } - - for _, value := range records { - if value.ResourceURL == "" { - value.ResourceURL = req.URL.String() + "/" + value.ID + return encodeJSONHandler(func(req *http.Request, rw http.ResponseWriter) (any, error) { + for _, record := range records { + if record.ResourceURL == "" { + record.ResourceURL = req.URL.String() + "/" + record.ID } } - bytes, err := json.Marshal(records) + return records, nil + }) +} + +func encodeJSONHandler(build func(req *http.Request, rw http.ResponseWriter) (any, error)) http.HandlerFunc { + return func(rw http.ResponseWriter, req *http.Request) { + data, err := build(req, rw) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + bytes, err := json.Marshal(data) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) return @@ -403,18 +332,17 @@ func mockHandlerGetRecords(records []internal.TXTRecord) http.HandlerFunc { } } -func mockHandlerDeleteRecord(rw http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodDelete { - http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } +func checkBasicAuth() servermock.LinkFunc { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + username, apiKey, ok := req.BasicAuth() + if username != fakeUsername || apiKey != fakeAPIKey || !ok { + rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, "Please enter your username and API key.")) + http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + return + } - username, apiKey, ok := req.BasicAuth() - if username != "bar" || apiKey != "foo" || !ok { - rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, "Please enter your username and API key.")) - http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - return + next.ServeHTTP(rw, req) + }) } - - rw.WriteHeader(http.StatusNoContent) } From b8beddc2675e4d240e737283d5a75a52d8f01405 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 12 Jul 2025 14:20:02 +0200 Subject: [PATCH 122/298] Add DNS provider for ZoneEdit (#2578) --- README.md | 2 +- cmd/zz_gen_cmd_dnshelp.go | 21 +++ docs/content/dns/zz_gen_zoneedit.md | 68 +++++++++ docs/data/zz_cli_help.toml | 4 +- providers/dns/zoneedit/internal/client.go | 107 +++++++++++++ .../dns/zoneedit/internal/client_test.go | 64 ++++++++ .../dns/zoneedit/internal/fixtures/error.xml | 1 + .../zoneedit/internal/fixtures/success.xml | 1 + providers/dns/zoneedit/internal/types.go | 18 +++ providers/dns/zoneedit/zoneedit.go | 123 +++++++++++++++ providers/dns/zoneedit/zoneedit.toml | 23 +++ providers/dns/zoneedit/zoneedit_test.go | 143 ++++++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 13 files changed, 575 insertions(+), 3 deletions(-) create mode 100644 docs/content/dns/zz_gen_zoneedit.md create mode 100644 providers/dns/zoneedit/internal/client.go create mode 100644 providers/dns/zoneedit/internal/client_test.go create mode 100644 providers/dns/zoneedit/internal/fixtures/error.xml create mode 100644 providers/dns/zoneedit/internal/fixtures/success.xml create mode 100644 providers/dns/zoneedit/internal/types.go create mode 100644 providers/dns/zoneedit/zoneedit.go create mode 100644 providers/dns/zoneedit/zoneedit.toml create mode 100644 providers/dns/zoneedit/zoneedit_test.go diff --git a/README.md b/README.md index f527a082d..59b62a8de 100644 --- a/README.md +++ b/README.md @@ -248,10 +248,10 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Yandex PDD Zone.ee + ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 8821d0d18..fe3d8813e 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -167,6 +167,7 @@ func allDNSCodes() string { "yandex", "yandex360", "yandexcloud", + "zoneedit", "zoneee", "zonomi", } @@ -3488,6 +3489,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/yandexcloud`) + case "zoneedit": + // generated from: providers/dns/zoneedit/zoneedit.toml + ew.writeln(`Configuration for ZoneEdit.`) + ew.writeln(`Code: 'zoneedit'`) + ew.writeln(`Since: 'v4.25.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "ZONEEDIT_PASSWORD": Password`) + ew.writeln(` - "ZONEEDIT_USER_ID": User ID`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "ZONEEDIT_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "ZONEEDIT_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "ZONEEDIT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/zoneedit`) + case "zoneee": // generated from: providers/dns/zoneee/zoneee.toml ew.writeln(`Configuration for Zone.ee.`) diff --git a/docs/content/dns/zz_gen_zoneedit.md b/docs/content/dns/zz_gen_zoneedit.md new file mode 100644 index 000000000..f39f10686 --- /dev/null +++ b/docs/content/dns/zz_gen_zoneedit.md @@ -0,0 +1,68 @@ +--- +title: "ZoneEdit" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: zoneedit +dnsprovider: + since: "v4.25.0" + code: "zoneedit" + url: "https://www.zoneedit.com" +--- + + + + + + +Configuration for [ZoneEdit](https://www.zoneedit.com). + + + + +- Code: `zoneedit` +- Since: v4.25.0 + + +Here is an example bash command using the ZoneEdit provider: + +```bash +ZONEEDIT_USER_ID="xxxxxxxxxxxxxxxxxxxxx" \ +ZONEEDIT_PASSWORD="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns zoneedit -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `ZONEEDIT_PASSWORD` | Password | +| `ZONEEDIT_USER_ID` | User ID | + +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 | +|--------------------------------|-------------| +| `ZONEEDIT_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `ZONEEDIT_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `ZONEEDIT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | + +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://support.zoneedit.com/en/knowledgebase/article/changes-to-dynamic-dns) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 723f063cd..0ba9b9d8a 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -94,7 +94,7 @@ USAGE: OPTIONS: --days value The number of days left on a certificate to renew it. (default: 30) - --dynamic Dynamically defines the renewal date. (1/3rd of the lifetime left or 1/2 of the lifetime left, if the lifetime is shorter than 10 days) (default: false) + --dynamic Compute dynamically, based on the lifetime of the certificate(s), when to renew: use 1/3rd of the lifetime left, or 1/2 of the lifetime for short-lived certificates). This supersedes --days and will be the default behavior in Lego v5. (default: false) --ari-disable Do not use the renewalInfo endpoint (RFC9773) to check if a certificate should be renewed. (default: false) --ari-wait-to-renew-duration value The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint. (default: 0s) --reuse-key Used to indicate you want to reuse your current private key for the new certificate. (default: false) @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneee, zonomi + acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/zoneedit/internal/client.go b/providers/dns/zoneedit/internal/client.go new file mode 100644 index 000000000..e97f4beb9 --- /dev/null +++ b/providers/dns/zoneedit/internal/client.go @@ -0,0 +1,107 @@ +package internal + +import ( + "bytes" + "encoding/xml" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "slices" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const defaultBaseURL = "https://dynamic.zoneedit.com" + +// Client the ZoneEdit API client. +type Client struct { + user string + authToken string + + baseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(user, authToken string) (*Client, error) { + if user == "" || authToken == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + user: user, + authToken: authToken, + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) CreateTXTRecord(domain, rdata string) error { + return c.perform("txt-create.php", domain, rdata) +} + +func (c *Client) DeleteTXTRecord(domain, rdata string) error { + return c.perform("txt-delete.php", domain, rdata) +} + +func (c *Client) perform(actionPath, domain, rdata string) error { + endpoint := c.baseURL.JoinPath(actionPath) + + query := endpoint.Query() + query.Set("host", domain) + query.Set("rdata", rdata) + endpoint.RawQuery = query.Encode() + + req, err := http.NewRequest(http.MethodGet, endpoint.String(), http.NoBody) + if err != nil { + return err + } + + return c.do(req) +} + +func (c *Client) do(req *http.Request) error { + req.SetBasicAuth(c.user, c.authToken) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + raw, _ := io.ReadAll(resp.Body) + + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + if bytes.Contains(raw, []byte("SUCCESS CODE")) { + return nil + } + + raw = bytes.TrimSpace(raw) + + // The answer is not an XML valid (missing closing), so I fix it to parse it. + if bytes.HasSuffix(raw, []byte(">")) { + raw = slices.Concat(raw[:len(raw)-1], []byte("/>")) + } + + var apiErr APIError + err = xml.Unmarshal(raw, &apiErr) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return fmt.Errorf("[status code: %d] %w", resp.StatusCode, apiErr) +} diff --git a/providers/dns/zoneedit/internal/client_test.go b/providers/dns/zoneedit/internal/client_test.go new file mode 100644 index 000000000..1d9f9be79 --- /dev/null +++ b/providers/dns/zoneedit/internal/client_test.go @@ -0,0 +1,64 @@ +package internal + +import ( + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder(func(server *httptest.Server) (*Client, error) { + client, err := NewClient("user", "secret") + if err != nil { + return nil, err + } + + client.baseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }) +} + +func TestClient_CreateTXTRecord(t *testing.T) { + client := mockBuilder(). + Route("GET /txt-create.php", + servermock.ResponseFromFixture("success.xml")). + Build(t) + + err := client.CreateTXTRecord("_acme-challenge.example.com", "value") + require.NoError(t, err) +} + +func TestClient_CreateTXTRecord_error(t *testing.T) { + client := mockBuilder(). + Route("GET /txt-create.php", + servermock.ResponseFromFixture("error.xml")). + Build(t) + + err := client.CreateTXTRecord("_acme-challenge.example.com", "value") + require.EqualError(t, err, "[status code: 200] 708: Failed Login: user (_acme-challenge.example.com)") +} + +func TestClient_DeleteTXTRecord(t *testing.T) { + client := mockBuilder(). + Route("GET /txt-delete.php", + servermock.ResponseFromFixture("success.xml")). + Build(t) + + err := client.DeleteTXTRecord("_acme-challenge.example.com", "value") + require.NoError(t, err) +} + +func TestClient_DeleteTXTRecord_error(t *testing.T) { + client := mockBuilder(). + Route("GET /txt-delete.php", + servermock.ResponseFromFixture("error.xml")). + Build(t) + + err := client.DeleteTXTRecord("_acme-challenge.example.com", "value") + require.EqualError(t, err, "[status code: 200] 708: Failed Login: user (_acme-challenge.example.com)") +} diff --git a/providers/dns/zoneedit/internal/fixtures/error.xml b/providers/dns/zoneedit/internal/fixtures/error.xml new file mode 100644 index 000000000..6c0f1de60 --- /dev/null +++ b/providers/dns/zoneedit/internal/fixtures/error.xml @@ -0,0 +1 @@ + diff --git a/providers/dns/zoneedit/internal/fixtures/success.xml b/providers/dns/zoneedit/internal/fixtures/success.xml new file mode 100644 index 000000000..80d75169d --- /dev/null +++ b/providers/dns/zoneedit/internal/fixtures/success.xml @@ -0,0 +1 @@ + diff --git a/providers/dns/zoneedit/internal/types.go b/providers/dns/zoneedit/internal/types.go new file mode 100644 index 000000000..96fa41c36 --- /dev/null +++ b/providers/dns/zoneedit/internal/types.go @@ -0,0 +1,18 @@ +package internal + +import ( + "encoding/xml" + "fmt" +) + +type APIError struct { + XMLName xml.Name `xml:"ERROR"` + Text string `xml:",chardata"` + Code string `xml:"CODE,attr"` + Message string `xml:"TEXT,attr"` + Zone string `xml:"ZONE,attr"` +} + +func (a APIError) Error() string { + return fmt.Sprintf("%s: %s (%s)", a.Code, a.Message, a.Zone) +} diff --git a/providers/dns/zoneedit/zoneedit.go b/providers/dns/zoneedit/zoneedit.go new file mode 100644 index 000000000..875b84233 --- /dev/null +++ b/providers/dns/zoneedit/zoneedit.go @@ -0,0 +1,123 @@ +// Package zoneedit implements a DNS provider for solving the DNS-01 challenge using ZoneEdit. +package zoneedit + +import ( + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/zoneedit/internal" +) + +// Environment variables names. +const ( + envNamespace = "ZONEEDIT_" + + EnvUser = envNamespace + "USER" + EnAuthToken = envNamespace + "AUTH_TOKEN" + + 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 { + User string + AuthToken string + + PropagationTimeout time.Duration + PollingInterval time.Duration + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for ZoneEdit. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvUser, EnAuthToken) + if err != nil { + return nil, fmt.Errorf("zoneedit: %w", err) + } + + config := NewDefaultConfig() + config.User = values[EnvUser] + config.AuthToken = values[EnAuthToken] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for ZoneEdit. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("zoneedit: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.User, config.AuthToken) + if err != nil { + return nil, fmt.Errorf("zoneedit: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + err := d.client.CreateTXTRecord(dns01.UnFqdn(info.EffectiveFQDN), info.Value) + if err != nil { + return fmt.Errorf("zoneedit: create TXT record: %w", err) + } + + // ERROR CODE="702" TEXT="Minimum 10 seconds between requests" + time.Sleep(11 * time.Second) + + 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) + + err := d.client.DeleteTXTRecord(dns01.UnFqdn(info.EffectiveFQDN), info.Value) + if err != nil { + return fmt.Errorf("zoneedit: delete TXT record: %w", err) + } + + // ERROR CODE="702" TEXT="Minimum 10 seconds between requests" + time.Sleep(11 * time.Second) + + 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/zoneedit/zoneedit.toml b/providers/dns/zoneedit/zoneedit.toml new file mode 100644 index 000000000..d3c547c23 --- /dev/null +++ b/providers/dns/zoneedit/zoneedit.toml @@ -0,0 +1,23 @@ +Name = "ZoneEdit" +Description = '''''' +URL = "https://www.zoneedit.com" +Code = "zoneedit" +Since = "v4.25.0" + +Example = ''' +ZONEEDIT_USER="xxxxxxxxxxxxxxxxxxxxx" \ +ZONEEDIT_AUTH_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns zoneedit -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + ZONEEDIT_USER = "User ID" + ZONEEDIT_AUTH_TOKEN = "Authentication token" + [Configuration.Additional] + ZONEEDIT_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + ZONEEDIT_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + ZONEEDIT_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://support.zoneedit.com/en/knowledgebase/article/changes-to-dynamic-dns" diff --git a/providers/dns/zoneedit/zoneedit_test.go b/providers/dns/zoneedit/zoneedit_test.go new file mode 100644 index 000000000..2a9b1754d --- /dev/null +++ b/providers/dns/zoneedit/zoneedit_test.go @@ -0,0 +1,143 @@ +package zoneedit + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvUser, EnAuthToken).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvUser: "user", + EnAuthToken: "secret", + }, + }, + { + desc: "missing user ID", + envVars: map[string]string{ + EnvUser: "", + EnAuthToken: "secret", + }, + expected: "zoneedit: some credentials information are missing: ZONEEDIT_USER", + }, + { + desc: "missing auth token", + envVars: map[string]string{ + EnvUser: "user", + EnAuthToken: "", + }, + expected: "zoneedit: some credentials information are missing: ZONEEDIT_AUTH_TOKEN", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "zoneedit: some credentials information are missing: ZONEEDIT_USER,ZONEEDIT_AUTH_TOKEN", + }, + } + + 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 + user string + authToken string + expected string + }{ + { + desc: "success", + user: "user", + authToken: "secret", + }, + { + desc: "missing user ID", + authToken: "secret", + expected: "zoneedit: credentials missing", + }, + { + desc: "missing auth token", + user: "user", + expected: "zoneedit: credentials missing", + }, + { + desc: "missing credentials", + expected: "zoneedit: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.User = test.user + config.AuthToken = test.authToken + + 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 5eee04faf..59205f7f6 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -161,6 +161,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/yandex" "github.com/go-acme/lego/v4/providers/dns/yandex360" "github.com/go-acme/lego/v4/providers/dns/yandexcloud" + "github.com/go-acme/lego/v4/providers/dns/zoneedit" "github.com/go-acme/lego/v4/providers/dns/zoneee" "github.com/go-acme/lego/v4/providers/dns/zonomi" ) @@ -478,6 +479,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return yandex360.NewDNSProvider() case "yandexcloud": return yandexcloud.NewDNSProvider() + case "zoneedit": + return zoneedit.NewDNSProvider() case "zoneee": return zoneee.NewDNSProvider() case "zonomi": From 08e9db687b270e4a149e736721925aa748f23ee3 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 12 Jul 2025 14:31:03 +0200 Subject: [PATCH 123/298] chore: minor changes (#2581) --- cmd/zz_gen_cmd_dnshelp.go | 2 -- docs/content/dns/zz_gen_dode.md | 1 - docs/content/dns/zz_gen_duckdns.md | 1 - providers/dns/corenetworks/internal/client.go | 14 +++++++------- providers/dns/corenetworks/internal/identity.go | 4 ++-- providers/dns/cpanel/internal/cpanel/client.go | 12 ++++++------ providers/dns/cpanel/internal/whm/client.go | 12 ++++++------ providers/dns/derak/internal/client.go | 14 +++++++------- providers/dns/directadmin/internal/client.go | 6 +++--- providers/dns/dode/dode.toml | 1 - providers/dns/dode/internal/client.go | 2 +- providers/dns/duckdns/duckdns.toml | 1 - providers/dns/duckdns/internal/client.go | 6 +++--- providers/dns/dynu/internal/client.go | 12 ++++++------ providers/dns/efficientip/internal/client.go | 10 +++++----- providers/dns/epik/internal/client.go | 10 +++++----- providers/dns/hosttech/internal/client.go | 12 ++++++------ providers/dns/internal/hostingde/client.go | 8 ++++---- providers/dns/internal/rimuhosting/client.go | 8 ++++---- providers/dns/internetbs/internal/client.go | 8 ++++---- providers/dns/ipv64/internal/client.go | 8 ++++---- providers/dns/iwantmyname/internal/client.go | 2 +- providers/dns/liara/internal/client.go | 8 ++++---- providers/dns/limacity/internal/client.go | 12 ++++++------ providers/dns/metaregistrar/internal/client.go | 4 ++-- providers/dns/mijnhost/internal/client.go | 8 ++++---- providers/dns/mittwald/internal/client.go | 14 +++++++------- providers/dns/mydnsjp/internal/client.go | 8 ++++---- providers/dns/nearlyfreespeech/internal/client.go | 6 +++--- providers/dns/nicmanager/internal/client.go | 8 ++++---- providers/dns/plesk/internal/client.go | 8 ++++---- providers/dns/regru/internal/client.go | 6 +++--- providers/dns/shellrent/internal/client.go | 12 ++++++------ providers/dns/tencentcloud/tencentcloud.go | 15 ++++++++++----- providers/dns/tencentcloud/wrapper.go | 9 +++++---- providers/dns/variomedia/internal/client.go | 8 ++++---- providers/dns/yandex/internal/client.go | 6 +++--- providers/dns/yandex360/internal/client.go | 6 +++--- 38 files changed, 146 insertions(+), 146 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index fe3d8813e..10e4ec604 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -1033,7 +1033,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(` - "DODE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "DODE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln(` - "DODE_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) - ew.writeln(` - "DODE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/dode`) @@ -1093,7 +1092,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(` - "DUCKDNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "DUCKDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln(` - "DUCKDNS_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) - ew.writeln(` - "DUCKDNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/duckdns`) diff --git a/docs/content/dns/zz_gen_dode.md b/docs/content/dns/zz_gen_dode.md index 240bd5276..153650406 100644 --- a/docs/content/dns/zz_gen_dode.md +++ b/docs/content/dns/zz_gen_dode.md @@ -51,7 +51,6 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | `DODE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `DODE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | | `DODE_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | -| `DODE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/docs/content/dns/zz_gen_duckdns.md b/docs/content/dns/zz_gen_duckdns.md index 8082075ee..1290b82fd 100644 --- a/docs/content/dns/zz_gen_duckdns.md +++ b/docs/content/dns/zz_gen_duckdns.md @@ -51,7 +51,6 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | `DUCKDNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `DUCKDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | | `DUCKDNS_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | -| `DUCKDNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). diff --git a/providers/dns/corenetworks/internal/client.go b/providers/dns/corenetworks/internal/client.go index 993b01f1e..ea2d7efa2 100644 --- a/providers/dns/corenetworks/internal/client.go +++ b/providers/dns/corenetworks/internal/client.go @@ -38,7 +38,7 @@ func NewClient(login, password string) *Client { // ListZone gets a list of all DNS zones. // https://beta.api.core-networks.de/doc/#functon_dnszones -func (c Client) ListZone(ctx context.Context) ([]Zone, error) { +func (c *Client) ListZone(ctx context.Context) ([]Zone, error) { endpoint := c.baseURL.JoinPath("dnszones") req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -57,7 +57,7 @@ func (c Client) ListZone(ctx context.Context) ([]Zone, error) { // GetZoneDetails provides detailed information about a DNS zone. // https://beta.api.core-networks.de/doc/#functon_dnszones_details -func (c Client) GetZoneDetails(ctx context.Context, zone string) (*ZoneDetails, error) { +func (c *Client) GetZoneDetails(ctx context.Context, zone string) (*ZoneDetails, error) { endpoint := c.baseURL.JoinPath("dnszones", zone) req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -76,7 +76,7 @@ func (c Client) GetZoneDetails(ctx context.Context, zone string) (*ZoneDetails, // ListRecords gets a list of DNS records belonging to the zone. // https://beta.api.core-networks.de/doc/#functon_dnszones_records -func (c Client) ListRecords(ctx context.Context, zone string) ([]Record, error) { +func (c *Client) ListRecords(ctx context.Context, zone string) ([]Record, error) { endpoint := c.baseURL.JoinPath("dnszones", zone, "records") req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -95,7 +95,7 @@ func (c Client) ListRecords(ctx context.Context, zone string) ([]Record, error) // AddRecord adds a record. // https://beta.api.core-networks.de/doc/#functon_dnszones_records_add -func (c Client) AddRecord(ctx context.Context, zone string, record Record) error { +func (c *Client) AddRecord(ctx context.Context, zone string, record Record) error { endpoint := c.baseURL.JoinPath("dnszones", zone, "records", "/") if record.Name == "" { @@ -117,7 +117,7 @@ func (c Client) AddRecord(ctx context.Context, zone string, record Record) error // DeleteRecords deletes all DNS records of a zone that match the DNS record passed. // https://beta.api.core-networks.de/doc/#functon_dnszones_records_delete -func (c Client) DeleteRecords(ctx context.Context, zone string, record Record) error { +func (c *Client) DeleteRecords(ctx context.Context, zone string, record Record) error { endpoint := c.baseURL.JoinPath("dnszones", zone, "records", "delete") if record.Name == "" { @@ -139,7 +139,7 @@ func (c Client) DeleteRecords(ctx context.Context, zone string, record Record) e // CommitRecords sends a commit to the zone. // https://beta.api.core-networks.de/doc/#functon_dnszones_commit -func (c Client) CommitRecords(ctx context.Context, zone string) error { +func (c *Client) CommitRecords(ctx context.Context, zone string) error { endpoint := c.baseURL.JoinPath("dnszones", zone, "records", "commit") req, err := newJSONRequest(ctx, http.MethodPost, endpoint, nil) @@ -155,7 +155,7 @@ func (c Client) CommitRecords(ctx context.Context, zone string) error { return nil } -func (c Client) do(req *http.Request, result any) error { +func (c *Client) do(req *http.Request, result any) error { at := getToken(req.Context()) if at != "" { req.Header.Set(authorizationHeader, "Bearer "+at) diff --git a/providers/dns/corenetworks/internal/identity.go b/providers/dns/corenetworks/internal/identity.go index 6a3b4d46a..8f5a2ee9a 100644 --- a/providers/dns/corenetworks/internal/identity.go +++ b/providers/dns/corenetworks/internal/identity.go @@ -13,7 +13,7 @@ const tokenKey token = "token" // CreateAuthenticationToken gets an authentication token. // https://beta.api.core-networks.de/doc/#functon_auth_token -func (c Client) CreateAuthenticationToken(ctx context.Context) (*Token, error) { +func (c *Client) CreateAuthenticationToken(ctx context.Context) (*Token, error) { endpoint := c.baseURL.JoinPath("auth", "token") req, err := newJSONRequest(ctx, http.MethodPost, endpoint, Auth{Login: c.login, Password: c.password}) @@ -30,7 +30,7 @@ func (c Client) CreateAuthenticationToken(ctx context.Context) (*Token, error) { return &token, nil } -func (c Client) CreateAuthenticatedContext(ctx context.Context) (context.Context, error) { +func (c *Client) CreateAuthenticatedContext(ctx context.Context) (context.Context, error) { tok, err := c.CreateAuthenticationToken(ctx) if err != nil { return nil, err diff --git a/providers/dns/cpanel/internal/cpanel/client.go b/providers/dns/cpanel/internal/cpanel/client.go index f0ababd9f..e869f6f4b 100644 --- a/providers/dns/cpanel/internal/cpanel/client.go +++ b/providers/dns/cpanel/internal/cpanel/client.go @@ -40,7 +40,7 @@ func NewClient(baseURL, username, token string) (*Client, error) { // FetchZoneInformation fetches zone information. // https://api.docs.cpanel.net/openapi/cpanel/operation/dns-parse_zone/ -func (c Client) FetchZoneInformation(ctx context.Context, domain string) ([]shared.ZoneRecord, error) { +func (c *Client) FetchZoneInformation(ctx context.Context, domain string) ([]shared.ZoneRecord, error) { endpoint := c.baseURL.JoinPath("DNS", "parse_zone") query := endpoint.Query() @@ -64,7 +64,7 @@ func (c Client) FetchZoneInformation(ctx context.Context, domain string) ([]shar // AddRecord adds a new record. // // add='{"dname":"example", "ttl":14400, "record_type":"TXT", "data":["string1", "string2"]}' -func (c Client) AddRecord(ctx context.Context, serial uint32, domain string, record shared.Record) (*shared.ZoneSerial, error) { +func (c *Client) AddRecord(ctx context.Context, serial uint32, domain string, record shared.Record) (*shared.ZoneSerial, error) { data, err := json.Marshal(record) if err != nil { return nil, fmt.Errorf("failed to create request JSON data: %w", err) @@ -76,7 +76,7 @@ func (c Client) AddRecord(ctx context.Context, serial uint32, domain string, rec // EditRecord edits an existing record. // // edit='{"line_index": 9, "dname":"example", "ttl":14400, "record_type":"TXT", "data":["string1", "string2"]}' -func (c Client) EditRecord(ctx context.Context, serial uint32, domain string, record shared.Record) (*shared.ZoneSerial, error) { +func (c *Client) EditRecord(ctx context.Context, serial uint32, domain string, record shared.Record) (*shared.ZoneSerial, error) { data, err := json.Marshal(record) if err != nil { return nil, fmt.Errorf("failed to create request JSON data: %w", err) @@ -88,12 +88,12 @@ func (c Client) EditRecord(ctx context.Context, serial uint32, domain string, re // DeleteRecord deletes an existing record. // // remove=22 -func (c Client) DeleteRecord(ctx context.Context, serial uint32, domain string, lineIndex int) (*shared.ZoneSerial, error) { +func (c *Client) DeleteRecord(ctx context.Context, serial uint32, domain string, lineIndex int) (*shared.ZoneSerial, error) { return c.updateZone(ctx, serial, domain, "remove", strconv.Itoa(lineIndex)) } // https://api.docs.cpanel.net/openapi/cpanel/operation/dns-mass_edit_zone/ -func (c Client) updateZone(ctx context.Context, serial uint32, domain, action, data string) (*shared.ZoneSerial, error) { +func (c *Client) updateZone(ctx context.Context, serial uint32, domain, action, data string) (*shared.ZoneSerial, error) { endpoint := c.baseURL.JoinPath("DNS", "mass_edit_zone") query := endpoint.Query() @@ -116,7 +116,7 @@ func (c Client) updateZone(ctx context.Context, serial uint32, domain, action, d return &result.Data, nil } -func (c Client) doRequest(ctx context.Context, endpoint *url.URL, result any) error { +func (c *Client) doRequest(ctx context.Context, endpoint *url.URL, result any) error { req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), http.NoBody) if err != nil { return fmt.Errorf("unable to create request: %w", err) diff --git a/providers/dns/cpanel/internal/whm/client.go b/providers/dns/cpanel/internal/whm/client.go index 41b29388c..742b25b6a 100644 --- a/providers/dns/cpanel/internal/whm/client.go +++ b/providers/dns/cpanel/internal/whm/client.go @@ -40,7 +40,7 @@ func NewClient(baseURL, username, token string) (*Client, error) { // FetchZoneInformation fetches zone information. // https://api.docs.cpanel.net/openapi/whm/operation/parse_dns_zone/ -func (c Client) FetchZoneInformation(ctx context.Context, domain string) ([]shared.ZoneRecord, error) { +func (c *Client) FetchZoneInformation(ctx context.Context, domain string) ([]shared.ZoneRecord, error) { endpoint := c.baseURL.JoinPath("parse_dns_zone") query := endpoint.Query() @@ -64,7 +64,7 @@ func (c Client) FetchZoneInformation(ctx context.Context, domain string) ([]shar // AddRecord adds a new record. // // add='{"dname":"example", "ttl":14400, "record_type":"TXT", "data":["string1", "string2"]}' -func (c Client) AddRecord(ctx context.Context, serial uint32, domain string, record shared.Record) (*shared.ZoneSerial, error) { +func (c *Client) AddRecord(ctx context.Context, serial uint32, domain string, record shared.Record) (*shared.ZoneSerial, error) { data, err := json.Marshal(record) if err != nil { return nil, fmt.Errorf("failed to create request JSON data: %w", err) @@ -76,7 +76,7 @@ func (c Client) AddRecord(ctx context.Context, serial uint32, domain string, rec // EditRecord edits an existing record. // // edit='{"line_index": 9, "dname":"example", "ttl":14400, "record_type":"TXT", "data":["string1", "string2"]}' -func (c Client) EditRecord(ctx context.Context, serial uint32, domain string, record shared.Record) (*shared.ZoneSerial, error) { +func (c *Client) EditRecord(ctx context.Context, serial uint32, domain string, record shared.Record) (*shared.ZoneSerial, error) { data, err := json.Marshal(record) if err != nil { return nil, fmt.Errorf("failed to create request JSON data: %w", err) @@ -88,12 +88,12 @@ func (c Client) EditRecord(ctx context.Context, serial uint32, domain string, re // DeleteRecord deletes an existing record. // // remove=22 -func (c Client) DeleteRecord(ctx context.Context, serial uint32, domain string, lineIndex int) (*shared.ZoneSerial, error) { +func (c *Client) DeleteRecord(ctx context.Context, serial uint32, domain string, lineIndex int) (*shared.ZoneSerial, error) { return c.updateZone(ctx, serial, domain, "remove", strconv.Itoa(lineIndex)) } // https://api.docs.cpanel.net/openapi/whm/operation/mass_edit_dns_zone/ -func (c Client) updateZone(ctx context.Context, serial uint32, domain, action, data string) (*shared.ZoneSerial, error) { +func (c *Client) updateZone(ctx context.Context, serial uint32, domain, action, data string) (*shared.ZoneSerial, error) { endpoint := c.baseURL.JoinPath("mass_edit_dns_zone") query := endpoint.Query() @@ -116,7 +116,7 @@ func (c Client) updateZone(ctx context.Context, serial uint32, domain, action, d return &result.Data, nil } -func (c Client) doRequest(ctx context.Context, endpoint *url.URL, result any) error { +func (c *Client) doRequest(ctx context.Context, endpoint *url.URL, result any) error { query := endpoint.Query() query.Set("api.version", "1") endpoint.RawQuery = query.Encode() diff --git a/providers/dns/derak/internal/client.go b/providers/dns/derak/internal/client.go index ea24388b2..a7c11f895 100644 --- a/providers/dns/derak/internal/client.go +++ b/providers/dns/derak/internal/client.go @@ -37,7 +37,7 @@ func NewClient(apiKey string) *Client { // GetRecords gets all records. // Note: the response is not influenced by the query parameters, so the documentation seems wrong. -func (c Client) GetRecords(ctx context.Context, zoneID string, params *GetRecordsParameters) (*GetRecordsResponse, error) { +func (c *Client) GetRecords(ctx context.Context, zoneID string, params *GetRecordsParameters) (*GetRecordsResponse, error) { endpoint := c.baseURL.JoinPath("zones", zoneID, "dnsrecords") v, err := querystring.Values(params) @@ -61,7 +61,7 @@ func (c Client) GetRecords(ctx context.Context, zoneID string, params *GetRecord } // GetRecord gets a record by ID. -func (c Client) GetRecord(ctx context.Context, zoneID, recordID string) (*Record, error) { +func (c *Client) GetRecord(ctx context.Context, zoneID, recordID string) (*Record, error) { endpoint := c.baseURL.JoinPath("zones", zoneID, "dnsrecords", recordID) req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -79,7 +79,7 @@ func (c Client) GetRecord(ctx context.Context, zoneID, recordID string) (*Record } // CreateRecord creates a new record. -func (c Client) CreateRecord(ctx context.Context, zoneID string, record Record) (*Record, error) { +func (c *Client) CreateRecord(ctx context.Context, zoneID string, record Record) (*Record, error) { endpoint := c.baseURL.JoinPath("zones", zoneID, "dnsrecords") req, err := newJSONRequest(ctx, http.MethodPut, endpoint, record) @@ -97,7 +97,7 @@ func (c Client) CreateRecord(ctx context.Context, zoneID string, record Record) } // EditRecord edits an existing record. -func (c Client) EditRecord(ctx context.Context, zoneID, recordID string, record Record) (*Record, error) { +func (c *Client) EditRecord(ctx context.Context, zoneID, recordID string, record Record) (*Record, error) { endpoint := c.baseURL.JoinPath("zones", zoneID, "dnsrecords", recordID) req, err := newJSONRequest(ctx, http.MethodPatch, endpoint, record) @@ -115,7 +115,7 @@ func (c Client) EditRecord(ctx context.Context, zoneID, recordID string, record } // DeleteRecord deletes an existing record. -func (c Client) DeleteRecord(ctx context.Context, zoneID, recordID string) error { +func (c *Client) DeleteRecord(ctx context.Context, zoneID, recordID string) error { endpoint := c.baseURL.JoinPath("zones", zoneID, "dnsrecords", recordID) req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) @@ -140,7 +140,7 @@ func (c Client) DeleteRecord(ctx context.Context, zoneID, recordID string) error // GetZones gets zones. // Note: it's not a part of the official API, there is no documentation about this. // The endpoint comes from UI calls analysis. -func (c Client) GetZones(ctx context.Context) ([]Zone, error) { +func (c *Client) GetZones(ctx context.Context) ([]Zone, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.zoneEndpoint, http.NoBody) if err != nil { return nil, err @@ -159,7 +159,7 @@ func (c Client) GetZones(ctx context.Context) ([]Zone, error) { return response.Result, nil } -func (c Client) do(req *http.Request, result any) error { +func (c *Client) do(req *http.Request, result any) error { req.SetBasicAuth("api", c.apiKey) resp, err := c.HTTPClient.Do(req) diff --git a/providers/dns/directadmin/internal/client.go b/providers/dns/directadmin/internal/client.go index fb84257bc..bf6d64371 100644 --- a/providers/dns/directadmin/internal/client.go +++ b/providers/dns/directadmin/internal/client.go @@ -38,7 +38,7 @@ func NewClient(baseURL, username, password string) (*Client, error) { }, nil } -func (c Client) SetRecord(ctx context.Context, domain string, record Record) error { +func (c *Client) SetRecord(ctx context.Context, domain string, record Record) error { data, err := querystring.Values(record) if err != nil { return err @@ -49,7 +49,7 @@ func (c Client) SetRecord(ctx context.Context, domain string, record Record) err return c.do(ctx, domain, data) } -func (c Client) DeleteRecord(ctx context.Context, domain string, record Record) error { +func (c *Client) DeleteRecord(ctx context.Context, domain string, record Record) error { data, err := querystring.Values(record) if err != nil { return err @@ -60,7 +60,7 @@ func (c Client) DeleteRecord(ctx context.Context, domain string, record Record) return c.do(ctx, domain, data) } -func (c Client) do(ctx context.Context, domain string, data url.Values) error { +func (c *Client) do(ctx context.Context, domain string, data url.Values) error { endpoint := c.baseURL.JoinPath("CMD_API_DNS_CONTROL") query := endpoint.Query() diff --git a/providers/dns/dode/dode.toml b/providers/dns/dode/dode.toml index 0954c902e..a96e9ee43 100644 --- a/providers/dns/dode/dode.toml +++ b/providers/dns/dode/dode.toml @@ -15,7 +15,6 @@ lego --email you@example.com --dns dode -d '*.example.com' -d example.com run [Configuration.Additional] DODE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" DODE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - DODE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" DODE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" DODE_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" diff --git a/providers/dns/dode/internal/client.go b/providers/dns/dode/internal/client.go index 91fa439c7..bbd98b399 100644 --- a/providers/dns/dode/internal/client.go +++ b/providers/dns/dode/internal/client.go @@ -36,7 +36,7 @@ func NewClient(token string) *Client { // UpdateTxtRecord Update the domains TXT record // To update the TXT record we just need to make one simple get request. -func (c Client) UpdateTxtRecord(ctx context.Context, fqdn, txt string, clearRecord bool) error { +func (c *Client) UpdateTxtRecord(ctx context.Context, fqdn, txt string, clearRecord bool) error { endpoint := c.baseURL.JoinPath("letsencrypt") query := endpoint.Query() diff --git a/providers/dns/duckdns/duckdns.toml b/providers/dns/duckdns/duckdns.toml index 35041a96b..9c0b3a6be 100644 --- a/providers/dns/duckdns/duckdns.toml +++ b/providers/dns/duckdns/duckdns.toml @@ -15,7 +15,6 @@ lego --email you@example.com --dns duckdns -d '*.example.com' -d example.com run [Configuration.Additional] DUCKDNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" DUCKDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - DUCKDNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" DUCKDNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" DUCKDNS_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" diff --git a/providers/dns/duckdns/internal/client.go b/providers/dns/duckdns/internal/client.go index ae86f64c8..ced5bf187 100644 --- a/providers/dns/duckdns/internal/client.go +++ b/providers/dns/duckdns/internal/client.go @@ -34,18 +34,18 @@ func NewClient(token string) *Client { } } -func (c Client) AddTXTRecord(ctx context.Context, domain, value string) error { +func (c *Client) AddTXTRecord(ctx context.Context, domain, value string) error { return c.UpdateTxtRecord(ctx, domain, value, false) } -func (c Client) RemoveTXTRecord(ctx context.Context, domain string) error { +func (c *Client) RemoveTXTRecord(ctx context.Context, domain string) error { return c.UpdateTxtRecord(ctx, domain, "", true) } // UpdateTxtRecord Update the domains TXT record // To update the TXT record we just need to make one simple get request. // In DuckDNS you only have one TXT record shared with the domain and all subdomains. -func (c Client) UpdateTxtRecord(ctx context.Context, domain, txt string, clearRecord bool) error { +func (c *Client) UpdateTxtRecord(ctx context.Context, domain, txt string, clearRecord bool) error { endpoint, _ := url.Parse(c.baseURL) mainDomain := getMainDomain(domain) diff --git a/providers/dns/dynu/internal/client.go b/providers/dns/dynu/internal/client.go index 6821863b3..a51556a0b 100644 --- a/providers/dns/dynu/internal/client.go +++ b/providers/dns/dynu/internal/client.go @@ -34,7 +34,7 @@ func NewClient() *Client { } // GetRecords Get DNS records based on a hostname and resource record type. -func (c Client) GetRecords(ctx context.Context, hostname, recordType string) ([]DNSRecord, error) { +func (c *Client) GetRecords(ctx context.Context, hostname, recordType string) ([]DNSRecord, error) { endpoint := c.baseURL.JoinPath("dns", "record", hostname) query := endpoint.Query() @@ -55,7 +55,7 @@ func (c Client) GetRecords(ctx context.Context, hostname, recordType string) ([] } // AddNewRecord Add a new DNS record for DNS service. -func (c Client) AddNewRecord(ctx context.Context, domainID int64, record DNSRecord) error { +func (c *Client) AddNewRecord(ctx context.Context, domainID int64, record DNSRecord) error { endpoint := c.baseURL.JoinPath("dns", strconv.FormatInt(domainID, 10), "record") reqBody, err := json.Marshal(record) @@ -77,7 +77,7 @@ func (c Client) AddNewRecord(ctx context.Context, domainID int64, record DNSReco } // DeleteRecord Remove a DNS record from DNS service. -func (c Client) DeleteRecord(ctx context.Context, domainID, recordID int64) error { +func (c *Client) DeleteRecord(ctx context.Context, domainID, recordID int64) error { endpoint := c.baseURL.JoinPath("dns", strconv.FormatInt(domainID, 10), "record", strconv.FormatInt(recordID, 10)) apiResp := APIException{} @@ -94,7 +94,7 @@ func (c Client) DeleteRecord(ctx context.Context, domainID, recordID int64) erro } // GetRootDomain Get the root domain name based on a hostname. -func (c Client) GetRootDomain(ctx context.Context, hostname string) (*DNSHostname, error) { +func (c *Client) GetRootDomain(ctx context.Context, hostname string) (*DNSHostname, error) { endpoint := c.baseURL.JoinPath("dns", "getroot", hostname) apiResp := DNSHostname{} @@ -111,7 +111,7 @@ func (c Client) GetRootDomain(ctx context.Context, hostname string) (*DNSHostnam } // doRetry the API is really unstable, so we need to retry on EOF. -func (c Client) doRetry(ctx context.Context, method, uri string, body []byte, result any) error { +func (c *Client) doRetry(ctx context.Context, method, uri string, body []byte, result any) error { operation := func() error { return c.do(ctx, method, uri, body, result) } @@ -131,7 +131,7 @@ func (c Client) doRetry(ctx context.Context, method, uri string, body []byte, re return nil } -func (c Client) do(ctx context.Context, method, uri string, body []byte, result any) error { +func (c *Client) do(ctx context.Context, method, uri string, body []byte, result any) error { var reqBody io.Reader if len(body) > 0 { reqBody = bytes.NewReader(body) diff --git a/providers/dns/efficientip/internal/client.go b/providers/dns/efficientip/internal/client.go index fb6ee185b..cc26c5412 100644 --- a/providers/dns/efficientip/internal/client.go +++ b/providers/dns/efficientip/internal/client.go @@ -33,7 +33,7 @@ func NewClient(hostname, username, password string) *Client { } } -func (c Client) ListRecords(ctx context.Context) ([]ResourceRecord, error) { +func (c *Client) ListRecords(ctx context.Context) ([]ResourceRecord, error) { endpoint := c.baseURL.JoinPath("dns_rr_list") req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -51,7 +51,7 @@ func (c Client) ListRecords(ctx context.Context) ([]ResourceRecord, error) { return result, nil } -func (c Client) GetRecord(ctx context.Context, id string) (*ResourceRecord, error) { +func (c *Client) GetRecord(ctx context.Context, id string) (*ResourceRecord, error) { endpoint := c.baseURL.JoinPath("dns_rr_info") query := endpoint.Query() @@ -77,7 +77,7 @@ func (c Client) GetRecord(ctx context.Context, id string) (*ResourceRecord, erro return &result[0], nil } -func (c Client) AddRecord(ctx context.Context, record ResourceRecord) (*BaseOutput, error) { +func (c *Client) AddRecord(ctx context.Context, record ResourceRecord) (*BaseOutput, error) { endpoint := c.baseURL.JoinPath("dns_rr_add") req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) @@ -99,7 +99,7 @@ func (c Client) AddRecord(ctx context.Context, record ResourceRecord) (*BaseOutp return &result[0], nil } -func (c Client) DeleteRecord(ctx context.Context, params DeleteInputParameters) (*BaseOutput, error) { +func (c *Client) DeleteRecord(ctx context.Context, params DeleteInputParameters) (*BaseOutput, error) { endpoint := c.baseURL.JoinPath("dns_rr_delete") // (rr_id || (rr_name && (dns_id || dns_name || hostaddr))) @@ -129,7 +129,7 @@ func (c Client) DeleteRecord(ctx context.Context, params DeleteInputParameters) return &result[0], nil } -func (c Client) do(req *http.Request, result any) error { +func (c *Client) do(req *http.Request, result any) error { req.SetBasicAuth(c.username, c.password) req.Header.Set("cache-control", "no-cache") diff --git a/providers/dns/epik/internal/client.go b/providers/dns/epik/internal/client.go index 640085065..70fb42fa9 100644 --- a/providers/dns/epik/internal/client.go +++ b/providers/dns/epik/internal/client.go @@ -37,7 +37,7 @@ func NewClient(signature string) *Client { // GetDNSRecords gets DNS records for a domain. // https://docs.userapi.epik.com/v2/#/DNS%20Host%20Records/getDnsRecord -func (c Client) GetDNSRecords(ctx context.Context, domain string) ([]Record, error) { +func (c *Client) GetDNSRecords(ctx context.Context, domain string) ([]Record, error) { endpoint := c.createEndpoint(domain, url.Values{}) req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -56,7 +56,7 @@ func (c Client) GetDNSRecords(ctx context.Context, domain string) ([]Record, err // CreateHostRecord creates a record for a domain. // https://docs.userapi.epik.com/v2/#/DNS%20Host%20Records/createHostRecord -func (c Client) CreateHostRecord(ctx context.Context, domain string, record RecordRequest) (*Data, error) { +func (c *Client) CreateHostRecord(ctx context.Context, domain string, record RecordRequest) (*Data, error) { endpoint := c.createEndpoint(domain, url.Values{}) payload := CreateHostRecords{Payload: record} @@ -77,7 +77,7 @@ func (c Client) CreateHostRecord(ctx context.Context, domain string, record Reco // RemoveHostRecord removes a record for a domain. // https://docs.userapi.epik.com/v2/#/DNS%20Host%20Records/removeHostRecord -func (c Client) RemoveHostRecord(ctx context.Context, domain, recordID string) (*Data, error) { +func (c *Client) RemoveHostRecord(ctx context.Context, domain, recordID string) (*Data, error) { params := url.Values{} params.Set("ID", recordID) @@ -97,7 +97,7 @@ func (c Client) RemoveHostRecord(ctx context.Context, domain, recordID string) ( return &data, nil } -func (c Client) do(req *http.Request, result any) error { +func (c *Client) do(req *http.Request, result any) error { useragent.SetHeader(req.Header) resp, err := c.HTTPClient.Do(req) @@ -128,7 +128,7 @@ func (c Client) do(req *http.Request, result any) error { return nil } -func (c Client) createEndpoint(domain string, params url.Values) *url.URL { +func (c *Client) createEndpoint(domain string, params url.Values) *url.URL { endpoint := c.baseURL.JoinPath("domains", domain, "records") params.Set("SIGNATURE", c.signature) diff --git a/providers/dns/hosttech/internal/client.go b/providers/dns/hosttech/internal/client.go index 78b594558..399b18d0e 100644 --- a/providers/dns/hosttech/internal/client.go +++ b/providers/dns/hosttech/internal/client.go @@ -36,7 +36,7 @@ func NewClient(hc *http.Client) *Client { // GetZones Get a list of all zones. // https://api.ns1.hosttech.eu/api/documentation/#/Zones/get_api_user_v1_zones -func (c Client) GetZones(ctx context.Context, query string, limit, offset int) ([]Zone, error) { +func (c *Client) GetZones(ctx context.Context, query string, limit, offset int) ([]Zone, error) { endpoint := c.baseURL.JoinPath("user", "v1", "zones") values := endpoint.Query() @@ -68,7 +68,7 @@ func (c Client) GetZones(ctx context.Context, query string, limit, offset int) ( // GetZone Get a single zone. // https://api.ns1.hosttech.eu/api/documentation/#/Zones/get_api_user_v1_zones__zoneId_ -func (c Client) GetZone(ctx context.Context, zoneID string) (*Zone, error) { +func (c *Client) GetZone(ctx context.Context, zoneID string) (*Zone, error) { endpoint := c.baseURL.JoinPath("user", "v1", "zones", zoneID) req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -87,7 +87,7 @@ func (c Client) GetZone(ctx context.Context, zoneID string) (*Zone, error) { // GetRecords Returns a list of all records for the given zone. // https://api.ns1.hosttech.eu/api/documentation/#/Records/get_api_user_v1_zones__zoneId__records -func (c Client) GetRecords(ctx context.Context, zoneID, recordType string) ([]Record, error) { +func (c *Client) GetRecords(ctx context.Context, zoneID, recordType string) ([]Record, error) { endpoint := c.baseURL.JoinPath("user", "v1", "zones", zoneID, "records") values := endpoint.Query() @@ -114,7 +114,7 @@ func (c Client) GetRecords(ctx context.Context, zoneID, recordType string) ([]Re // AddRecord Adds a new record to the zone and returns the newly created record. // https://api.ns1.hosttech.eu/api/documentation/#/Records/post_api_user_v1_zones__zoneId__records -func (c Client) AddRecord(ctx context.Context, zoneID string, record Record) (*Record, error) { +func (c *Client) AddRecord(ctx context.Context, zoneID string, record Record) (*Record, error) { endpoint := c.baseURL.JoinPath("user", "v1", "zones", zoneID, "records") req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) @@ -133,7 +133,7 @@ func (c Client) AddRecord(ctx context.Context, zoneID string, record Record) (*R // DeleteRecord Deletes a single record for the given id. // https://api.ns1.hosttech.eu/api/documentation/#/Records/delete_api_user_v1_zones__zoneId__records__recordId_ -func (c Client) DeleteRecord(ctx context.Context, zoneID, recordID string) error { +func (c *Client) DeleteRecord(ctx context.Context, zoneID, recordID string) error { endpoint := c.baseURL.JoinPath("user", "v1", "zones", zoneID, "records", recordID) req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) @@ -144,7 +144,7 @@ func (c Client) DeleteRecord(ctx context.Context, zoneID, recordID string) error return c.do(req, nil) } -func (c Client) do(req *http.Request, result any) error { +func (c *Client) do(req *http.Request, result any) error { resp, errD := c.httpClient.Do(req) if errD != nil { return errutils.NewHTTPDoError(req, errD) diff --git a/providers/dns/internal/hostingde/client.go b/providers/dns/internal/hostingde/client.go index 8416f202b..869d93d3e 100644 --- a/providers/dns/internal/hostingde/client.go +++ b/providers/dns/internal/hostingde/client.go @@ -39,7 +39,7 @@ func NewClient(apiKey string) *Client { } // GetZone gets a zone. -func (c Client) GetZone(ctx context.Context, req ZoneConfigsFindRequest) (*ZoneConfig, error) { +func (c *Client) GetZone(ctx context.Context, req ZoneConfigsFindRequest) (*ZoneConfig, error) { var zoneConfig *ZoneConfig operation := func() error { @@ -73,7 +73,7 @@ func (c Client) GetZone(ctx context.Context, req ZoneConfigsFindRequest) (*ZoneC // ListZoneConfigs lists zone configuration. // https://www.hosting.de/api/?json#list-zoneconfigs -func (c Client) ListZoneConfigs(ctx context.Context, req ZoneConfigsFindRequest) (*ZoneResponse, error) { +func (c *Client) ListZoneConfigs(ctx context.Context, req ZoneConfigsFindRequest) (*ZoneResponse, error) { endpoint := c.BaseURL.JoinPath("zoneConfigsFind") req.AuthToken = c.apiKey @@ -98,7 +98,7 @@ func (c Client) ListZoneConfigs(ctx context.Context, req ZoneConfigsFindRequest) // UpdateZone updates a zone. // https://www.hosting.de/api/?json#updating-zones -func (c Client) UpdateZone(ctx context.Context, req ZoneUpdateRequest) (*Zone, error) { +func (c *Client) UpdateZone(ctx context.Context, req ZoneUpdateRequest) (*Zone, error) { endpoint := c.BaseURL.JoinPath("zoneUpdate") req.AuthToken = c.apiKey @@ -118,7 +118,7 @@ func (c Client) UpdateZone(ctx context.Context, req ZoneUpdateRequest) (*Zone, e return response.Response, nil } -func (c Client) post(ctx context.Context, endpoint *url.URL, request, result any) ([]byte, error) { +func (c *Client) post(ctx context.Context, endpoint *url.URL, request, result any) ([]byte, error) { body, err := json.Marshal(request) if err != nil { return nil, fmt.Errorf("failed to create request JSON body: %w", err) diff --git a/providers/dns/internal/rimuhosting/client.go b/providers/dns/internal/rimuhosting/client.go index 4976f3781..06292453a 100644 --- a/providers/dns/internal/rimuhosting/client.go +++ b/providers/dns/internal/rimuhosting/client.go @@ -49,7 +49,7 @@ func NewClient(apiKey string) *Client { // ex: // - https://zonomi.com/app/dns/dyndns.jsp?action=QUERY&name=example.com&api_key=apikeyvaluehere // - https://zonomi.com/app/dns/dyndns.jsp?action=QUERY&name=**.example.com&api_key=apikeyvaluehere -func (c Client) FindTXTRecords(ctx context.Context, domain string) ([]Record, error) { +func (c *Client) FindTXTRecords(ctx context.Context, domain string) ([]Record, error) { action := ActionParameter{ Action: QueryAction, Name: domain, @@ -65,7 +65,7 @@ func (c Client) FindTXTRecords(ctx context.Context, domain string) ([]Record, er } // DoActions performs actions. -func (c Client) DoActions(ctx context.Context, actions ...ActionParameter) (*DNSAPIResult, error) { +func (c *Client) DoActions(ctx context.Context, actions ...ActionParameter) (*DNSAPIResult, error) { if len(actions) == 0 { return nil, errors.New("no action") } @@ -93,7 +93,7 @@ func (c Client) DoActions(ctx context.Context, actions ...ActionParameter) (*DNS return resp, nil } -func (c Client) toMultiParameters(params []ActionParameter) multiActionParameter { +func (c *Client) toMultiParameters(params []ActionParameter) multiActionParameter { multi := multiActionParameter{ APIKey: c.apiKey, } @@ -109,7 +109,7 @@ func (c Client) toMultiParameters(params []ActionParameter) multiActionParameter return multi } -func (c Client) do(ctx context.Context, params, result any) error { +func (c *Client) do(ctx context.Context, params, result any) error { baseURL, err := url.Parse(c.BaseURL) if err != nil { return err diff --git a/providers/dns/internetbs/internal/client.go b/providers/dns/internetbs/internal/client.go index 23cc7d1b3..4de57dc9a 100644 --- a/providers/dns/internetbs/internal/client.go +++ b/providers/dns/internetbs/internal/client.go @@ -46,7 +46,7 @@ func NewClient(apiKey, password string) *Client { } // AddRecord The command is intended to add a new DNS record to a specific zone (domain). -func (c Client) AddRecord(ctx context.Context, query RecordQuery) error { +func (c *Client) AddRecord(ctx context.Context, query RecordQuery) error { var r APIResponse err := c.doRequest(ctx, "Add", query, &r) if err != nil { @@ -61,7 +61,7 @@ func (c Client) AddRecord(ctx context.Context, query RecordQuery) error { } // RemoveRecord The command is intended to remove a DNS record from a specific zone. -func (c Client) RemoveRecord(ctx context.Context, query RecordQuery) error { +func (c *Client) RemoveRecord(ctx context.Context, query RecordQuery) error { var r APIResponse err := c.doRequest(ctx, "Remove", query, &r) if err != nil { @@ -76,7 +76,7 @@ func (c Client) RemoveRecord(ctx context.Context, query RecordQuery) error { } // ListRecords The command is intended to retrieve the list of DNS records for a specific domain. -func (c Client) ListRecords(ctx context.Context, query ListRecordQuery) ([]Record, error) { +func (c *Client) ListRecords(ctx context.Context, query ListRecordQuery) ([]Record, error) { var l ListResponse err := c.doRequest(ctx, "List", query, &l) if err != nil { @@ -90,7 +90,7 @@ func (c Client) ListRecords(ctx context.Context, query ListRecordQuery) ([]Recor return l.Records, nil } -func (c Client) doRequest(ctx context.Context, action string, params, result any) error { +func (c *Client) doRequest(ctx context.Context, action string, params, result any) error { endpoint := c.baseURL.JoinPath("Domain", "DnsRecord", action) values, err := querystring.Values(params) diff --git a/providers/dns/ipv64/internal/client.go b/providers/dns/ipv64/internal/client.go index fbb871aa3..1cb9c532f 100644 --- a/providers/dns/ipv64/internal/client.go +++ b/providers/dns/ipv64/internal/client.go @@ -34,7 +34,7 @@ func NewClient(hc *http.Client) *Client { } } -func (c Client) GetDomains(ctx context.Context) (*Domains, error) { +func (c *Client) GetDomains(ctx context.Context) (*Domains, error) { endpoint := c.baseURL.JoinPath("api") query := endpoint.Query() @@ -56,7 +56,7 @@ func (c Client) GetDomains(ctx context.Context) (*Domains, error) { return results, nil } -func (c Client) AddRecord(ctx context.Context, domain, prefix, recordType, content string) error { +func (c *Client) AddRecord(ctx context.Context, domain, prefix, recordType, content string) error { endpoint := c.baseURL.JoinPath("api") data := make(url.Values) @@ -73,7 +73,7 @@ func (c Client) AddRecord(ctx context.Context, domain, prefix, recordType, conte return c.do(req, nil) } -func (c Client) DeleteRecord(ctx context.Context, domain, prefix, recordType, content string) error { +func (c *Client) DeleteRecord(ctx context.Context, domain, prefix, recordType, content string) error { endpoint := c.baseURL.JoinPath("api") data := make(url.Values) @@ -90,7 +90,7 @@ func (c Client) DeleteRecord(ctx context.Context, domain, prefix, recordType, co return c.do(req, nil) } -func (c Client) do(req *http.Request, result any) error { +func (c *Client) do(req *http.Request, result any) error { if req.Method != http.MethodGet { req.Header.Set("Content-Type", "application/x-www-form-urlencoded") } diff --git a/providers/dns/iwantmyname/internal/client.go b/providers/dns/iwantmyname/internal/client.go index 1edc7531f..c3418c854 100644 --- a/providers/dns/iwantmyname/internal/client.go +++ b/providers/dns/iwantmyname/internal/client.go @@ -35,7 +35,7 @@ func NewClient(username, password string) *Client { } // SendRequest send a request (create/add/delete) to the API. -func (c Client) SendRequest(ctx context.Context, record Record) error { +func (c *Client) SendRequest(ctx context.Context, record Record) error { values, err := querystring.Values(record) if err != nil { return err diff --git a/providers/dns/liara/internal/client.go b/providers/dns/liara/internal/client.go index 5faf9f1f2..3d4af1d3b 100644 --- a/providers/dns/liara/internal/client.go +++ b/providers/dns/liara/internal/client.go @@ -35,7 +35,7 @@ func NewClient(hc *http.Client) *Client { // GetRecords gets the records of a domain. // https://openapi.liara.ir/?urls.primaryName=DNS -func (c Client) GetRecords(ctx context.Context, domainName string) ([]Record, error) { +func (c *Client) GetRecords(ctx context.Context, domainName string) ([]Record, error) { endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records") req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -69,7 +69,7 @@ func (c Client) GetRecords(ctx context.Context, domainName string) ([]Record, er } // CreateRecord creates a record. -func (c Client) CreateRecord(ctx context.Context, domainName string, record Record) (*Record, error) { +func (c *Client) CreateRecord(ctx context.Context, domainName string, record Record) (*Record, error) { endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records") req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) @@ -103,7 +103,7 @@ func (c Client) CreateRecord(ctx context.Context, domainName string, record Reco } // GetRecord gets a specific record. -func (c Client) GetRecord(ctx context.Context, domainName, recordID string) (*Record, error) { +func (c *Client) GetRecord(ctx context.Context, domainName, recordID string) (*Record, error) { endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records", recordID) req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -137,7 +137,7 @@ func (c Client) GetRecord(ctx context.Context, domainName, recordID string) (*Re } // DeleteRecord deletes a record. -func (c Client) DeleteRecord(ctx context.Context, domainName, recordID string) error { +func (c *Client) DeleteRecord(ctx context.Context, domainName, recordID string) error { endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records", recordID) req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) diff --git a/providers/dns/limacity/internal/client.go b/providers/dns/limacity/internal/client.go index 8a8b93adb..07622e121 100644 --- a/providers/dns/limacity/internal/client.go +++ b/providers/dns/limacity/internal/client.go @@ -32,7 +32,7 @@ func NewClient(apiKey string) *Client { } } -func (c Client) GetDomains(ctx context.Context) ([]Domain, error) { +func (c *Client) GetDomains(ctx context.Context) ([]Domain, error) { endpoint := c.baseURL.JoinPath("domains.json") req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -49,7 +49,7 @@ func (c Client) GetDomains(ctx context.Context) ([]Domain, error) { return results.Data, nil } -func (c Client) GetRecords(ctx context.Context, domainID int) ([]Record, error) { +func (c *Client) GetRecords(ctx context.Context, domainID int) ([]Record, error) { endpoint := c.baseURL.JoinPath("domains", strconv.Itoa(domainID), "records.json") req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -66,7 +66,7 @@ func (c Client) GetRecords(ctx context.Context, domainID int) ([]Record, error) return results.Data, nil } -func (c Client) AddRecord(ctx context.Context, domainID int, record Record) error { +func (c *Client) AddRecord(ctx context.Context, domainID int, record Record) error { endpoint := c.baseURL.JoinPath("domains", strconv.Itoa(domainID), "records.json") req, err := newJSONRequest(ctx, http.MethodPost, endpoint, NameserverRecordPayload{Data: record}) @@ -83,7 +83,7 @@ func (c Client) AddRecord(ctx context.Context, domainID int, record Record) erro return nil } -func (c Client) UpdateRecord(ctx context.Context, domainID, recordID int, record Record) error { +func (c *Client) UpdateRecord(ctx context.Context, domainID, recordID int, record Record) error { endpoint := c.baseURL.JoinPath("domains", strconv.Itoa(domainID), "records", strconv.Itoa(recordID)) req, err := newJSONRequest(ctx, http.MethodPut, endpoint, NameserverRecordPayload{Data: record}) @@ -100,7 +100,7 @@ func (c Client) UpdateRecord(ctx context.Context, domainID, recordID int, record return nil } -func (c Client) DeleteRecord(ctx context.Context, domainID, recordID int) error { +func (c *Client) DeleteRecord(ctx context.Context, domainID, recordID int) error { // /domains/{domainId}/records/{recordId} DELETE endpoint := c.baseURL.JoinPath("domains", strconv.Itoa(domainID), "records", strconv.Itoa(recordID)) @@ -118,7 +118,7 @@ func (c Client) DeleteRecord(ctx context.Context, domainID, recordID int) error return nil } -func (c Client) do(req *http.Request, result any) error { +func (c *Client) do(req *http.Request, result any) error { req.SetBasicAuth("api", c.apiKey) resp, err := c.HTTPClient.Do(req) diff --git a/providers/dns/metaregistrar/internal/client.go b/providers/dns/metaregistrar/internal/client.go index 4d3c8adc4..711a1e94c 100644 --- a/providers/dns/metaregistrar/internal/client.go +++ b/providers/dns/metaregistrar/internal/client.go @@ -44,7 +44,7 @@ func NewClient(token string) (*Client, error) { // UpdateDNSZone updates the DNS zone for a domain. // To add or remove a TXT record we make a PATCH request. // https://metaregistrar.dev/docu/metaapi/requests/patch_Update_dns_zone.html -func (c Client) UpdateDNSZone(ctx context.Context, domain string, updateRequest DNSZoneUpdateRequest) (*DNSZoneUpdateResponse, error) { +func (c *Client) UpdateDNSZone(ctx context.Context, domain string, updateRequest DNSZoneUpdateRequest) (*DNSZoneUpdateResponse, error) { endpoint := c.baseURL.JoinPath("dnszone", domain) req, err := newJSONRequest(ctx, http.MethodPatch, endpoint, updateRequest) @@ -62,7 +62,7 @@ func (c Client) UpdateDNSZone(ctx context.Context, domain string, updateRequest return result, nil } -func (c Client) do(req *http.Request, result any) error { +func (c *Client) do(req *http.Request, result any) error { req.Header.Add(tokenHeader, c.token) resp, err := c.HTTPClient.Do(req) diff --git a/providers/dns/mijnhost/internal/client.go b/providers/dns/mijnhost/internal/client.go index 82bdcfeb9..1a67a73b1 100644 --- a/providers/dns/mijnhost/internal/client.go +++ b/providers/dns/mijnhost/internal/client.go @@ -38,7 +38,7 @@ func NewClient(apiKey string) *Client { // ListDomains Retrieve all domains from an account. // https://mijn.host/api/doc/api-3563872 -func (c Client) ListDomains(ctx context.Context) ([]Domain, error) { +func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) { endpoint := c.baseURL.JoinPath("domains") req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -57,7 +57,7 @@ func (c Client) ListDomains(ctx context.Context) ([]Domain, error) { // GetRecords Retrieve DNS records of specific domain. // https://mijn.host/api/doc/api-3563906 -func (c Client) GetRecords(ctx context.Context, domain string) ([]Record, error) { +func (c *Client) GetRecords(ctx context.Context, domain string) ([]Record, error) { endpoint := c.baseURL.JoinPath("domains", domain, "dns") req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -76,7 +76,7 @@ func (c Client) GetRecords(ctx context.Context, domain string) ([]Record, error) // UpdateRecords Update DNS records of specific domain. // https://mijn.host/api/doc/api-3563907 -func (c Client) UpdateRecords(ctx context.Context, domain string, records []Record) error { +func (c *Client) UpdateRecords(ctx context.Context, domain string, records []Record) error { endpoint := c.baseURL.JoinPath("domains", domain, "dns") req, err := newJSONRequest(ctx, http.MethodPut, endpoint, RecordData{Records: records}) @@ -92,7 +92,7 @@ func (c Client) UpdateRecords(ctx context.Context, domain string, records []Reco return nil } -func (c Client) do(req *http.Request, result any) error { +func (c *Client) do(req *http.Request, result any) error { req.Header.Set(authorizationHeader, c.apiKey) resp, err := c.HTTPClient.Do(req) diff --git a/providers/dns/mittwald/internal/client.go b/providers/dns/mittwald/internal/client.go index 712caf8df..69222903d 100644 --- a/providers/dns/mittwald/internal/client.go +++ b/providers/dns/mittwald/internal/client.go @@ -38,7 +38,7 @@ func NewClient(token string) *Client { // ListDomains List Domains. // https://api.mittwald.de/v2/docs/#/Domain/domain-list-domains -func (c Client) ListDomains(ctx context.Context) ([]Domain, error) { +func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) { endpoint := c.baseURL.JoinPath("domains") req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -57,7 +57,7 @@ func (c Client) ListDomains(ctx context.Context) ([]Domain, error) { // GetDNSZone Get a DNSZone. // https://api.mittwald.de/v2/docs/#/Domain/dns-get-dns-zone -func (c Client) GetDNSZone(ctx context.Context, zoneID string) (*DNSZone, error) { +func (c *Client) GetDNSZone(ctx context.Context, zoneID string) (*DNSZone, error) { endpoint := c.baseURL.JoinPath("dns-zones", zoneID) req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -76,7 +76,7 @@ func (c Client) GetDNSZone(ctx context.Context, zoneID string) (*DNSZone, error) // ListDNSZones List DNSZones belonging to a Project. // https://api.mittwald.de/v2/docs/#/Domain/dns-list-dns-zones -func (c Client) ListDNSZones(ctx context.Context, projectID string) ([]DNSZone, error) { +func (c *Client) ListDNSZones(ctx context.Context, projectID string) ([]DNSZone, error) { endpoint := c.baseURL.JoinPath("projects", projectID, "dns-zones") req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -95,7 +95,7 @@ func (c Client) ListDNSZones(ctx context.Context, projectID string) ([]DNSZone, // CreateDNSZone Create a DNSZone. // https://api.mittwald.de/v2/docs/#/Domain/dns-create-dns-zone -func (c Client) CreateDNSZone(ctx context.Context, zone CreateDNSZoneRequest) (*DNSZone, error) { +func (c *Client) CreateDNSZone(ctx context.Context, zone CreateDNSZoneRequest) (*DNSZone, error) { endpoint := c.baseURL.JoinPath("dns-zones") req, err := newJSONRequest(ctx, http.MethodPost, endpoint, zone) @@ -114,7 +114,7 @@ func (c Client) CreateDNSZone(ctx context.Context, zone CreateDNSZoneRequest) (* // UpdateTXTRecord Update a record set on a DNSZone. // https://api.mittwald.de/v2/docs/#/Domain/dns-update-record-set -func (c Client) UpdateTXTRecord(ctx context.Context, zoneID string, record TXTRecord) error { +func (c *Client) UpdateTXTRecord(ctx context.Context, zoneID string, record TXTRecord) error { endpoint := c.baseURL.JoinPath("dns-zones", zoneID, "record-sets", "txt") req, err := newJSONRequest(ctx, http.MethodPut, endpoint, record) @@ -127,7 +127,7 @@ func (c Client) UpdateTXTRecord(ctx context.Context, zoneID string, record TXTRe // DeleteDNSZone Delete a DNSZone. // https://api.mittwald.de/v2/docs/#/Domain/dns-delete-dns-zone -func (c Client) DeleteDNSZone(ctx context.Context, zoneID string) error { +func (c *Client) DeleteDNSZone(ctx context.Context, zoneID string) error { endpoint := c.baseURL.JoinPath("dns-zones", zoneID) req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) @@ -138,7 +138,7 @@ func (c Client) DeleteDNSZone(ctx context.Context, zoneID string) error { return c.do(req, nil) } -func (c Client) do(req *http.Request, result any) error { +func (c *Client) do(req *http.Request, result any) error { req.Header.Set(authorizationHeader, "Bearer "+c.token) resp, err := c.HTTPClient.Do(req) diff --git a/providers/dns/mydnsjp/internal/client.go b/providers/dns/mydnsjp/internal/client.go index 8b6824c29..20469d657 100644 --- a/providers/dns/mydnsjp/internal/client.go +++ b/providers/dns/mydnsjp/internal/client.go @@ -34,15 +34,15 @@ func NewClient(masterID, password string) *Client { } } -func (c Client) AddTXTRecord(ctx context.Context, domain, value string) error { +func (c *Client) AddTXTRecord(ctx context.Context, domain, value string) error { return c.doRequest(ctx, domain, value, "REGIST") } -func (c Client) DeleteTXTRecord(ctx context.Context, domain, value string) error { +func (c *Client) DeleteTXTRecord(ctx context.Context, domain, value string) error { return c.doRequest(ctx, domain, value, "DELETE") } -func (c Client) buildRequest(ctx context.Context, domain, value, cmd string) (*http.Request, error) { +func (c *Client) buildRequest(ctx context.Context, domain, value, cmd string) (*http.Request, error) { params := url.Values{} params.Set("CERTBOT_DOMAIN", domain) params.Set("CERTBOT_VALIDATION", value) @@ -58,7 +58,7 @@ func (c Client) buildRequest(ctx context.Context, domain, value, cmd string) (*h return req, nil } -func (c Client) doRequest(ctx context.Context, domain, value, cmd string) error { +func (c *Client) doRequest(ctx context.Context, domain, value, cmd string) error { req, err := c.buildRequest(ctx, domain, value, cmd) if err != nil { return err diff --git a/providers/dns/nearlyfreespeech/internal/client.go b/providers/dns/nearlyfreespeech/internal/client.go index 69ed8d064..fcfe4e9b7 100644 --- a/providers/dns/nearlyfreespeech/internal/client.go +++ b/providers/dns/nearlyfreespeech/internal/client.go @@ -46,7 +46,7 @@ func NewClient(login, apiKey string) *Client { } } -func (c Client) AddRecord(ctx context.Context, domain string, record Record) error { +func (c *Client) AddRecord(ctx context.Context, domain string, record Record) error { endpoint := c.baseURL.JoinPath("dns", dns01.UnFqdn(domain), "addRR") params, err := querystring.Values(record) @@ -57,7 +57,7 @@ func (c Client) AddRecord(ctx context.Context, domain string, record Record) err return c.doRequest(ctx, endpoint, params) } -func (c Client) RemoveRecord(ctx context.Context, domain string, record Record) error { +func (c *Client) RemoveRecord(ctx context.Context, domain string, record Record) error { endpoint := c.baseURL.JoinPath("dns", dns01.UnFqdn(domain), "removeRR") params, err := querystring.Values(record) @@ -68,7 +68,7 @@ func (c Client) RemoveRecord(ctx context.Context, domain string, record Record) return c.doRequest(ctx, endpoint, params) } -func (c Client) doRequest(ctx context.Context, endpoint *url.URL, params url.Values) error { +func (c *Client) doRequest(ctx context.Context, endpoint *url.URL, params url.Values) error { payload := params.Encode() req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), strings.NewReader(payload)) diff --git a/providers/dns/nicmanager/internal/client.go b/providers/dns/nicmanager/internal/client.go index 5632ae9ad..eb84e29ec 100644 --- a/providers/dns/nicmanager/internal/client.go +++ b/providers/dns/nicmanager/internal/client.go @@ -74,7 +74,7 @@ func NewClient(opts Options) *Client { return c } -func (c Client) GetZone(ctx context.Context, name string) (*Zone, error) { +func (c *Client) GetZone(ctx context.Context, name string) (*Zone, error) { endpoint := c.baseURL.JoinPath(c.mode, name) req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -91,7 +91,7 @@ func (c Client) GetZone(ctx context.Context, name string) (*Zone, error) { return &zone, nil } -func (c Client) AddRecord(ctx context.Context, zone string, payload RecordCreateUpdate) error { +func (c *Client) AddRecord(ctx context.Context, zone string, payload RecordCreateUpdate) error { endpoint := c.baseURL.JoinPath(c.mode, zone, "records") req, err := newJSONRequest(ctx, http.MethodPost, endpoint, payload) @@ -107,7 +107,7 @@ func (c Client) AddRecord(ctx context.Context, zone string, payload RecordCreate return nil } -func (c Client) DeleteRecord(ctx context.Context, zone string, record int) error { +func (c *Client) DeleteRecord(ctx context.Context, zone string, record int) error { endpoint := c.baseURL.JoinPath(c.mode, zone, "records", strconv.Itoa(record)) req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) @@ -123,7 +123,7 @@ func (c Client) DeleteRecord(ctx context.Context, zone string, record int) error return nil } -func (c Client) do(req *http.Request, expectedStatusCode int, result any) error { +func (c *Client) do(req *http.Request, expectedStatusCode int, result any) error { req.SetBasicAuth(c.username, c.password) if c.otp != "" { diff --git a/providers/dns/plesk/internal/client.go b/providers/dns/plesk/internal/client.go index 5a2e2f4b8..88a7fdd9f 100644 --- a/providers/dns/plesk/internal/client.go +++ b/providers/dns/plesk/internal/client.go @@ -35,7 +35,7 @@ func NewClient(baseURL *url.URL, login, password string) *Client { // GetSite gets a site. // https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-sites-domains/getting-information-about-sites.66583/ -func (c Client) GetSite(ctx context.Context, domain string) (int, error) { +func (c *Client) GetSite(ctx context.Context, domain string) (int, error) { payload := RequestPacketType{Site: &SiteTypeRequest{Get: SiteGetRequest{Filter: &SiteFilterType{ Name: domain, }}}} @@ -62,7 +62,7 @@ func (c Client) GetSite(ctx context.Context, domain string) (int, error) { // AddRecord adds a TXT record. // https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798/ -func (c Client) AddRecord(ctx context.Context, siteID int, host, value string) (int, error) { +func (c *Client) AddRecord(ctx context.Context, siteID int, host, value string) (int, error) { payload := RequestPacketType{DNS: &DNSInputType{AddRec: []AddRecRequest{{ SiteID: siteID, Type: "TXT", @@ -92,7 +92,7 @@ func (c Client) AddRecord(ctx context.Context, siteID int, host, value string) ( // DeleteRecord Deletes a TXT record. // https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/deleting-dns-records.34864/ -func (c Client) DeleteRecord(ctx context.Context, recordID int) (int, error) { +func (c *Client) DeleteRecord(ctx context.Context, recordID int) (int, error) { payload := RequestPacketType{DNS: &DNSInputType{DelRec: []DelRecRequest{{Filter: DNSSelectionFilterType{ ID: recordID, }}}}} @@ -117,7 +117,7 @@ func (c Client) DeleteRecord(ctx context.Context, recordID int) (int, error) { return response.DNS.DelRec[0].Result.ID, nil } -func (c Client) doRequest(ctx context.Context, payload RequestPacketType) (*ResponsePacketType, error) { +func (c *Client) doRequest(ctx context.Context, payload RequestPacketType) (*ResponsePacketType, error) { endpoint := c.baseURL.JoinPath("/enterprise/control/agent.php") body := new(bytes.Buffer) diff --git a/providers/dns/regru/internal/client.go b/providers/dns/regru/internal/client.go index 7ce633b05..4b0205b0f 100644 --- a/providers/dns/regru/internal/client.go +++ b/providers/dns/regru/internal/client.go @@ -38,7 +38,7 @@ func NewClient(username, password string) *Client { // RemoveTxtRecord removes a TXT record. // https://www.reg.ru/support/help/api2#zone_remove_record -func (c Client) RemoveTxtRecord(ctx context.Context, domain, subDomain, content string) error { +func (c *Client) RemoveTxtRecord(ctx context.Context, domain, subDomain, content string) error { request := RemoveRecordRequest{ Domains: []Domain{{DName: domain}}, SubDomain: subDomain, @@ -57,7 +57,7 @@ func (c Client) RemoveTxtRecord(ctx context.Context, domain, subDomain, content // AddTXTRecord adds a TXT record. // https://www.reg.ru/support/help/api2#zone_add_txt -func (c Client) AddTXTRecord(ctx context.Context, domain, subDomain, content string) error { +func (c *Client) AddTXTRecord(ctx context.Context, domain, subDomain, content string) error { request := AddTxtRequest{ Domains: []Domain{{DName: domain}}, SubDomain: subDomain, @@ -73,7 +73,7 @@ func (c Client) AddTXTRecord(ctx context.Context, domain, subDomain, content str return resp.HasError() } -func (c Client) doRequest(ctx context.Context, request any, fragments ...string) (*APIResponse, error) { +func (c *Client) doRequest(ctx context.Context, request any, fragments ...string) (*APIResponse, error) { endpoint := c.baseURL.JoinPath(fragments...) inputData, err := json.Marshal(request) diff --git a/providers/dns/shellrent/internal/client.go b/providers/dns/shellrent/internal/client.go index fbddf3120..8ec02bfc0 100644 --- a/providers/dns/shellrent/internal/client.go +++ b/providers/dns/shellrent/internal/client.go @@ -42,7 +42,7 @@ func NewClient(username, token string) *Client { // ListServices lists service IDs. // https://api.shellrent.com/elenco-dei-servizi-acquistati -func (c Client) ListServices(ctx context.Context) ([]int, error) { +func (c *Client) ListServices(ctx context.Context) ([]int, error) { endpoint := c.baseURL.JoinPath("purchase") req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -72,7 +72,7 @@ func (c Client) ListServices(ctx context.Context) ([]int, error) { // GetServiceDetails gets service details. // https://api.shellrent.com/dettagli-servizio-acquistato -func (c Client) GetServiceDetails(ctx context.Context, serviceID int) (*ServiceDetails, error) { +func (c *Client) GetServiceDetails(ctx context.Context, serviceID int) (*ServiceDetails, error) { endpoint := c.baseURL.JoinPath("purchase", "details", strconv.Itoa(serviceID)) req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -96,7 +96,7 @@ func (c Client) GetServiceDetails(ctx context.Context, serviceID int) (*ServiceD // GetDomainDetails gets domain details. // https://api.shellrent.com/dettagli-dominio -func (c Client) GetDomainDetails(ctx context.Context, domainID int) (*DomainDetails, error) { +func (c *Client) GetDomainDetails(ctx context.Context, domainID int) (*DomainDetails, error) { endpoint := c.baseURL.JoinPath("domain", "details", strconv.Itoa(domainID)) req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -119,7 +119,7 @@ func (c Client) GetDomainDetails(ctx context.Context, domainID int) (*DomainDeta // CreateRecord created a record. // https://api.shellrent.com/creazione-record-dns-di-un-dominio -func (c Client) CreateRecord(ctx context.Context, domainID int, record Record) (int, error) { +func (c *Client) CreateRecord(ctx context.Context, domainID int, record Record) (int, error) { endpoint := c.baseURL.JoinPath("dns_record", "store", strconv.Itoa(domainID)) req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) @@ -142,7 +142,7 @@ func (c Client) CreateRecord(ctx context.Context, domainID int, record Record) ( // DeleteRecord deletes a record. // https://api.shellrent.com/eliminazione-record-dns-di-un-dominio -func (c Client) DeleteRecord(ctx context.Context, domainID, recordID int) error { +func (c *Client) DeleteRecord(ctx context.Context, domainID, recordID int) error { endpoint := c.baseURL.JoinPath("dns_record", "remove", strconv.Itoa(domainID), strconv.Itoa(recordID)) req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) @@ -164,7 +164,7 @@ func (c Client) DeleteRecord(ctx context.Context, domainID, recordID int) error return nil } -func (c Client) do(req *http.Request, result any) error { +func (c *Client) do(req *http.Request, result any) error { req.Header.Set(authorizationHeader, c.username+"."+c.token) resp, err := c.HTTPClient.Do(req) diff --git a/providers/dns/tencentcloud/tencentcloud.go b/providers/dns/tencentcloud/tencentcloud.go index 0b662f8c7..21fb45095 100644 --- a/providers/dns/tencentcloud/tencentcloud.go +++ b/providers/dns/tencentcloud/tencentcloud.go @@ -2,6 +2,7 @@ package tencentcloud import ( + "context" "errors" "fmt" "math" @@ -117,7 +118,9 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - zone, err := d.getHostedZone(info.EffectiveFQDN) + ctx := context.Background() + + zone, err := d.getHostedZone(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("tencentcloud: failed to get hosted zone: %w", err) } @@ -136,7 +139,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { request.Value = common.StringPtr(info.Value) request.TTL = common.Uint64Ptr(uint64(d.config.TTL)) - _, err = d.client.CreateRecord(request) + _, err = d.client.CreateRecordWithContext(ctx, request) if err != nil { return fmt.Errorf("dnspod: API call failed: %w", err) } @@ -148,12 +151,14 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - zone, err := d.getHostedZone(info.EffectiveFQDN) + ctx := context.Background() + + zone, err := d.getHostedZone(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("tencentcloud: failed to get hosted zone: %w", err) } - records, err := d.findTxtRecords(zone, info.EffectiveFQDN) + records, err := d.findTxtRecords(ctx, zone, info.EffectiveFQDN) if err != nil { return fmt.Errorf("tencentcloud: failed to find TXT records: %w", err) } @@ -164,7 +169,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { request.DomainId = zone.DomainId request.RecordId = record.RecordId - _, err := d.client.DeleteRecord(request) + _, err := d.client.DeleteRecordWithContext(ctx, request) if err != nil { return fmt.Errorf("tencentcloud: delete record failed: %w", err) } diff --git a/providers/dns/tencentcloud/wrapper.go b/providers/dns/tencentcloud/wrapper.go index 32b66d523..5ce786515 100644 --- a/providers/dns/tencentcloud/wrapper.go +++ b/providers/dns/tencentcloud/wrapper.go @@ -1,6 +1,7 @@ package tencentcloud import ( + "context" "errors" "fmt" @@ -11,13 +12,13 @@ import ( "golang.org/x/net/idna" ) -func (d *DNSProvider) getHostedZone(domain string) (*dnspod.DomainListItem, error) { +func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*dnspod.DomainListItem, error) { request := dnspod.NewDescribeDomainListRequest() var domains []*dnspod.DomainListItem for { - response, err := d.client.DescribeDomainList(request) + response, err := d.client.DescribeDomainListWithContext(ctx, request) if err != nil { return nil, fmt.Errorf("API call failed: %w", err) } @@ -51,7 +52,7 @@ func (d *DNSProvider) getHostedZone(domain string) (*dnspod.DomainListItem, erro return hostedZone, nil } -func (d *DNSProvider) findTxtRecords(zone *dnspod.DomainListItem, fqdn string) ([]*dnspod.RecordListItem, error) { +func (d *DNSProvider) findTxtRecords(ctx context.Context, zone *dnspod.DomainListItem, fqdn string) ([]*dnspod.RecordListItem, error) { recordName, err := extractRecordName(fqdn, *zone.Name) if err != nil { return nil, err @@ -64,7 +65,7 @@ func (d *DNSProvider) findTxtRecords(zone *dnspod.DomainListItem, fqdn string) ( request.RecordType = common.StringPtr("TXT") request.RecordLine = common.StringPtr("默认") - response, err := d.client.DescribeRecordList(request) + response, err := d.client.DescribeRecordListWithContext(ctx, request) if err != nil { var sdkError *errorsdk.TencentCloudSDKError if errors.As(err, &sdkError) { diff --git a/providers/dns/variomedia/internal/client.go b/providers/dns/variomedia/internal/client.go index 4a671e88e..8c2a124cc 100644 --- a/providers/dns/variomedia/internal/client.go +++ b/providers/dns/variomedia/internal/client.go @@ -38,7 +38,7 @@ func NewClient(apiToken string) *Client { // CreateDNSRecord creates a new DNS entry. // https://api.variomedia.de/docs/dns-records.html#erstellen -func (c Client) CreateDNSRecord(ctx context.Context, record DNSRecord) (*CreateDNSRecordResponse, error) { +func (c *Client) CreateDNSRecord(ctx context.Context, record DNSRecord) (*CreateDNSRecordResponse, error) { endpoint := c.baseURL.JoinPath("dns-records") data := CreateDNSRecordRequest{Data: Data{ @@ -62,7 +62,7 @@ func (c Client) CreateDNSRecord(ctx context.Context, record DNSRecord) (*CreateD // DeleteDNSRecord deletes a DNS record. // https://api.variomedia.de/docs/dns-records.html#l%C3%B6schen -func (c Client) DeleteDNSRecord(ctx context.Context, id string) (*DeleteRecordResponse, error) { +func (c *Client) DeleteDNSRecord(ctx context.Context, id string) (*DeleteRecordResponse, error) { endpoint := c.baseURL.JoinPath("dns-records", id) req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) @@ -81,7 +81,7 @@ func (c Client) DeleteDNSRecord(ctx context.Context, id string) (*DeleteRecordRe // GetJob returns a single job based on its ID. // https://api.variomedia.de/docs/job-queue.html -func (c Client) GetJob(ctx context.Context, id string) (*GetJobResponse, error) { +func (c *Client) GetJob(ctx context.Context, id string) (*GetJobResponse, error) { endpoint := c.baseURL.JoinPath("queue-jobs", id) req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -98,7 +98,7 @@ func (c Client) GetJob(ctx context.Context, id string) (*GetJobResponse, error) return &result, nil } -func (c Client) do(req *http.Request, data any) error { +func (c *Client) do(req *http.Request, data any) error { req.Header.Set(authorizationHeader, "token "+c.apiToken) resp, err := c.HTTPClient.Do(req) diff --git a/providers/dns/yandex/internal/client.go b/providers/dns/yandex/internal/client.go index 5d7e6bff3..98480a793 100644 --- a/providers/dns/yandex/internal/client.go +++ b/providers/dns/yandex/internal/client.go @@ -12,7 +12,7 @@ import ( "time" "github.com/go-acme/lego/v4/providers/dns/internal/errutils" - "github.com/google/go-querystring/query" + querystring "github.com/google/go-querystring/query" ) const defaultBaseURL = "https://pddimp.yandex.ru/api2/admin/dns" @@ -130,7 +130,7 @@ func newRequest(ctx context.Context, method string, endpoint *url.URL, payload a if payload != nil { switch method { case http.MethodPost: - values, err := query.Values(payload) + values, err := querystring.Values(payload) if err != nil { return nil, err } @@ -138,7 +138,7 @@ func newRequest(ctx context.Context, method string, endpoint *url.URL, payload a buf.WriteString(values.Encode()) case http.MethodGet: - values, err := query.Values(payload) + values, err := querystring.Values(payload) if err != nil { return nil, err } diff --git a/providers/dns/yandex360/internal/client.go b/providers/dns/yandex360/internal/client.go index 2bebc6c20..9ffececaf 100644 --- a/providers/dns/yandex360/internal/client.go +++ b/providers/dns/yandex360/internal/client.go @@ -47,7 +47,7 @@ func NewClient(oauthToken string, orgID int64) (*Client, error) { // AddRecord Adds a DNS record. // POST https://api30.yandex.net/directory/v1/org/{orgId}/domains/{domain}/dns // https://yandex.ru/dev/api360/doc/ref/DomainDNSService/DomainDNSService_Create.html -func (c Client) AddRecord(ctx context.Context, domain string, record Record) (*Record, error) { +func (c *Client) AddRecord(ctx context.Context, domain string, record Record) (*Record, error) { endpoint := c.baseURL.JoinPath("directory", "v1", "org", strconv.FormatInt(c.orgID, 10), "domains", domain, "dns") req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) @@ -68,7 +68,7 @@ func (c Client) AddRecord(ctx context.Context, domain string, record Record) (*R // DeleteRecord Deletes a DNS record. // DELETE https://api360.yandex.net/directory/v1/org/{orgId}/domains/{domain}/dns/{recordId} // https://yandex.ru/dev/api360/doc/ref/DomainDNSService/DomainDNSService_Delete.html -func (c Client) DeleteRecord(ctx context.Context, domain string, recordID int64) error { +func (c *Client) DeleteRecord(ctx context.Context, domain string, recordID int64) error { endpoint := c.baseURL.JoinPath("directory", "v1", "org", strconv.FormatInt(c.orgID, 10), "domains", domain, "dns", strconv.FormatInt(recordID, 10)) req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) @@ -79,7 +79,7 @@ func (c Client) DeleteRecord(ctx context.Context, domain string, recordID int64) return c.do(req, nil) } -func (c Client) do(req *http.Request, result any) error { +func (c *Client) do(req *http.Request, result any) error { req.Header.Set("Authorization", "OAuth "+c.oauthToken) resp, err := c.HTTPClient.Do(req) From b0e3fd26824cdfbd04b861e6398609df0960a07e Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 12 Jul 2025 15:10:25 +0200 Subject: [PATCH 124/298] chore: check generated files (#2582) --- .github/workflows/pr.yml | 5 +++++ cmd/zz_gen_cmd_dnshelp.go | 4 ++-- docs/content/dns/zz_gen_zoneedit.md | 8 ++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index eddb5422f..6930ce70a 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -39,6 +39,11 @@ jobs: git diff --exit-code go.mod git diff --exit-code go.sum + - name: Generate and Check generated elements + run: | + make generate-dns + git diff --exit-code + # https://golangci-lint.run/usage/install#other-ci - name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }} run: | diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 10e4ec604..b1772e863 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -3495,8 +3495,8 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Credentials:`) - ew.writeln(` - "ZONEEDIT_PASSWORD": Password`) - ew.writeln(` - "ZONEEDIT_USER_ID": User ID`) + ew.writeln(` - "ZONEEDIT_AUTH_TOKEN": Authentication token`) + ew.writeln(` - "ZONEEDIT_USER": User ID`) ew.writeln() ew.writeln(`Additional Configuration:`) diff --git a/docs/content/dns/zz_gen_zoneedit.md b/docs/content/dns/zz_gen_zoneedit.md index f39f10686..e259a2a04 100644 --- a/docs/content/dns/zz_gen_zoneedit.md +++ b/docs/content/dns/zz_gen_zoneedit.md @@ -26,8 +26,8 @@ Configuration for [ZoneEdit](https://www.zoneedit.com). Here is an example bash command using the ZoneEdit provider: ```bash -ZONEEDIT_USER_ID="xxxxxxxxxxxxxxxxxxxxx" \ -ZONEEDIT_PASSWORD="xxxxxxxxxxxxxxxxxxxxx" \ +ZONEEDIT_USER="xxxxxxxxxxxxxxxxxxxxx" \ +ZONEEDIT_AUTH_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ lego --email you@example.com --dns zoneedit -d '*.example.com' -d example.com run ``` @@ -38,8 +38,8 @@ lego --email you@example.com --dns zoneedit -d '*.example.com' -d example.com ru | Environment Variable Name | Description | |-----------------------|-------------| -| `ZONEEDIT_PASSWORD` | Password | -| `ZONEEDIT_USER_ID` | User ID | +| `ZONEEDIT_AUTH_TOKEN` | Authentication token | +| `ZONEEDIT_USER` | User ID | 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" %}}). From a8e19ef7f312d8dd348a6c2a8317a5be3f01eeeb Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 13 Jul 2025 18:09:26 +0200 Subject: [PATCH 125/298] chore: replace official Cloudflare API client by an internal API client (#2583) --- go.mod | 2 - go.sum | 4 - .../servermock/link_request_body_json.go | 20 +- providers/dns/cloudflare/cloudflare.go | 12 +- providers/dns/cloudflare/cloudflare_test.go | 79 ++++++- providers/dns/cloudflare/internal/client.go | 197 ++++++++++++++++++ .../dns/cloudflare/internal/client_test.go | 176 ++++++++++++++++ .../fixtures/create_record-request.json | 6 + .../internal/fixtures/create_record.json | 40 ++++ .../internal/fixtures/delete_record.json | 5 + .../cloudflare/internal/fixtures/error.json | 17 ++ .../cloudflare/internal/fixtures/zones.json | 83 ++++++++ providers/dns/cloudflare/internal/options.go | 52 +++++ providers/dns/cloudflare/internal/types.go | 120 +++++++++++ providers/dns/cloudflare/wrapper.go | 57 +++-- 15 files changed, 826 insertions(+), 44 deletions(-) create mode 100644 providers/dns/cloudflare/internal/client.go create mode 100644 providers/dns/cloudflare/internal/client_test.go create mode 100644 providers/dns/cloudflare/internal/fixtures/create_record-request.json create mode 100644 providers/dns/cloudflare/internal/fixtures/create_record.json create mode 100644 providers/dns/cloudflare/internal/fixtures/delete_record.json create mode 100644 providers/dns/cloudflare/internal/fixtures/error.json create mode 100644 providers/dns/cloudflare/internal/fixtures/zones.json create mode 100644 providers/dns/cloudflare/internal/options.go create mode 100644 providers/dns/cloudflare/internal/types.go diff --git a/go.mod b/go.mod index 0a15d03c0..e5474f48d 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,6 @@ require ( github.com/baidubce/bce-sdk-go v0.9.223 github.com/cenkalti/backoff/v4 v4.3.0 github.com/civo/civogo v0.3.11 - github.com/cloudflare/cloudflare-go v0.115.0 github.com/dnsimple/dnsimple-go/v4 v4.0.0 github.com/exoscale/egoscale/v3 v3.1.13 github.com/go-jose/go-jose/v4 v4.0.5 @@ -148,7 +147,6 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.16.0 // indirect github.com/go-resty/resty/v2 v2.16.5 // indirect - github.com/goccy/go-json v0.10.5 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect diff --git a/go.sum b/go.sum index bb55586a4..417cc1532 100644 --- a/go.sum +++ b/go.sum @@ -249,8 +249,6 @@ github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5P github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.115.0 h1:84/dxeeXweCc0PN5Cto44iTA8AkG1fyT11yPO5ZB7sM= -github.com/cloudflare/cloudflare-go v0.115.0/go.mod h1:Ds6urDwn/TF2uIU24mu7H91xkKP8gSAHxQ44DSZgVmU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= @@ -359,8 +357,6 @@ github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= diff --git a/platform/tester/servermock/link_request_body_json.go b/platform/tester/servermock/link_request_body_json.go index 1d1fecce9..2e9985c3d 100644 --- a/platform/tester/servermock/link_request_body_json.go +++ b/platform/tester/servermock/link_request_body_json.go @@ -14,9 +14,10 @@ import ( // RequestBodyJSONLink validates JSON request bodies. type RequestBodyJSONLink struct { - body []byte - filename string - data any + body []byte + filename string + directory string + data any } // CheckRequestJSONBody creates a [RequestBodyJSONLink] initialized with a string. @@ -31,7 +32,10 @@ func CheckRequestJSONBodyFromStruct(data any) *RequestBodyJSONLink { // CheckRequestJSONBodyFromFile creates a [RequestBodyJSONLink] initialized with the provided request body file. func CheckRequestJSONBodyFromFile(filename string) *RequestBodyJSONLink { - return &RequestBodyJSONLink{filename: filename} + return &RequestBodyJSONLink{ + filename: filename, + directory: "fixtures", + } } func (l *RequestBodyJSONLink) Bind(next http.Handler) http.Handler { @@ -55,7 +59,7 @@ func (l *RequestBodyJSONLink) Bind(next http.Handler) http.Handler { switch { case l.filename != "": - expectedRaw, err = os.ReadFile(filepath.Join("fixtures", l.filename)) + expectedRaw, err = os.ReadFile(filepath.Join(l.directory, l.filename)) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) return @@ -97,3 +101,9 @@ func (l *RequestBodyJSONLink) Bind(next http.Handler) http.Handler { next.ServeHTTP(rw, req) }) } + +func (l *RequestBodyJSONLink) WithDirectory(directory string) *RequestBodyJSONLink { + l.directory = directory + + return l +} diff --git a/providers/dns/cloudflare/cloudflare.go b/providers/dns/cloudflare/cloudflare.go index 0fa52b34d..5fd350925 100644 --- a/providers/dns/cloudflare/cloudflare.go +++ b/providers/dns/cloudflare/cloudflare.go @@ -11,11 +11,11 @@ import ( "sync" "time" - "github.com/cloudflare/cloudflare-go" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/cloudflare/internal" ) // Environment variables names. @@ -156,24 +156,26 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) + ctx := context.Background() + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) if err != nil { return fmt.Errorf("cloudflare: could not find zone for domain %q: %w", domain, err) } - zoneID, err := d.client.ZoneIDByName(authZone) + zoneID, err := d.client.ZoneIDByName(ctx, authZone) if err != nil { return fmt.Errorf("cloudflare: failed to find zone %s: %w", authZone, err) } - dnsRecord := cloudflare.CreateDNSRecordParams{ + dnsRecord := internal.Record{ Type: "TXT", Name: dns01.UnFqdn(info.EffectiveFQDN), Content: `"` + info.Value + `"`, TTL: d.config.TTL, } - response, err := d.client.CreateDNSRecord(context.Background(), zoneID, dnsRecord) + response, err := d.client.CreateDNSRecord(ctx, zoneID, dnsRecord) if err != nil { return fmt.Errorf("cloudflare: failed to create TXT record: %w", err) } @@ -196,7 +198,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("cloudflare: could not find zone for domain %q: %w", domain, err) } - zoneID, err := d.client.ZoneIDByName(authZone) + zoneID, err := d.client.ZoneIDByName(context.Background(), authZone) if err != nil { return fmt.Errorf("cloudflare: failed to find zone %s: %w", authZone, err) } diff --git a/providers/dns/cloudflare/cloudflare_test.go b/providers/dns/cloudflare/cloudflare_test.go index f3bba69d2..b288931f1 100644 --- a/providers/dns/cloudflare/cloudflare_test.go +++ b/providers/dns/cloudflare/cloudflare_test.go @@ -1,10 +1,13 @@ package cloudflare import ( + "net/http/httptest" + "path/filepath" "testing" "time" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -232,22 +235,17 @@ func TestNewDNSProviderConfig(t *testing.T) { }, { desc: "missing credentials", - expected: "cloudflare: invalid credentials: key & email must not be empty", + expected: "cloudflare: invalid credentials: authEmail, authKey or authToken must be set", }, { desc: "missing email", authKey: "123", - expected: "cloudflare: invalid credentials: key & email must not be empty", + expected: "cloudflare: invalid credentials: authEmail and authKey must be set together", }, { desc: "missing api key", authEmail: "test@example.com", - expected: "cloudflare: invalid credentials: key & email must not be empty", - }, - { - desc: "missing api token, fallback to api key/email", - authToken: "", - expected: "cloudflare: invalid credentials: key & email must not be empty", + expected: "cloudflare: invalid credentials: authEmail and authKey must be set together", }, } @@ -299,3 +297,68 @@ func TestLiveCleanUp(t *testing.T) { err = provider.CleanUp(envTest.GetDomain(), "", "123d==") require.NoError(t, err) } + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.AuthEmail = "foo@example.com" + config.AuthKey = "secret" + config.BaseURL = server.URL + + return NewDNSProviderConfig(config) + }, + servermock.CheckHeader(). + WithRegexp("User-Agent", `goacme-lego/[0-9.]+ \(.+\)`). + With("X-Auth-Email", "foo@example.com"). + With("X-Auth-Key", "secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + // https://developers.cloudflare.com/api/resources/zones/methods/list/ + Route("GET /zones", + responseFromFixture("zones.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com"). + With("per_page", "50")). + // https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/create/ + Route("POST /zones/023e105f4ecef8ad9ca31a8372d0c353/dns_records", + responseFromFixture("create_record.json"), + servermock.CheckHeader(). + WithContentType("application/json"), + servermock.CheckRequestJSONBodyFromFile("create_record-request.json"). + WithDirectory(filepath.Join("internal", "fixtures"))). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + // https://developers.cloudflare.com/api/resources/zones/methods/list/ + Route("GET /zones", + responseFromFixture("zones.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com"). + With("per_page", "50")). + // https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/delete/ + Route("DELETE /zones/023e105f4ecef8ad9ca31a8372d0c353/dns_records/xxx", + responseFromFixture("delete_record.json")). + Build(t) + + token := "abc" + + provider.recordIDsMu.Lock() + provider.recordIDs["abc"] = "xxx" + provider.recordIDsMu.Unlock() + + err := provider.CleanUp("example.com", token, "123d==") + require.NoError(t, err) +} + +func responseFromFixture(filename string) *servermock.ResponseFromFileHandler { + return servermock.ResponseFromFile(filepath.Join("internal", "fixtures", filename)) +} diff --git a/providers/dns/cloudflare/internal/client.go b/providers/dns/cloudflare/internal/client.go new file mode 100644 index 000000000..495ba5618 --- /dev/null +++ b/providers/dns/cloudflare/internal/client.go @@ -0,0 +1,197 @@ +/* +Package internal Cloudflare API client. + +The official client is huge and still growing. +- https://github.com/cloudflare/cloudflare-go/issues/4171 +*/ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" +) + +const defaultBaseURL = "https://api.cloudflare.com/client/v4" + +// Client the Cloudflare API client. +type Client struct { + authEmail string + authKey string + authToken string + + baseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(opts ...Option) (*Client, error) { + baseURL, _ := url.Parse(defaultBaseURL) + + client := &Client{ + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + } + + for _, opt := range opts { + err := opt(client) + if err != nil { + return nil, err + } + } + + if client.authToken != "" { + return client, nil + } + + if client.authEmail == "" && client.authKey == "" { + return nil, errors.New("invalid credentials: authEmail, authKey or authToken must be set") + } + + if client.authEmail == "" || client.authKey == "" { + return nil, errors.New("invalid credentials: authEmail and authKey must be set together") + } + + return client, nil +} + +// CreateDNSRecord creates a new DNS record for a zone. +// https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/create/ +func (c *Client) CreateDNSRecord(ctx context.Context, zoneID string, record Record) (*Record, error) { + endpoint := c.baseURL.JoinPath("zones", zoneID, "dns_records") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) + if err != nil { + return nil, err + } + + var result APIResponse[Record] + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return &result.Result, nil +} + +// DeleteDNSRecord Delete DNS record. +// https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/delete/ +func (c *Client) DeleteDNSRecord(ctx context.Context, zoneID, recordID string) error { + endpoint := c.baseURL.JoinPath("zones", zoneID, "dns_records", recordID) + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + return c.do(req, nil) +} + +// https://developers.cloudflare.com/api/resources/zones/methods/list/ +func (c *Client) ZonesByName(ctx context.Context, name string) ([]Zone, error) { + endpoint := c.baseURL.JoinPath("zones") + + query := endpoint.Query() + query.Set("name", name) + query.Set("per_page", "50") + endpoint.RawQuery = query.Encode() + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var result APIResponse[[]Zone] + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result.Result, nil +} + +func (c *Client) do(req *http.Request, result any) error { + // https://developers.cloudflare.com/fundamentals/api/how-to/make-api-calls/ + if c.authToken != "" { + req.Header.Set("Authorization", "Bearer "+c.authToken) + } else { + req.Header.Set("X-Auth-Email", c.authEmail) + req.Header.Set("X-Auth-Key", c.authKey) + } + + useragent.SetHeader(req.Header) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var response APIResponse[any] + err := json.Unmarshal(raw, &response) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return fmt.Errorf("[status code %d] %w", resp.StatusCode, response.Errors) +} diff --git a/providers/dns/cloudflare/internal/client_test.go b/providers/dns/cloudflare/internal/client_test.go new file mode 100644 index 000000000..69ca2007c --- /dev/null +++ b/providers/dns/cloudflare/internal/client_test.go @@ -0,0 +1,176 @@ +package internal + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient( + WithAuthKey("foo@example.com", "secret"), + WithHTTPClient(server.Client()), + WithBaseURL(server.URL), + ) + if err != nil { + return nil, err + } + + return client, nil + }, + servermock.CheckHeader(). + WithRegexp("User-Agent", `goacme-lego/[0-9.]+ \(.+\)`). + WithAccept("application/json"). + With("X-Auth-Email", "foo@example.com"). + With("X-Auth-Key", "secret"), + ) +} + +func TestClient_CreateDNSRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /zones/023e105f4ecef8ad9ca31a8372d0c353/dns_records", + servermock.ResponseFromFixture("create_record.json"), + servermock.CheckHeader(). + WithContentType("application/json"), + servermock.CheckRequestJSONBodyFromFile("create_record-request.json")). + Build(t) + + record := Record{ + Name: "_acme-challenge.example.com", + TTL: 120, + Type: "TXT", + Content: `"ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"`, + } + + newRecord, err := client.CreateDNSRecord(t.Context(), "023e105f4ecef8ad9ca31a8372d0c353", record) + require.NoError(t, err) + + expected := &Record{ + ID: "023e105f4ecef8ad9ca31a8372d0c353", + Name: "example.com", + TTL: 3600, + Type: "A", + Comment: "Domain verification record", + Content: "198.51.100.4", + } + + assert.Equal(t, expected, newRecord) +} + +func TestClient_CreateDNSRecord_error(t *testing.T) { + client := mockBuilder(). + Route("POST /zones/023e105f4ecef8ad9ca31a8372d0c353/dns_records", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) + + record := Record{ + Name: "_acme-challenge.example.com", + TTL: 120, + Type: "TXT", + Content: `"ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"`, + } + + _, err := client.CreateDNSRecord(t.Context(), "023e105f4ecef8ad9ca31a8372d0c353", record) + require.EqualError(t, err, "[status code 400] 6003: Invalid request headers; 6103: Invalid format for X-Auth-Key header") +} + +func TestClient_DeleteDNSRecord(t *testing.T) { + client := mockBuilder(). + Route("DELETE /zones/023e105f4ecef8ad9ca31a8372d0c353/dns_records/xxx", + servermock.ResponseFromFixture("delete_record.json")). + Build(t) + + err := client.DeleteDNSRecord(context.Background(), "023e105f4ecef8ad9ca31a8372d0c353", "xxx") + require.NoError(t, err) +} + +func TestClient_DeleteDNSRecord_error(t *testing.T) { + client := mockBuilder(). + Route("DELETE /zones/023e105f4ecef8ad9ca31a8372d0c353/dns_records/xxx", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) + + err := client.DeleteDNSRecord(context.Background(), "023e105f4ecef8ad9ca31a8372d0c353", "xxx") + require.EqualError(t, err, "[status code 400] 6003: Invalid request headers; 6103: Invalid format for X-Auth-Key header") +} + +func TestClient_ZonesByName(t *testing.T) { + client := mockBuilder(). + Route("GET /zones", + servermock.ResponseFromFixture("zones.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com"). + With("per_page", "50")). + Build(t) + + zones, err := client.ZonesByName(context.Background(), "example.com") + require.NoError(t, err) + + expected := []Zone{ + { + ID: "023e105f4ecef8ad9ca31a8372d0c353", + Account: Account{ID: "023e105f4ecef8ad9ca31a8372d0c353", Name: "Example Account Name"}, + Meta: Meta{ + CdnOnly: true, + CustomCertificateQuota: 1, + DNSOnly: true, + FoundationDNS: true, + PageRuleQuota: 100, + PhishingDetected: false, + Step: 2, + }, + Name: "example.com", + Owner: Owner{ + ID: "023e105f4ecef8ad9ca31a8372d0c353", + Name: "Example Org", + Type: "organization", + }, + Plan: Plan{ + ID: "023e105f4ecef8ad9ca31a8372d0c353", + CanSubscribe: false, + Currency: "USD", + ExternallyManaged: false, + Frequency: "monthly", + IsSubscribed: false, + LegacyDiscount: false, + LegacyID: "free", + Price: 10, + Name: "Example Org", + }, + CnameSuffix: "cdn.cloudflare.com", + Paused: true, + Permissions: []string{"#worker:read"}, + Tenant: Tenant{ + ID: "023e105f4ecef8ad9ca31a8372d0c353", + Name: "Example Account Name", + }, + TenantUnit: TenantUnit{ + ID: "023e105f4ecef8ad9ca31a8372d0c353", + }, + Type: "full", + VanityNameServers: []string{"ns1.example.com", "ns2.example.com"}, + }, + } + + assert.Equal(t, expected, zones) +} + +func TestClient_ZonesByName_error(t *testing.T) { + client := mockBuilder(). + Route("GET /zones", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) + + _, err := client.ZonesByName(context.Background(), "example.com") + require.EqualError(t, err, "[status code 400] 6003: Invalid request headers; 6103: Invalid format for X-Auth-Key header") +} diff --git a/providers/dns/cloudflare/internal/fixtures/create_record-request.json b/providers/dns/cloudflare/internal/fixtures/create_record-request.json new file mode 100644 index 000000000..1b8604dc9 --- /dev/null +++ b/providers/dns/cloudflare/internal/fixtures/create_record-request.json @@ -0,0 +1,6 @@ +{ + "type": "TXT", + "name": "_acme-challenge.example.com", + "content": "\"ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY\"", + "ttl": 120 +} diff --git a/providers/dns/cloudflare/internal/fixtures/create_record.json b/providers/dns/cloudflare/internal/fixtures/create_record.json new file mode 100644 index 000000000..7e08e993b --- /dev/null +++ b/providers/dns/cloudflare/internal/fixtures/create_record.json @@ -0,0 +1,40 @@ +{ + "errors": [ + { + "code": 1000, + "message": "message", + "documentation_url": "documentation_url", + "source": { + "pointer": "pointer" + } + } + ], + "messages": [ + { + "code": 1000, + "message": "message", + "documentation_url": "documentation_url", + "source": { + "pointer": "pointer" + } + } + ], + "success": true, + "result": { + "name": "example.com", + "ttl": 3600, + "type": "A", + "comment": "Domain verification record", + "content": "198.51.100.4", + "proxied": true, + "settings": { + "ipv4_only": true, + "ipv6_only": true + }, + "tags": [ + "owner:dns-team" + ], + "id": "023e105f4ecef8ad9ca31a8372d0c353", + "proxiable": true + } +} diff --git a/providers/dns/cloudflare/internal/fixtures/delete_record.json b/providers/dns/cloudflare/internal/fixtures/delete_record.json new file mode 100644 index 000000000..038ac7b23 --- /dev/null +++ b/providers/dns/cloudflare/internal/fixtures/delete_record.json @@ -0,0 +1,5 @@ +{ + "result": { + "id": "023e105f4ecef8ad9ca31a8372d0c353" + } +} diff --git a/providers/dns/cloudflare/internal/fixtures/error.json b/providers/dns/cloudflare/internal/fixtures/error.json new file mode 100644 index 000000000..1b2360cc4 --- /dev/null +++ b/providers/dns/cloudflare/internal/fixtures/error.json @@ -0,0 +1,17 @@ +{ + "success": false, + "errors": [ + { + "code": 6003, + "message": "Invalid request headers", + "error_chain": [ + { + "code": 6103, + "message": "Invalid format for X-Auth-Key header" + } + ] + } + ], + "messages": [], + "result": null +} diff --git a/providers/dns/cloudflare/internal/fixtures/zones.json b/providers/dns/cloudflare/internal/fixtures/zones.json new file mode 100644 index 000000000..1dd94c4e3 --- /dev/null +++ b/providers/dns/cloudflare/internal/fixtures/zones.json @@ -0,0 +1,83 @@ +{ + "errors": [ + { + "code": 1000, + "message": "message", + "documentation_url": "documentation_url", + "source": { + "pointer": "pointer" + } + } + ], + "messages": [ + { + "code": 1000, + "message": "message", + "documentation_url": "documentation_url", + "source": { + "pointer": "pointer" + } + } + ], + "success": true, + "result": [ + { + "id": "023e105f4ecef8ad9ca31a8372d0c353", + "account": { + "id": "023e105f4ecef8ad9ca31a8372d0c353", + "name": "Example Account Name" + }, + "meta": { + "cdn_only": true, + "custom_certificate_quota": 1, + "dns_only": true, + "foundation_dns": true, + "page_rule_quota": 100, + "phishing_detected": false, + "step": 2 + }, + "name": "example.com", + "owner": { + "id": "023e105f4ecef8ad9ca31a8372d0c353", + "name": "Example Org", + "type": "organization" + }, + "plan": { + "id": "023e105f4ecef8ad9ca31a8372d0c353", + "can_subscribe": false, + "currency": "USD", + "externally_managed": false, + "frequency": "monthly", + "is_subscribed": false, + "legacy_discount": false, + "legacy_id": "free", + "price": 10, + "name": "Example Org" + }, + "cname_suffix": "cdn.cloudflare.com", + "paused": true, + "permissions": [ + "#worker:read" + ], + "tenant": { + "id": "023e105f4ecef8ad9ca31a8372d0c353", + "name": "Example Account Name" + }, + "tenant_unit": { + "id": "023e105f4ecef8ad9ca31a8372d0c353" + }, + "type": "full", + "vanity_name_servers": [ + "ns1.example.com", + "ns2.example.com" + ] + } + ], + "result_info": { + "count": 1, + "page": 1, + "per_page": 20, + "total_count": 1, + "total_pages": 1 + } +} diff --git a/providers/dns/cloudflare/internal/options.go b/providers/dns/cloudflare/internal/options.go new file mode 100644 index 000000000..aa551a422 --- /dev/null +++ b/providers/dns/cloudflare/internal/options.go @@ -0,0 +1,52 @@ +package internal + +import ( + "net/http" + "net/url" +) + +type Option func(c *Client) error + +func WithAuthKey(authEmail, authKey string) Option { + return func(c *Client) error { + c.authEmail = authEmail + c.authKey = authKey + + return nil + } +} + +func WithAuthToken(authToken string) Option { + return func(c *Client) error { + c.authToken = authToken + + return nil + } +} + +func WithBaseURL(baseURL string) Option { + return func(c *Client) error { + if baseURL == "" { + return nil + } + + bu, err := url.Parse(baseURL) + if err != nil { + return err + } + + c.baseURL = bu + + return nil + } +} + +func WithHTTPClient(client *http.Client) Option { + return func(c *Client) error { + if client != nil { + c.HTTPClient = client + } + + return nil + } +} diff --git a/providers/dns/cloudflare/internal/types.go b/providers/dns/cloudflare/internal/types.go new file mode 100644 index 000000000..2b6d2e2b6 --- /dev/null +++ b/providers/dns/cloudflare/internal/types.go @@ -0,0 +1,120 @@ +package internal + +import "fmt" + +type Record struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + TTL int `json:"ttl,omitempty"` + Type string `json:"type,omitempty"` + Comment string `json:"comment,omitempty"` + Content string `json:"content,omitempty"` +} + +type APIResponse[T any] struct { + Errors Errors `json:"errors,omitempty"` + Messages []Message `json:"messages,omitempty"` + Success bool `json:"success,omitempty"` + Result T `json:"result,omitempty"` + ResultInfo *ResultInfo `json:"result_info,omitempty"` +} + +type Message struct { + Code int `json:"code"` + Message string `json:"message"` + DocumentationURL string `json:"documentation_url"` + Source *Source `json:"source"` + ErrorChain []ErrorChain `json:"error_chain"` +} + +type Source struct { + Pointer string `json:"pointer"` +} + +type ErrorChain struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type Errors []Message + +func (e Errors) Error() string { + var msg string + + for _, item := range e { + msg = fmt.Sprintf("%d: %s", item.Code, item.Message) + + for _, link := range item.ErrorChain { + msg += fmt.Sprintf("; %d: %s", link.Code, link.Message) + } + } + + return msg +} + +type ResultInfo struct { + Count int `json:"count"` + Page int `json:"page"` + PerPage int `json:"per_page"` + TotalCount int `json:"total_count"` + TotalPages int `json:"total_pages"` +} + +type Zone struct { + ID string `json:"id"` + Account Account `json:"account"` + Meta Meta `json:"meta"` + Name string `json:"name"` + Owner Owner `json:"owner"` + Plan Plan `json:"plan"` + CnameSuffix string `json:"cname_suffix"` + Paused bool `json:"paused"` + Permissions []string `json:"permissions"` + Tenant Tenant `json:"tenant"` + TenantUnit TenantUnit `json:"tenant_unit"` + Type string `json:"type"` + VanityNameServers []string `json:"vanity_name_servers"` +} + +type Account struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type Meta struct { + CdnOnly bool `json:"cdn_only"` + CustomCertificateQuota int `json:"custom_certificate_quota"` + DNSOnly bool `json:"dns_only"` + FoundationDNS bool `json:"foundation_dns"` + PageRuleQuota int `json:"page_rule_quota"` + PhishingDetected bool `json:"phishing_detected"` + Step int `json:"step"` +} + +type Owner struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` +} + +type Plan struct { + ID string `json:"id"` + CanSubscribe bool `json:"can_subscribe"` + Currency string `json:"currency"` + ExternallyManaged bool `json:"externally_managed"` + Frequency string `json:"frequency"` + IsSubscribed bool `json:"is_subscribed"` + LegacyDiscount bool `json:"legacy_discount"` + LegacyID string `json:"legacy_id"` + Price int `json:"price"` + Name string `json:"name"` +} + +type Tenant struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type TenantUnit struct { + ID string `json:"id"` +} diff --git a/providers/dns/cloudflare/wrapper.go b/providers/dns/cloudflare/wrapper.go index 92733a57f..1ab36800d 100644 --- a/providers/dns/cloudflare/wrapper.go +++ b/providers/dns/cloudflare/wrapper.go @@ -2,29 +2,28 @@ package cloudflare import ( "context" + "errors" "sync" - "github.com/cloudflare/cloudflare-go" "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/providers/dns/cloudflare/internal" ) type metaClient struct { - clientEdit *cloudflare.API // needs Zone/DNS/Edit permissions - clientRead *cloudflare.API // needs Zone/Zone/Read permissions + clientEdit *internal.Client // needs Zone/DNS/Edit permissions + clientRead *internal.Client // needs Zone/Zone/Read permissions zones map[string]string // caches calls to ZoneIDByName, see lookupZoneID() zonesMu *sync.RWMutex } func newClient(config *Config) (*metaClient, error) { - options := []cloudflare.Option{cloudflare.HTTPClient(config.HTTPClient)} - if config.BaseURL != "" { - options = append(options, cloudflare.BaseURL(config.BaseURL)) - } - // with AuthKey/AuthEmail we can access all available APIs if config.AuthToken == "" { - client, err := cloudflare.New(config.AuthKey, config.AuthEmail, options...) + client, err := internal.NewClient( + internal.WithBaseURL(config.BaseURL), + internal.WithHTTPClient(config.HTTPClient), + internal.WithAuthKey(config.AuthEmail, config.AuthKey)) if err != nil { return nil, err } @@ -37,7 +36,10 @@ func newClient(config *Config) (*metaClient, error) { }, nil } - dns, err := cloudflare.NewWithAPIToken(config.AuthToken, options...) + dns, err := internal.NewClient( + internal.WithBaseURL(config.BaseURL), + internal.WithHTTPClient(config.HTTPClient), + internal.WithAuthToken(config.AuthToken)) if err != nil { return nil, err } @@ -51,7 +53,10 @@ func newClient(config *Config) (*metaClient, error) { }, nil } - zone, err := cloudflare.NewWithAPIToken(config.ZoneToken, options...) + zone, err := internal.NewClient( + internal.WithBaseURL(config.BaseURL), + internal.WithHTTPClient(config.HTTPClient), + internal.WithAuthToken(config.ZoneToken)) if err != nil { return nil, err } @@ -64,19 +69,15 @@ func newClient(config *Config) (*metaClient, error) { }, nil } -func (m *metaClient) CreateDNSRecord(ctx context.Context, zoneID string, rr cloudflare.CreateDNSRecordParams) (cloudflare.DNSRecord, error) { - return m.clientEdit.CreateDNSRecord(ctx, cloudflare.ZoneIdentifier(zoneID), rr) -} - -func (m *metaClient) DNSRecords(ctx context.Context, zoneID string, rr cloudflare.ListDNSRecordsParams) ([]cloudflare.DNSRecord, *cloudflare.ResultInfo, error) { - return m.clientEdit.ListDNSRecords(ctx, cloudflare.ZoneIdentifier(zoneID), rr) +func (m *metaClient) CreateDNSRecord(ctx context.Context, zoneID string, rr internal.Record) (*internal.Record, error) { + return m.clientEdit.CreateDNSRecord(ctx, zoneID, rr) } func (m *metaClient) DeleteDNSRecord(ctx context.Context, zoneID, recordID string) error { - return m.clientEdit.DeleteDNSRecord(ctx, cloudflare.ZoneIdentifier(zoneID), recordID) + return m.clientEdit.DeleteDNSRecord(ctx, zoneID, recordID) } -func (m *metaClient) ZoneIDByName(fdqn string) (string, error) { +func (m *metaClient) ZoneIDByName(ctx context.Context, fdqn string) (string, error) { m.zonesMu.RLock() id := m.zones[fdqn] m.zonesMu.RUnlock() @@ -85,7 +86,12 @@ func (m *metaClient) ZoneIDByName(fdqn string) (string, error) { return id, nil } - id, err := m.clientRead.ZoneIDByName(dns01.UnFqdn(fdqn)) + zones, err := m.clientRead.ZonesByName(ctx, dns01.UnFqdn(fdqn)) + if err != nil { + return "", err + } + + id, err = extractZoneID(zones) if err != nil { return "", err } @@ -95,3 +101,14 @@ func (m *metaClient) ZoneIDByName(fdqn string) (string, error) { m.zonesMu.Unlock() return id, nil } + +func extractZoneID(res []internal.Zone) (string, error) { + switch len(res) { + case 0: + return "", errors.New("zone could not be found") + case 1: + return res[0].ID, nil + default: + return "", errors.New("ambiguous zone name; an account ID might help") + } +} From 1742e6d0ae14e22ff703cbcd709d26df16cdb841 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 13 Jul 2025 18:27:36 +0200 Subject: [PATCH 126/298] chore: replace official Civo API client by an internal API client (#2584) --- go.mod | 1 - go.sum | 4 - providers/dns/civo/civo.go | 50 ++++- providers/dns/civo/civo_test.go | 71 ++++++ providers/dns/civo/internal/client.go | 212 ++++++++++++++++++ providers/dns/civo/internal/client_test.go | 155 +++++++++++++ .../fixtures/create_dns_record-request.json | 6 + .../internal/fixtures/create_dns_record.json | 11 + .../internal/fixtures/delete_dns_record.json | 3 + .../dns/civo/internal/fixtures/error.json | 4 + .../internal/fixtures/list_dns_records.json | 13 ++ .../internal/fixtures/list_domain_names.json | 7 + providers/dns/civo/internal/types.go | 28 +++ 13 files changed, 548 insertions(+), 17 deletions(-) create mode 100644 providers/dns/civo/internal/client.go create mode 100644 providers/dns/civo/internal/client_test.go create mode 100644 providers/dns/civo/internal/fixtures/create_dns_record-request.json create mode 100644 providers/dns/civo/internal/fixtures/create_dns_record.json create mode 100644 providers/dns/civo/internal/fixtures/delete_dns_record.json create mode 100644 providers/dns/civo/internal/fixtures/error.json create mode 100644 providers/dns/civo/internal/fixtures/list_dns_records.json create mode 100644 providers/dns/civo/internal/fixtures/list_domain_names.json create mode 100644 providers/dns/civo/internal/types.go diff --git a/go.mod b/go.mod index e5474f48d..ba66c7c36 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,6 @@ require ( github.com/aziontech/azionapi-go-sdk v0.142.0 github.com/baidubce/bce-sdk-go v0.9.223 github.com/cenkalti/backoff/v4 v4.3.0 - github.com/civo/civogo v0.3.11 github.com/dnsimple/dnsimple-go/v4 v4.0.0 github.com/exoscale/egoscale/v3 v3.1.13 github.com/go-jose/go-jose/v4 v4.0.5 diff --git a/go.sum b/go.sum index 417cc1532..e62ae05c0 100644 --- a/go.sum +++ b/go.sum @@ -243,8 +243,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= -github.com/civo/civogo v0.3.11 h1:mON/fyrV946Sbk6paRtOSGsN+asCgCmHCgArf5xmGxM= -github.com/civo/civogo v0.3.11/go.mod h1:7+GeeFwc4AYTULaEshpT2vIcl3Qq8HPoxA17viX3l6g= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= @@ -724,7 +722,6 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= @@ -1107,7 +1104,6 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= diff --git a/providers/dns/civo/civo.go b/providers/dns/civo/civo.go index e2ee41bd4..46c474b52 100644 --- a/providers/dns/civo/civo.go +++ b/providers/dns/civo/civo.go @@ -2,14 +2,16 @@ package civo import ( + "context" "errors" "fmt" + "net/http" "time" - "github.com/civo/civogo" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/civo/internal" ) // Environment variables names. @@ -21,6 +23,7 @@ const ( EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) const ( @@ -33,11 +36,12 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - ProjectID string - Token string + Token string + PropagationTimeout time.Duration PollingInterval time.Duration TTL int + HTTPClient *http.Client } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -46,13 +50,16 @@ func NewDefaultConfig() *Config { TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, defaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, defaultPollingInterval), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, } } // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { config *Config - client *civogo.Client + client *internal.Client } // NewDNSProvider returns a DNSProvider instance configured for CIVO. @@ -84,7 +91,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } // Create a Civo client - DNS is region independent, we can use any region - client, err := civogo.NewClient(config.Token, "LON1") + client, err := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.Token), "LON1") if err != nil { return nil, fmt.Errorf("civo: %w", err) } @@ -96,6 +103,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) + ctx := context.Background() + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) if err != nil { return fmt.Errorf("civo: could not find zone for domain %q: %w", domain, err) @@ -103,7 +112,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { zone := dns01.UnFqdn(authZone) - dnsDomain, err := d.client.GetDNSDomain(zone) + domainID, err := d.getDomainIDByName(ctx, zone) if err != nil { return fmt.Errorf("civo: %w", err) } @@ -113,10 +122,10 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("civo: %w", err) } - _, err = d.client.CreateDNSRecord(dnsDomain.ID, &civogo.DNSRecordConfig{ + _, err = d.client.CreateDNSRecord(ctx, domainID, internal.Record{ Name: subDomain, Value: info.Value, - Type: civogo.DNSRecordTypeTXT, + Type: "TXT", TTL: d.config.TTL, }) if err != nil { @@ -130,6 +139,8 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) + ctx := context.Background() + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) if err != nil { return fmt.Errorf("civo: could not find zone for domain %q: %w", domain, err) @@ -137,12 +148,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { zone := dns01.UnFqdn(authZone) - dnsDomain, err := d.client.GetDNSDomain(zone) + domainID, err := d.getDomainIDByName(ctx, zone) if err != nil { return fmt.Errorf("civo: %w", err) } - dnsRecords, err := d.client.ListDNSRecords(dnsDomain.ID) + dnsRecords, err := d.client.ListDNSRecords(ctx, domainID) if err != nil { return fmt.Errorf("civo: %w", err) } @@ -152,7 +163,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("civo: %w", err) } - var dnsRecord civogo.DNSRecord + var dnsRecord internal.Record for _, entry := range dnsRecords { if entry.Name == subDomain && entry.Value == info.Value { dnsRecord = entry @@ -160,7 +171,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } } - _, err = d.client.DeleteDNSRecord(&dnsRecord) + err = d.client.DeleteDNSRecord(ctx, dnsRecord) if err != nil { return fmt.Errorf("civo: %w", err) } @@ -173,3 +184,18 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } + +func (d *DNSProvider) getDomainIDByName(ctx context.Context, domain string) (string, error) { + domains, err := d.client.ListDomains(ctx) + if err != nil { + return "", fmt.Errorf("list domains: %w", err) + } + + for _, d := range domains { + if d.Name == domain { + return d.ID, nil + } + } + + return "", fmt.Errorf("domain %q not found", domain) +} diff --git a/providers/dns/civo/civo_test.go b/providers/dns/civo/civo_test.go index 333cf0b1f..8d08a17cd 100644 --- a/providers/dns/civo/civo_test.go +++ b/providers/dns/civo/civo_test.go @@ -2,10 +2,14 @@ package civo import ( "fmt" + "net/http/httptest" + "net/url" + "path/filepath" "testing" "time" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -124,3 +128,70 @@ func TestLiveCleanUp(t *testing.T) { err = provider.CleanUp(envTest.GetDomain(), "", "123d==") require.NoError(t, err) } + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.Token = "secret" + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With("Authorization", "Bearer secret"). + WithRegexp("User-Agent", `goacme-lego/[0-9.]+ \(.+\)`), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + // https://www.civo.com/api/dns#list-domain-names + Route("GET /dns", + responseFromFixture("list_domain_names.json"), + servermock.CheckQueryParameter().Strict(). + With("region", "LON1")). + // https://www.civo.com/api/dns#create-a-new-dns-record + Route("POST /dns/7088fcea-7658-43e6-97fa-273f901978fd/records", + responseFromFixture("create_dns_record.json"), + servermock.CheckRequestJSONBodyFromFile("create_dns_record-request.json"). + WithDirectory(filepath.Join("internal", "fixtures"))). + Build(t) + + err := provider.Present("example.com", "abd", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + // https://www.civo.com/api/dns#list-domain-names + Route("GET /dns", + responseFromFixture("list_domain_names.json"), + servermock.CheckQueryParameter(). + With("region", "LON1")). + // https://www.civo.com/api/dns#list-dns-records + Route("GET /dns/7088fcea-7658-43e6-97fa-273f901978fd/records", + responseFromFixture("list_dns_records.json"), + servermock.CheckQueryParameter().Strict(). + With("region", "LON1")). + // https://www.civo.com/api/dns#deleting-a-dns-record + Route("DELETE /dns/edc5dacf-a2ad-4757-41ee-c12f06259c70/records/76cc107f-fbef-4e2b-b97f-f5d34f4075d3", + responseFromFixture("delete_dns_record.json"), + servermock.CheckQueryParameter().Strict(). + With("region", "LON1")). + Build(t) + + err := provider.CleanUp("example.com", "abd", "123d==") + require.NoError(t, err) +} + +func responseFromFixture(filename string) *servermock.ResponseFromFileHandler { + return servermock.ResponseFromFile(filepath.Join("internal", "fixtures", filename)) +} diff --git a/providers/dns/civo/internal/client.go b/providers/dns/civo/internal/client.go new file mode 100644 index 000000000..25a11ef60 --- /dev/null +++ b/providers/dns/civo/internal/client.go @@ -0,0 +1,212 @@ +/* +Package internal Civo API client. + +Because the dependencies on k8s, the official client cannot be used. +- https://github.com/civo/civogo/blob/v0.2.99/go.mod -> k8s.io/client-go +- https://github.com/civo/civogo/blob/v0.3.34/go.mod -> k8s.io/api +- https://github.com/civo/civogo/blob/v0.3.38/go.mod -> k8s.io/api + k8s.io/apimachinery +- Current version -> https://github.com/civo/civogo/blob/v0.6.1/go.mod +*/ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" + "golang.org/x/oauth2" +) + +const defaultBaseURL = "https://api.civo.com/v2" + +// Client the Civo API client. +type Client struct { + region string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(hc *http.Client, region string) (*Client, error) { + baseURL, _ := url.Parse(defaultBaseURL) + + if hc == nil { + hc = &http.Client{Timeout: 10 * time.Second} + } + + return &Client{ + region: region, + BaseURL: baseURL, + HTTPClient: hc, + }, nil +} + +// ListDomains a list of all domain names within the account. +// https://www.civo.com/api/dns#list-domain-names +func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) { + endpoint := c.BaseURL.JoinPath("dns") + + req, err := c.newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var result []Domain + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result, nil +} + +// ListDNSRecords a list of all DNS records in the specified domain. +// https://www.civo.com/api/dns#list-dns-records +func (c *Client) ListDNSRecords(ctx context.Context, domainID string) ([]Record, error) { + endpoint := c.BaseURL.JoinPath("dns", domainID, "records") + + req, err := c.newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var result []Record + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result, nil +} + +// CreateDNSRecord creates DNS records for a specific domain. +// https://www.civo.com/api/dns#create-a-new-dns-record +func (c *Client) CreateDNSRecord(ctx context.Context, domainID string, record Record) (*Record, error) { + endpoint := c.BaseURL.JoinPath("dns", domainID, "records") + + req, err := c.newJSONRequest(ctx, http.MethodPost, endpoint, record) + if err != nil { + return nil, err + } + + var result Record + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return &result, nil +} + +// DeleteDNSRecord remove a DNS record from a domain. +// https://www.civo.com/api/dns#deleting-a-dns-record +func (c *Client) DeleteDNSRecord(ctx context.Context, record Record) error { + endpoint := c.BaseURL.JoinPath("dns", record.DomainID, "records", record.ID) + + req, err := c.newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) do(req *http.Request, result any) error { + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func (c *Client) newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + if method == http.MethodGet || method == http.MethodDelete { + query := endpoint.Query() + query.Set("region", c.region) + + endpoint.RawQuery = query.Encode() + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + useragent.SetHeader(req.Header) + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} + +// OAuthStaticAccessToken Authorization header. +// https://www.civo.com/api#authentication +func OAuthStaticAccessToken(client *http.Client, accessToken string) *http.Client { + if client == nil { + client = &http.Client{Timeout: 5 * time.Second} + } + + client.Transport = &oauth2.Transport{ + Source: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: accessToken}), + Base: client.Transport, + } + + return client +} diff --git a/providers/dns/civo/internal/client_test.go b/providers/dns/civo/internal/client_test.go new file mode 100644 index 000000000..42b4ad718 --- /dev/null +++ b/providers/dns/civo/internal/client_test.go @@ -0,0 +1,155 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(OAuthStaticAccessToken(server.Client(), "secret"), "LON1") + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With("Authorization", "Bearer secret"). + WithRegexp("User-Agent", `goacme-lego/[0-9.]+ \(.+\)`), + ) +} + +func TestClient_ListDomains(t *testing.T) { + client := mockBuilder(). + Route("GET /dns", + servermock.ResponseFromFixture("list_domain_names.json"), + servermock.CheckQueryParameter().Strict(). + With("region", "LON1")). + Build(t) + + domains, err := client.ListDomains(t.Context()) + require.NoError(t, err) + + expected := []Domain{{ + ID: "7088fcea-7658-43e6-97fa-273f901978fd", + AccountID: "e7e8386e-434e-482f-95e0-c406e5d564c2", + Name: "example.com", + }} + + assert.Equal(t, expected, domains) +} + +func TestClient_ListDNSRecords(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/7088fcea-7658-43e6-97fa-273f901978fd/records", + servermock.ResponseFromFixture("list_dns_records.json"), + servermock.CheckQueryParameter().Strict(). + With("region", "LON1")). + Build(t) + + records, err := client.ListDNSRecords(t.Context(), "7088fcea-7658-43e6-97fa-273f901978fd") + require.NoError(t, err) + + expected := []Record{ + { + ID: "76cc107f-fbef-4e2b-b97f-f5d34f4075d3", + DomainID: "edc5dacf-a2ad-4757-41ee-c12f06259c70", + Name: "_acme-challenge", + Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + Type: "txt", + TTL: 600, + }, + } + + assert.Equal(t, expected, records) +} + +func TestClient_ListDNSRecords_error(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/7088fcea-7658-43e6-97fa-273f901978fd/records", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + _, err := client.ListDNSRecords(t.Context(), "7088fcea-7658-43e6-97fa-273f901978fd") + require.EqualError(t, err, "database_account_not_found: Failed to find the account within the internal database") +} + +func TestClient_ListDNSRecords_error_raw(t *testing.T) { + // the API says: + // > 4xx/5xx status may not be JSON, unless it's obvious that the response should be parsed for a specific reason. + // > So, for example, 404 Not Found pages are a standard page of text + // > but 403 Unauthorized requests may have a reason attribute available in the JSON object. + // https://www.civo.com/api#parameters-and-responses + + client := mockBuilder(). + Route("GET /dns/7088fcea-7658-43e6-97fa-273f901978fd/records", + servermock.RawStringResponse(http.StatusText(http.StatusNotFound)). + WithStatusCode(http.StatusNotFound)). + Build(t) + + _, err := client.ListDNSRecords(t.Context(), "7088fcea-7658-43e6-97fa-273f901978fd") + require.EqualError(t, err, "unexpected status code: [status code: 404] body: Not Found") +} + +func TestClient_CreateDNSRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /dns/7088fcea-7658-43e6-97fa-273f901978fd/records", + servermock.ResponseFromFixture("create_dns_record.json"), + servermock.CheckRequestJSONBodyFromFile("create_dns_record-request.json")). + Build(t) + + record := Record{ + Name: "_acme-challenge", + Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + Type: "TXT", + TTL: 600, + } + + newRecord, err := client.CreateDNSRecord(t.Context(), "7088fcea-7658-43e6-97fa-273f901978fd", record) + require.NoError(t, err) + + expected := &Record{ + ID: "76cc107f-fbef-4e2b-b97f-f5d34f4075d3", + DomainID: "edc5dacf-a2ad-4757-41ee-c12f06259c70", + Name: "_acme-challenge", + Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + Type: "txt", + TTL: 600, + } + + assert.Equal(t, expected, newRecord) +} + +func TestClient_DeleteDNSRecord(t *testing.T) { + client := mockBuilder(). + Route("DELETE /dns/edc5dacf-a2ad-4757-41ee-c12f06259c70/records/76cc107f-fbef-4e2b-b97f-f5d34f4075d3", + servermock.ResponseFromFixture("delete_dns_record.json"), + servermock.CheckQueryParameter().Strict(). + With("region", "LON1")). + Build(t) + + record := Record{ + ID: "76cc107f-fbef-4e2b-b97f-f5d34f4075d3", + DomainID: "edc5dacf-a2ad-4757-41ee-c12f06259c70", + Name: "_acme-challenge", + Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + Type: "TXT", + TTL: 600, + } + + err := client.DeleteDNSRecord(t.Context(), record) + require.NoError(t, err) +} diff --git a/providers/dns/civo/internal/fixtures/create_dns_record-request.json b/providers/dns/civo/internal/fixtures/create_dns_record-request.json new file mode 100644 index 000000000..ec881e142 --- /dev/null +++ b/providers/dns/civo/internal/fixtures/create_dns_record-request.json @@ -0,0 +1,6 @@ +{ + "type": "TXT", + "name": "_acme-challenge", + "value": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "ttl": 600 +} diff --git a/providers/dns/civo/internal/fixtures/create_dns_record.json b/providers/dns/civo/internal/fixtures/create_dns_record.json new file mode 100644 index 000000000..d9557cf23 --- /dev/null +++ b/providers/dns/civo/internal/fixtures/create_dns_record.json @@ -0,0 +1,11 @@ +{ + "id": "76cc107f-fbef-4e2b-b97f-f5d34f4075d3", + "created_at": "2019-04-11T12:47:56.000+01:00", + "updated_at": "2019-04-11T12:47:56.000+01:00", + "account_id": null, + "domain_id": "edc5dacf-a2ad-4757-41ee-c12f06259c70", + "name": "_acme-challenge", + "value": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "type": "txt", + "ttl": 600 +} diff --git a/providers/dns/civo/internal/fixtures/delete_dns_record.json b/providers/dns/civo/internal/fixtures/delete_dns_record.json new file mode 100644 index 000000000..80bf76ad5 --- /dev/null +++ b/providers/dns/civo/internal/fixtures/delete_dns_record.json @@ -0,0 +1,3 @@ +{ + "result": "success" +} diff --git a/providers/dns/civo/internal/fixtures/error.json b/providers/dns/civo/internal/fixtures/error.json new file mode 100644 index 000000000..0a55e079f --- /dev/null +++ b/providers/dns/civo/internal/fixtures/error.json @@ -0,0 +1,4 @@ +{ + "code": "database_account_not_found", + "reason": "Failed to find the account within the internal database" +} diff --git a/providers/dns/civo/internal/fixtures/list_dns_records.json b/providers/dns/civo/internal/fixtures/list_dns_records.json new file mode 100644 index 000000000..0c4e54737 --- /dev/null +++ b/providers/dns/civo/internal/fixtures/list_dns_records.json @@ -0,0 +1,13 @@ +[ + { + "id": "76cc107f-fbef-4e2b-b97f-f5d34f4075d3", + "created_at": "2019-04-11T12:47:56.000+01:00", + "updated_at": "2019-04-11T12:47:56.000+01:00", + "account_id": null, + "domain_id": "edc5dacf-a2ad-4757-41ee-c12f06259c70", + "name": "_acme-challenge", + "value": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "type": "txt", + "ttl": 600 + } +] diff --git a/providers/dns/civo/internal/fixtures/list_domain_names.json b/providers/dns/civo/internal/fixtures/list_domain_names.json new file mode 100644 index 000000000..909cdca04 --- /dev/null +++ b/providers/dns/civo/internal/fixtures/list_domain_names.json @@ -0,0 +1,7 @@ +[ + { + "id": "7088fcea-7658-43e6-97fa-273f901978fd", + "account_id": "e7e8386e-434e-482f-95e0-c406e5d564c2", + "name": "example.com" + } +] diff --git a/providers/dns/civo/internal/types.go b/providers/dns/civo/internal/types.go new file mode 100644 index 000000000..d173e2fcd --- /dev/null +++ b/providers/dns/civo/internal/types.go @@ -0,0 +1,28 @@ +package internal + +import "fmt" + +type APIError struct { + Code string `json:"code"` + Reason string `json:"reason"` +} + +func (a *APIError) Error() string { + return fmt.Sprintf("%s: %s", a.Code, a.Reason) +} + +type Record struct { + ID string `json:"id,omitempty"` + AccountID string `json:"account_id,omitempty"` + DomainID string `json:"domain_id,omitempty"` + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` + Type string `json:"type,omitempty"` + TTL int `json:"ttl,omitempty"` +} + +type Domain struct { + ID string `json:"id,omitempty"` + AccountID string `json:"account_id,omitempty"` + Name string `json:"name,omitempty"` +} From 8b40479678cb7e1b34b1b73180f237112361cd36 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 15 Jul 2025 14:24:45 +0200 Subject: [PATCH 127/298] chore: migrate to yandex cloud API Client v2 (#2585) --- go.mod | 45 ++++++----- go.sum | 99 ++++++++++++------------ providers/dns/yandexcloud/yandexcloud.go | 55 ++++++------- 3 files changed, 101 insertions(+), 98 deletions(-) diff --git a/go.mod b/go.mod index ba66c7c36..638a2a335 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/google/go-querystring v1.1.0 github.com/gophercloud/gophercloud v1.14.1 github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 - github.com/hashicorp/go-retryablehttp v0.7.7 + github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/go-version v1.7.0 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.141 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df @@ -83,12 +83,13 @@ require ( github.com/vinyldns/go-vinyldns v0.9.16 github.com/volcengine/volc-sdk-golang v1.0.199 github.com/vultr/govultr/v3 v3.17.0 - github.com/yandex-cloud/go-genproto v0.0.0-20250319153614-fb9d3e5eb01a - github.com/yandex-cloud/go-sdk v0.0.0-20250320143332-9cbcfc5de4ae - golang.org/x/crypto v0.36.0 - golang.org/x/net v0.37.0 + github.com/yandex-cloud/go-genproto v0.14.0 + github.com/yandex-cloud/go-sdk/services/dns v0.0.2 + github.com/yandex-cloud/go-sdk/v2 v2.0.6 + golang.org/x/crypto v0.39.0 + golang.org/x/net v0.41.0 golang.org/x/oauth2 v0.28.0 - golang.org/x/text v0.23.0 + golang.org/x/text v0.27.0 golang.org/x/time v0.11.0 google.golang.org/api v0.227.0 gopkg.in/ns1/ns1-go.v2 v2.13.0 @@ -147,15 +148,14 @@ require ( github.com/go-playground/validator/v10 v10.16.0 // indirect github.com/go-resty/resty/v2 v2.16.5 // indirect github.com/gofrs/flock v0.12.1 // indirect - github.com/golang-jwt/jwt/v4 v4.5.1 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect - github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -199,22 +199,21 @@ require ( go.mongodb.org/mongo-driver v1.13.1 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect - go.opentelemetry.io/otel v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.34.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.3.0 // indirect + go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect - golang.org/x/mod v0.23.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/tools v0.30.0 // indirect - google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect - google.golang.org/grpc v1.71.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect + golang.org/x/mod v0.25.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/tools v0.34.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/grpc v1.73.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e62ae05c0..9ce1c0f55 100644 --- a/go.sum +++ b/go.sum @@ -364,8 +364,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= -github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= @@ -476,7 +476,6 @@ github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBt github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -491,11 +490,9 @@ github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjh github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= @@ -927,10 +924,12 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yandex-cloud/go-genproto v0.0.0-20250319153614-fb9d3e5eb01a h1:YO8gGyAV4N5SR3NzloZ1128IahSpXWr78oU7aEe7f04= -github.com/yandex-cloud/go-genproto v0.0.0-20250319153614-fb9d3e5eb01a/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= -github.com/yandex-cloud/go-sdk v0.0.0-20250320143332-9cbcfc5de4ae h1:x+uGuST05LVlgCxF5TsP8kQCCTW7uIeAQJ1dKtSmWqE= -github.com/yandex-cloud/go-sdk v0.0.0-20250320143332-9cbcfc5de4ae/go.mod h1:V71iJlJnS/NtNNdg/B7SwccBS19aXxwY3fv/wut9D74= +github.com/yandex-cloud/go-genproto v0.14.0 h1:yDqD260mICkjodXyAaDhESfrLr6gIGwwRc9MYE0jvW0= +github.com/yandex-cloud/go-genproto v0.14.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-sdk/services/dns v0.0.2 h1:NgnRRqTT/vnbwsiaZjtRBZaDDa6lIP3dFyCHCgclgYg= +github.com/yandex-cloud/go-sdk/services/dns v0.0.2/go.mod h1:IfuJXXIz7bkQIVD1HS6UinfffzpXexJUPMvffVUsPOU= +github.com/yandex-cloud/go-sdk/v2 v2.0.6 h1:RGG+HxLZPnX9FghJzVIxqcWyFgq0Dxk7NrVZAhEMmAY= +github.com/yandex-cloud/go-sdk/v2 v2.0.6/go.mod h1:fhu879XqRmN5hJ1HcYDDIdNdugApKLq6FoEuK8vc4r4= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= @@ -960,32 +959,36 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.5 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/ratelimit v0.3.0 h1:IdZd9wqvFXnvLvSEBo0KPcGfkoBGNkpTHlrE3Rcjkjw= go.uber.org/ratelimit v0.3.0/go.mod h1:So5LG7CV1zWpY1sHe+DXTJqQvOx+FFPFaAs2SnoyBaI= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -1015,8 +1018,8 @@ golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1057,8 +1060,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= -golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1113,8 +1116,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1137,8 +1140,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1219,8 +1222,8 @@ golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -1231,8 +1234,8 @@ golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1249,8 +1252,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1314,8 +1317,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= -golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1380,12 +1383,10 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU= -google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1403,8 +1404,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1419,8 +1420,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/providers/dns/yandexcloud/yandexcloud.go b/providers/dns/yandexcloud/yandexcloud.go index ca44ab82b..346b6d952 100644 --- a/providers/dns/yandexcloud/yandexcloud.go +++ b/providers/dns/yandexcloud/yandexcloud.go @@ -14,9 +14,12 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - ycdns "github.com/yandex-cloud/go-genproto/yandex/cloud/dns/v1" - ycsdk "github.com/yandex-cloud/go-sdk" - "github.com/yandex-cloud/go-sdk/iamkey" + ycdnsproto "github.com/yandex-cloud/go-genproto/yandex/cloud/dns/v1" + ycdns "github.com/yandex-cloud/go-sdk/services/dns/v1" + ycsdk "github.com/yandex-cloud/go-sdk/v2" + "github.com/yandex-cloud/go-sdk/v2/credentials" + "github.com/yandex-cloud/go-sdk/v2/pkg/iamkey" + "github.com/yandex-cloud/go-sdk/v2/pkg/options" ) // Environment variables names. @@ -54,7 +57,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - client *ycsdk.SDK + client ycdns.DnsZoneClient config *Config } @@ -91,13 +94,13 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("yandexcloud: iam token is malformed: %w", err) } - client, err := ycsdk.Build(context.Background(), ycsdk.Config{Credentials: creds}) + sdk, err := ycsdk.Build(context.Background(), options.WithCredentials(creds)) if err != nil { return nil, errors.New("yandexcloud: unable to build yandex cloud sdk") } return &DNSProvider{ - client: client, + client: ycdns.NewDnsZoneClient(sdk), config: config, }, nil } @@ -191,12 +194,12 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { } // getZones retrieves available zones from yandex cloud. -func (d *DNSProvider) getZones(ctx context.Context) ([]*ycdns.DnsZone, error) { - list := &ycdns.ListDnsZonesRequest{ +func (d *DNSProvider) getZones(ctx context.Context) ([]*ycdnsproto.DnsZone, error) { + list := &ycdnsproto.ListDnsZonesRequest{ FolderId: d.config.FolderID, } - response, err := d.client.DNS().DnsZone().List(ctx, list) + response, err := d.client.List(ctx, list) if err != nil { return nil, errors.New("unable to fetch dns zones") } @@ -205,27 +208,27 @@ func (d *DNSProvider) getZones(ctx context.Context) ([]*ycdns.DnsZone, error) { } func (d *DNSProvider) upsertRecordSetData(ctx context.Context, zoneID, name, value string) error { - get := &ycdns.GetDnsZoneRecordSetRequest{ + get := &ycdnsproto.GetDnsZoneRecordSetRequest{ DnsZoneId: zoneID, Name: name, Type: "TXT", } - exist, err := d.client.DNS().DnsZone().GetRecordSet(ctx, get) + exist, err := d.client.GetRecordSet(ctx, get) if err != nil { if !strings.Contains(err.Error(), "RecordSet not found") { return err } } - record := &ycdns.RecordSet{ + record := &ycdnsproto.RecordSet{ Name: name, Type: "TXT", Ttl: int64(d.config.TTL), Data: []string{}, } - var deletions []*ycdns.RecordSet + var deletions []*ycdnsproto.RecordSet if exist != nil { record.SetData(append(record.GetData(), exist.GetData()...)) deletions = append(deletions, exist) @@ -237,25 +240,25 @@ func (d *DNSProvider) upsertRecordSetData(ctx context.Context, zoneID, name, val return nil } - update := &ycdns.UpdateRecordSetsRequest{ + update := &ycdnsproto.UpdateRecordSetsRequest{ DnsZoneId: zoneID, Deletions: deletions, - Additions: []*ycdns.RecordSet{record}, + Additions: []*ycdnsproto.RecordSet{record}, } - _, err = d.client.DNS().DnsZone().UpdateRecordSets(ctx, update) + _, err = d.client.UpdateRecordSets(ctx, update) return err } func (d *DNSProvider) removeRecordSetData(ctx context.Context, zoneID, name, value string) error { - get := &ycdns.GetDnsZoneRecordSetRequest{ + get := &ycdnsproto.GetDnsZoneRecordSetRequest{ DnsZoneId: zoneID, Name: name, Type: "TXT", } - previousRecord, err := d.client.DNS().DnsZone().GetRecordSet(ctx, get) + previousRecord, err := d.client.GetRecordSet(ctx, get) if err != nil { if strings.Contains(err.Error(), "RecordSet not found") { // RecordSet is not present, nothing to do @@ -265,11 +268,11 @@ func (d *DNSProvider) removeRecordSetData(ctx context.Context, zoneID, name, val return err } - var additions []*ycdns.RecordSet + var additions []*ycdnsproto.RecordSet if len(previousRecord.GetData()) > 1 { // RecordSet is not empty we should update it - record := &ycdns.RecordSet{ + record := &ycdnsproto.RecordSet{ Name: name, Type: "TXT", Ttl: int64(d.config.TTL), @@ -285,19 +288,19 @@ func (d *DNSProvider) removeRecordSetData(ctx context.Context, zoneID, name, val additions = append(additions, record) } - update := &ycdns.UpdateRecordSetsRequest{ + update := &ycdnsproto.UpdateRecordSetsRequest{ DnsZoneId: zoneID, - Deletions: []*ycdns.RecordSet{previousRecord}, + Deletions: []*ycdnsproto.RecordSet{previousRecord}, Additions: additions, } - _, err = d.client.DNS().DnsZone().UpdateRecordSets(ctx, update) + _, err = d.client.UpdateRecordSets(ctx, update) return err } // decodeCredentials converts base64 encoded json of iam token to struct. -func decodeCredentials(accountB64 string) (ycsdk.Credentials, error) { +func decodeCredentials(accountB64 string) (credentials.Credentials, error) { account, err := base64.StdEncoding.DecodeString(accountB64) if err != nil { return nil, err @@ -309,10 +312,10 @@ func decodeCredentials(accountB64 string) (ycsdk.Credentials, error) { return nil, err } - return ycsdk.ServiceAccountKey(key) + return credentials.ServiceAccountKey(key) } -func appendRecordSetData(record *ycdns.RecordSet, value string) bool { +func appendRecordSetData(record *ycdnsproto.RecordSet, value string) bool { if slices.Contains(record.GetData(), value) { return false } From 0eac4b3dda66935f8a538ffca08d596a7b55a848 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 15 Jul 2025 21:09:01 +0200 Subject: [PATCH 128/298] tests: improve function naming (#2586) --- platform/tester/servermock/handler_file.go | 7 +++++ .../tester/servermock/link_request_body.go | 12 +++++++- .../servermock/link_request_body_json.go | 28 ++++++++++--------- .../dns/acmedns/internal/http_storage_test.go | 4 +-- providers/dns/allinkl/internal/client_test.go | 6 ++-- .../dns/arvancloud/internal/client_test.go | 2 +- providers/dns/autodns/internal/client_test.go | 4 +-- .../dns/checkdomain/internal/client_test.go | 4 +-- providers/dns/civo/civo_test.go | 18 ++++-------- providers/dns/civo/internal/client_test.go | 2 +- .../dns/clouddns/internal/client_test.go | 14 +++++----- .../dns/clouddns/internal/identity_test.go | 2 +- providers/dns/cloudflare/cloudflare_test.go | 16 ++++------- .../dns/cloudflare/internal/client_test.go | 2 +- .../dns/dnsmadeeasy/internal/client_test.go | 2 +- .../dns/domeneshop/internal/client_test.go | 2 +- providers/dns/dynu/internal/client_test.go | 2 +- providers/dns/gandi/internal/client_test.go | 14 +++++----- providers/dns/godaddy/internal/client_test.go | 4 +-- providers/dns/hetzner/internal/client_test.go | 2 +- .../dns/infomaniak/internal/client_test.go | 2 +- .../dns/internal/hostingde/client_test.go | 4 +-- .../dns/internal/selectel/client_test.go | 2 +- providers/dns/namecheap/namecheap_test.go | 9 +++--- providers/dns/netcup/internal/client_test.go | 2 +- providers/dns/netcup/internal/session_test.go | 4 +-- providers/dns/njalla/internal/client_test.go | 6 ++-- providers/dns/otc/otc_test.go | 19 +++++-------- providers/dns/pdns/internal/client_test.go | 6 ++-- providers/dns/safedns/internal/client_test.go | 2 +- providers/dns/versio/internal/client_test.go | 2 +- 31 files changed, 103 insertions(+), 102 deletions(-) diff --git a/platform/tester/servermock/handler_file.go b/platform/tester/servermock/handler_file.go index d826c648a..c5a9b33e1 100644 --- a/platform/tester/servermock/handler_file.go +++ b/platform/tester/servermock/handler_file.go @@ -15,6 +15,7 @@ type ResponseFromFileHandler struct { filename string } +// ResponseFromFile creates a [ResponseFromFileHandler] using a filename. func ResponseFromFile(filename string) *ResponseFromFileHandler { return &ResponseFromFileHandler{ statusCode: http.StatusOK, @@ -23,10 +24,16 @@ func ResponseFromFile(filename string) *ResponseFromFileHandler { } } +// ResponseFromFixture creates a [ResponseFromFileHandler] using a filename from the `fixtures` directory. func ResponseFromFixture(filename string) *ResponseFromFileHandler { return ResponseFromFile(filepath.Join("fixtures", filename)) } +// ResponseFromInternal creates a [ResponseFromFileHandler] using a filename from the `internal/fixtures` directory. +func ResponseFromInternal(filename string) *ResponseFromFileHandler { + return ResponseFromFile(filepath.Join("internal", "fixtures", filename)) +} + func (h *ResponseFromFileHandler) ServeHTTP(rw http.ResponseWriter, _ *http.Request) { for k, values := range h.headers { for _, v := range values { diff --git a/platform/tester/servermock/link_request_body.go b/platform/tester/servermock/link_request_body.go index 67ab4ae3f..b58c3cc79 100644 --- a/platform/tester/servermock/link_request_body.go +++ b/platform/tester/servermock/link_request_body.go @@ -27,6 +27,16 @@ func CheckRequestBodyFromFile(filename string) *RequestBodyLink { return &RequestBodyLink{filename: filename} } +// CheckRequestBodyFromFixture creates a [RequestBodyLink] initialized with the provided request body file from the `fixtures` directory. +func CheckRequestBodyFromFixture(filename string) *RequestBodyLink { + return CheckRequestBodyFromFile(filepath.Join("fixtures", filename)) +} + +// CheckRequestBodyFromInternal creates a [RequestBodyLink] initialized with the provided request body file from the `internal/fixtures directory. +func CheckRequestBodyFromInternal(filename string) *RequestBodyLink { + return CheckRequestBodyFromFile(filepath.Join("internal", "fixtures", filename)) +} + func (l *RequestBodyLink) Bind(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { if req.ContentLength == 0 { @@ -45,7 +55,7 @@ func (l *RequestBodyLink) Bind(next http.Handler) http.Handler { expectedRaw := slices.Clone(l.body) if l.filename != "" { - expectedRaw, err = os.ReadFile(filepath.Join("fixtures", l.filename)) + expectedRaw, err = os.ReadFile(l.filename) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) return diff --git a/platform/tester/servermock/link_request_body_json.go b/platform/tester/servermock/link_request_body_json.go index 2e9985c3d..3dc2f0cfa 100644 --- a/platform/tester/servermock/link_request_body_json.go +++ b/platform/tester/servermock/link_request_body_json.go @@ -14,10 +14,9 @@ import ( // RequestBodyJSONLink validates JSON request bodies. type RequestBodyJSONLink struct { - body []byte - filename string - directory string - data any + body []byte + filename string + data any } // CheckRequestJSONBody creates a [RequestBodyJSONLink] initialized with a string. @@ -33,11 +32,20 @@ func CheckRequestJSONBodyFromStruct(data any) *RequestBodyJSONLink { // CheckRequestJSONBodyFromFile creates a [RequestBodyJSONLink] initialized with the provided request body file. func CheckRequestJSONBodyFromFile(filename string) *RequestBodyJSONLink { return &RequestBodyJSONLink{ - filename: filename, - directory: "fixtures", + filename: filename, } } +// CheckRequestJSONBodyFromFixture creates a [RequestBodyJSONLink] initialized with the provided request body file from the `fixtures` directory. +func CheckRequestJSONBodyFromFixture(filename string) *RequestBodyJSONLink { + return CheckRequestJSONBodyFromFile(filepath.Join("fixtures", filename)) +} + +// CheckRequestJSONBodyFromInternal creates a [RequestBodyJSONLink] initialized with the provided request body file from the `internal/fixtures` directory. +func CheckRequestJSONBodyFromInternal(filename string) *RequestBodyJSONLink { + return CheckRequestJSONBodyFromFile(filepath.Join("internal", "fixtures", filename)) +} + func (l *RequestBodyJSONLink) Bind(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { if req.ContentLength == 0 { @@ -59,7 +67,7 @@ func (l *RequestBodyJSONLink) Bind(next http.Handler) http.Handler { switch { case l.filename != "": - expectedRaw, err = os.ReadFile(filepath.Join(l.directory, l.filename)) + expectedRaw, err = os.ReadFile(l.filename) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) return @@ -101,9 +109,3 @@ func (l *RequestBodyJSONLink) Bind(next http.Handler) http.Handler { next.ServeHTTP(rw, req) }) } - -func (l *RequestBodyJSONLink) WithDirectory(directory string) *RequestBodyJSONLink { - l.directory = directory - - return l -} diff --git a/providers/dns/acmedns/internal/http_storage_test.go b/providers/dns/acmedns/internal/http_storage_test.go index 0be6dd949..abc3c0cde 100644 --- a/providers/dns/acmedns/internal/http_storage_test.go +++ b/providers/dns/acmedns/internal/http_storage_test.go @@ -98,7 +98,7 @@ func TestHTTPStorage_FetchAll_error(t *testing.T) { func TestHTTPStorage_Put(t *testing.T) { storage := mockBuilder(). Route("POST /example.com", nil, - servermock.CheckRequestJSONBodyFromFile("request-body.json")). + servermock.CheckRequestJSONBodyFromFixture("request-body.json")). Build(t) account := goacmedns.Account{ @@ -137,7 +137,7 @@ func TestHTTPStorage_Put_CNAME_created(t *testing.T) { Route("POST /example.com", servermock.Noop(). WithStatusCode(http.StatusCreated), - servermock.CheckRequestJSONBodyFromFile("request-body.json")). + servermock.CheckRequestJSONBodyFromFixture("request-body.json")). Build(t) account := goacmedns.Account{ diff --git a/providers/dns/allinkl/internal/client_test.go b/providers/dns/allinkl/internal/client_test.go index 5954e2463..4b111e31c 100644 --- a/providers/dns/allinkl/internal/client_test.go +++ b/providers/dns/allinkl/internal/client_test.go @@ -20,7 +20,7 @@ func setupClient(server *httptest.Server) (*Client, error) { func TestClient_GetDNSSettings(t *testing.T) { client := servermock.NewBuilder[*Client](setupClient). Route("POST /", servermock.ResponseFromFixture("get_dns_settings.xml"), - servermock.CheckRequestBodyFromFile("get_dns_settings-request.xml"). + servermock.CheckRequestBodyFromFixture("get_dns_settings-request.xml"). IgnoreWhitespace()). Build(t) @@ -99,7 +99,7 @@ func TestClient_GetDNSSettings(t *testing.T) { func TestClient_AddDNSSettings(t *testing.T) { client := servermock.NewBuilder[*Client](setupClient). Route("POST /", servermock.ResponseFromFixture("add_dns_settings.xml"), - servermock.CheckRequestBodyFromFile("add_dns_settings-request.xml"). + servermock.CheckRequestBodyFromFixture("add_dns_settings-request.xml"). IgnoreWhitespace()). Build(t) @@ -119,7 +119,7 @@ func TestClient_AddDNSSettings(t *testing.T) { func TestClient_DeleteDNSSettings(t *testing.T) { client := servermock.NewBuilder[*Client](setupClient). Route("POST /", servermock.ResponseFromFixture("delete_dns_settings.xml"), - servermock.CheckRequestBodyFromFile("delete_dns_settings-request.xml"). + servermock.CheckRequestBodyFromFixture("delete_dns_settings-request.xml"). IgnoreWhitespace()). Build(t) diff --git a/providers/dns/arvancloud/internal/client_test.go b/providers/dns/arvancloud/internal/client_test.go index 38cb740c1..d13bc6f34 100644 --- a/providers/dns/arvancloud/internal/client_test.go +++ b/providers/dns/arvancloud/internal/client_test.go @@ -48,7 +48,7 @@ func TestClient_CreateRecord(t *testing.T) { Route("POST /cdn/4.0/domains/"+domain+"/dns-records", servermock.ResponseFromFixture("create_txt_record.json"). WithStatusCode(http.StatusCreated), - servermock.CheckRequestJSONBodyFromFile("create_record-request.json")). + servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). Build(t) record := DNSRecord{ diff --git a/providers/dns/autodns/internal/client_test.go b/providers/dns/autodns/internal/client_test.go index 5eb6486ea..6fc31ca34 100644 --- a/providers/dns/autodns/internal/client_test.go +++ b/providers/dns/autodns/internal/client_test.go @@ -28,7 +28,7 @@ func TestClient_AddTxtRecords(t *testing.T) { client := mockBuilder(). Route("POST /zone/example.com/_stream", servermock.ResponseFromFixture("add_record.json"), - servermock.CheckRequestJSONBodyFromFile("add_record-request.json"), + servermock.CheckRequestJSONBodyFromFixture("add_record-request.json"), servermock.CheckHeader(). With("X-Domainrobot-Context", "123")). Build(t) @@ -58,7 +58,7 @@ func TestClient_RemoveTXTRecords(t *testing.T) { client := mockBuilder(). Route("POST /zone/example.com/_stream", servermock.ResponseFromFixture("remove_record.json"), - servermock.CheckRequestJSONBodyFromFile("remove_record-request.json"), + servermock.CheckRequestJSONBodyFromFixture("remove_record-request.json"), servermock.CheckHeader(). With("X-Domainrobot-Context", "123")). Build(t) diff --git a/providers/dns/checkdomain/internal/client_test.go b/providers/dns/checkdomain/internal/client_test.go index 31d419a5f..68e4f1244 100644 --- a/providers/dns/checkdomain/internal/client_test.go +++ b/providers/dns/checkdomain/internal/client_test.go @@ -59,7 +59,7 @@ func TestClient_CheckNameservers(t *testing.T) { func TestClient_CreateRecord(t *testing.T) { client := mockBuilder(). Route("POST /v1/domains/1/nameservers/records", nil, - servermock.CheckRequestJSONBodyFromFile("create_record-request.json")). + servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). Build(t) record := &Record{ @@ -110,7 +110,7 @@ func TestClient_DeleteTXTRecord(t *testing.T) { }, })). Route("PUT /v1/domains/1/nameservers/records", nil, - servermock.CheckRequestJSONBodyFromFile("delete_txt_record-request.json")). + servermock.CheckRequestJSONBodyFromFixture("delete_txt_record-request.json")). Build(t) info := dns01.GetChallengeInfo(domainName, "abc") diff --git a/providers/dns/civo/civo_test.go b/providers/dns/civo/civo_test.go index 8d08a17cd..eb215fbcb 100644 --- a/providers/dns/civo/civo_test.go +++ b/providers/dns/civo/civo_test.go @@ -4,7 +4,6 @@ import ( "fmt" "net/http/httptest" "net/url" - "path/filepath" "testing" "time" @@ -155,14 +154,13 @@ func TestDNSProvider_Present(t *testing.T) { provider := mockBuilder(). // https://www.civo.com/api/dns#list-domain-names Route("GET /dns", - responseFromFixture("list_domain_names.json"), + servermock.ResponseFromInternal("list_domain_names.json"), servermock.CheckQueryParameter().Strict(). With("region", "LON1")). // https://www.civo.com/api/dns#create-a-new-dns-record Route("POST /dns/7088fcea-7658-43e6-97fa-273f901978fd/records", - responseFromFixture("create_dns_record.json"), - servermock.CheckRequestJSONBodyFromFile("create_dns_record-request.json"). - WithDirectory(filepath.Join("internal", "fixtures"))). + servermock.ResponseFromInternal("create_dns_record.json"), + servermock.CheckRequestJSONBodyFromInternal("create_dns_record-request.json")). Build(t) err := provider.Present("example.com", "abd", "123d==") @@ -173,17 +171,17 @@ func TestDNSProvider_CleanUp(t *testing.T) { provider := mockBuilder(). // https://www.civo.com/api/dns#list-domain-names Route("GET /dns", - responseFromFixture("list_domain_names.json"), + servermock.ResponseFromInternal("list_domain_names.json"), servermock.CheckQueryParameter(). With("region", "LON1")). // https://www.civo.com/api/dns#list-dns-records Route("GET /dns/7088fcea-7658-43e6-97fa-273f901978fd/records", - responseFromFixture("list_dns_records.json"), + servermock.ResponseFromInternal("list_dns_records.json"), servermock.CheckQueryParameter().Strict(). With("region", "LON1")). // https://www.civo.com/api/dns#deleting-a-dns-record Route("DELETE /dns/edc5dacf-a2ad-4757-41ee-c12f06259c70/records/76cc107f-fbef-4e2b-b97f-f5d34f4075d3", - responseFromFixture("delete_dns_record.json"), + servermock.ResponseFromInternal("delete_dns_record.json"), servermock.CheckQueryParameter().Strict(). With("region", "LON1")). Build(t) @@ -191,7 +189,3 @@ func TestDNSProvider_CleanUp(t *testing.T) { err := provider.CleanUp("example.com", "abd", "123d==") require.NoError(t, err) } - -func responseFromFixture(filename string) *servermock.ResponseFromFileHandler { - return servermock.ResponseFromFile(filepath.Join("internal", "fixtures", filename)) -} diff --git a/providers/dns/civo/internal/client_test.go b/providers/dns/civo/internal/client_test.go index 42b4ad718..5b47c185e 100644 --- a/providers/dns/civo/internal/client_test.go +++ b/providers/dns/civo/internal/client_test.go @@ -108,7 +108,7 @@ func TestClient_CreateDNSRecord(t *testing.T) { client := mockBuilder(). Route("POST /dns/7088fcea-7658-43e6-97fa-273f901978fd/records", servermock.ResponseFromFixture("create_dns_record.json"), - servermock.CheckRequestJSONBodyFromFile("create_dns_record-request.json")). + servermock.CheckRequestJSONBodyFromFixture("create_dns_record-request.json")). Build(t) record := Record{ diff --git a/providers/dns/clouddns/internal/client_test.go b/providers/dns/clouddns/internal/client_test.go index a8092933c..a5b780e42 100644 --- a/providers/dns/clouddns/internal/client_test.go +++ b/providers/dns/clouddns/internal/client_test.go @@ -27,14 +27,14 @@ func TestClient_AddRecord(t *testing.T) { client := mockBuilder(). Route("POST /api/domain/search", servermock.ResponseFromFixture("domain_search.json"), - servermock.CheckRequestJSONBodyFromFile("domain_search-request.json")). + servermock.CheckRequestJSONBodyFromFixture("domain_search-request.json")). Route("POST /api/record-txt", nil, - servermock.CheckRequestJSONBodyFromFile("record_txt-request.json")). + servermock.CheckRequestJSONBodyFromFixture("record_txt-request.json")). Route("PUT /api/domain/A/publish", nil, - servermock.CheckRequestJSONBodyFromFile("publish-request.json")). + servermock.CheckRequestJSONBodyFromFixture("publish-request.json")). Route("POST /login", servermock.ResponseFromFixture("login.json"), - servermock.CheckRequestJSONBodyFromFile("login-request.json")). + servermock.CheckRequestJSONBodyFromFixture("login-request.json")). Build(t) ctx, err := client.CreateAuthenticatedContext(t.Context()) @@ -48,15 +48,15 @@ func TestClient_DeleteRecord(t *testing.T) { client := mockBuilder(). Route("POST /api/domain/search", servermock.ResponseFromFixture("domain_search.json"), - servermock.CheckRequestJSONBodyFromFile("domain_search-request.json")). + servermock.CheckRequestJSONBodyFromFixture("domain_search-request.json")). Route("GET /api/domain/A", servermock.ResponseFromFixture("domain-request.json")). Route("DELETE /api/record/R01", nil). Route("PUT /api/domain/A/publish", nil, - servermock.CheckRequestJSONBodyFromFile("publish-request.json")). + servermock.CheckRequestJSONBodyFromFixture("publish-request.json")). Route("POST /login", servermock.ResponseFromFixture("login.json"), - servermock.CheckRequestJSONBodyFromFile("login-request.json")). + servermock.CheckRequestJSONBodyFromFixture("login-request.json")). Build(t) ctx, err := client.CreateAuthenticatedContext(t.Context()) diff --git a/providers/dns/clouddns/internal/identity_test.go b/providers/dns/clouddns/internal/identity_test.go index df5e20eb8..267f73335 100644 --- a/providers/dns/clouddns/internal/identity_test.go +++ b/providers/dns/clouddns/internal/identity_test.go @@ -12,7 +12,7 @@ func TestClient_CreateAuthenticatedContext(t *testing.T) { client := mockBuilder(). Route("POST /login", servermock.ResponseFromFixture("login.json"), - servermock.CheckRequestJSONBodyFromFile("login-request.json")). + servermock.CheckRequestJSONBodyFromFixture("login-request.json")). Route("DELETE /api/record/xxx", nil). Build(t) diff --git a/providers/dns/cloudflare/cloudflare_test.go b/providers/dns/cloudflare/cloudflare_test.go index b288931f1..10e96503a 100644 --- a/providers/dns/cloudflare/cloudflare_test.go +++ b/providers/dns/cloudflare/cloudflare_test.go @@ -2,7 +2,6 @@ package cloudflare import ( "net/http/httptest" - "path/filepath" "testing" "time" @@ -319,17 +318,16 @@ func TestDNSProvider_Present(t *testing.T) { provider := mockBuilder(). // https://developers.cloudflare.com/api/resources/zones/methods/list/ Route("GET /zones", - responseFromFixture("zones.json"), + servermock.ResponseFromInternal("zones.json"), servermock.CheckQueryParameter().Strict(). With("name", "example.com"). With("per_page", "50")). // https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/create/ Route("POST /zones/023e105f4ecef8ad9ca31a8372d0c353/dns_records", - responseFromFixture("create_record.json"), + servermock.ResponseFromInternal("create_record.json"), servermock.CheckHeader(). WithContentType("application/json"), - servermock.CheckRequestJSONBodyFromFile("create_record-request.json"). - WithDirectory(filepath.Join("internal", "fixtures"))). + servermock.CheckRequestJSONBodyFromInternal("create_record-request.json")). Build(t) err := provider.Present("example.com", "abc", "123d==") @@ -340,13 +338,13 @@ func TestDNSProvider_CleanUp(t *testing.T) { provider := mockBuilder(). // https://developers.cloudflare.com/api/resources/zones/methods/list/ Route("GET /zones", - responseFromFixture("zones.json"), + servermock.ResponseFromInternal("zones.json"), servermock.CheckQueryParameter().Strict(). With("name", "example.com"). With("per_page", "50")). // https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/delete/ Route("DELETE /zones/023e105f4ecef8ad9ca31a8372d0c353/dns_records/xxx", - responseFromFixture("delete_record.json")). + servermock.ResponseFromInternal("delete_record.json")). Build(t) token := "abc" @@ -358,7 +356,3 @@ func TestDNSProvider_CleanUp(t *testing.T) { err := provider.CleanUp("example.com", token, "123d==") require.NoError(t, err) } - -func responseFromFixture(filename string) *servermock.ResponseFromFileHandler { - return servermock.ResponseFromFile(filepath.Join("internal", "fixtures", filename)) -} diff --git a/providers/dns/cloudflare/internal/client_test.go b/providers/dns/cloudflare/internal/client_test.go index 69ca2007c..9d286016f 100644 --- a/providers/dns/cloudflare/internal/client_test.go +++ b/providers/dns/cloudflare/internal/client_test.go @@ -39,7 +39,7 @@ func TestClient_CreateDNSRecord(t *testing.T) { servermock.ResponseFromFixture("create_record.json"), servermock.CheckHeader(). WithContentType("application/json"), - servermock.CheckRequestJSONBodyFromFile("create_record-request.json")). + servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). Build(t) record := Record{ diff --git a/providers/dns/dnsmadeeasy/internal/client_test.go b/providers/dns/dnsmadeeasy/internal/client_test.go index f302c8d9b..cde212fc8 100644 --- a/providers/dns/dnsmadeeasy/internal/client_test.go +++ b/providers/dns/dnsmadeeasy/internal/client_test.go @@ -91,7 +91,7 @@ func TestClient_GetRecords(t *testing.T) { func TestClient_CreateRecord(t *testing.T) { client := mockBuilder(). Route("POST /dns/managed/1/records", nil, - servermock.CheckRequestJSONBodyFromFile("create_record-request.json")). + servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). Build(t) domain := &Domain{ID: 1, Name: "foo"} diff --git a/providers/dns/domeneshop/internal/client_test.go b/providers/dns/domeneshop/internal/client_test.go index beddc1cb2..2f5fb0d95 100644 --- a/providers/dns/domeneshop/internal/client_test.go +++ b/providers/dns/domeneshop/internal/client_test.go @@ -28,7 +28,7 @@ func TestClient_CreateTXTRecord(t *testing.T) { client := mockBuilder(). Route("POST /domains/1/dns", servermock.ResponseFromFixture("create_record.json"), - servermock.CheckRequestJSONBodyFromFile("create_record-request.json")). + servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). Build(t) err := client.CreateTXTRecord(t.Context(), &Domain{ID: 1}, "example.com", "txtTXTtxt") diff --git a/providers/dns/dynu/internal/client_test.go b/providers/dns/dynu/internal/client_test.go index 7dc94eca2..f70a8e377 100644 --- a/providers/dns/dynu/internal/client_test.go +++ b/providers/dns/dynu/internal/client_test.go @@ -215,7 +215,7 @@ func TestAddNewRecord(t *testing.T) { client := mockBuilder(). Route(test.pattern, servermock.ResponseFromFixture(test.file).WithStatusCode(test.status), - servermock.CheckRequestJSONBodyFromFile("add_new_record-request.json")). + servermock.CheckRequestJSONBodyFromFixture("add_new_record-request.json")). Build(t) record := DNSRecord{ diff --git a/providers/dns/gandi/internal/client_test.go b/providers/dns/gandi/internal/client_test.go index 573f812fa..a800767a2 100644 --- a/providers/dns/gandi/internal/client_test.go +++ b/providers/dns/gandi/internal/client_test.go @@ -25,7 +25,7 @@ func mockBuilder() *servermock.Builder[*Client] { func TestClient_GetZoneID(t *testing.T) { client := mockBuilder(). Route("POST /", servermock.ResponseFromFixture("get_zone_id.xml"), - servermock.CheckRequestBodyFromFile("get_zone_id-request.xml").IgnoreWhitespace()). + servermock.CheckRequestBodyFromFixture("get_zone_id-request.xml").IgnoreWhitespace()). Build(t) zoneID, err := client.GetZoneID(t.Context(), "example.com") @@ -37,7 +37,7 @@ func TestClient_GetZoneID(t *testing.T) { func TestClient_CloneZone(t *testing.T) { client := mockBuilder(). Route("POST /", servermock.ResponseFromFixture("clone_zone.xml"), - servermock.CheckRequestBodyFromFile("clone_zone-request.xml").IgnoreWhitespace()). + servermock.CheckRequestBodyFromFixture("clone_zone-request.xml").IgnoreWhitespace()). Build(t) zoneID, err := client.CloneZone(t.Context(), 6, "foo") @@ -49,7 +49,7 @@ func TestClient_CloneZone(t *testing.T) { func TestClient_NewZoneVersion(t *testing.T) { client := mockBuilder(). Route("POST /", servermock.ResponseFromFixture("new_zone_version.xml"), - servermock.CheckRequestBodyFromFile("new_zone_version-request.xml").IgnoreWhitespace()). + servermock.CheckRequestBodyFromFixture("new_zone_version-request.xml").IgnoreWhitespace()). Build(t) zoneID, err := client.NewZoneVersion(t.Context(), 6) @@ -61,7 +61,7 @@ func TestClient_NewZoneVersion(t *testing.T) { func TestClient_AddTXTRecord(t *testing.T) { client := mockBuilder(). Route("POST /", servermock.ResponseFromFixture("empty.xml"), - servermock.CheckRequestBodyFromFile("add_txt_record-request.xml").IgnoreWhitespace()). + servermock.CheckRequestBodyFromFixture("add_txt_record-request.xml").IgnoreWhitespace()). Build(t) err := client.AddTXTRecord(t.Context(), 1, 123, "foo", "content", 120) @@ -71,7 +71,7 @@ func TestClient_AddTXTRecord(t *testing.T) { func TestClient_SetZoneVersion(t *testing.T) { client := mockBuilder(). Route("POST /", servermock.ResponseFromFixture("set_zone_version.xml"), - servermock.CheckRequestBodyFromFile("set_zone_version-request.xml").IgnoreWhitespace()). + servermock.CheckRequestBodyFromFixture("set_zone_version-request.xml").IgnoreWhitespace()). Build(t) err := client.SetZoneVersion(t.Context(), 1, 123) @@ -81,7 +81,7 @@ func TestClient_SetZoneVersion(t *testing.T) { func TestClient_SetZone(t *testing.T) { client := mockBuilder(). Route("POST /", servermock.ResponseFromFixture("set_zone.xml"), - servermock.CheckRequestBodyFromFile("set_zone-request.xml").IgnoreWhitespace()). + servermock.CheckRequestBodyFromFixture("set_zone-request.xml").IgnoreWhitespace()). Build(t) err := client.SetZone(t.Context(), "example.com", 1) @@ -91,7 +91,7 @@ func TestClient_SetZone(t *testing.T) { func TestClient_DeleteZone(t *testing.T) { client := mockBuilder(). Route("POST /", servermock.ResponseFromFixture("delete_zone.xml"), - servermock.CheckRequestBodyFromFile("delete_zone-request.xml").IgnoreWhitespace()). + servermock.CheckRequestBodyFromFixture("delete_zone-request.xml").IgnoreWhitespace()). Build(t) err := client.DeleteZone(t.Context(), 1) diff --git a/providers/dns/godaddy/internal/client_test.go b/providers/dns/godaddy/internal/client_test.go index 741a55f3e..694a16565 100644 --- a/providers/dns/godaddy/internal/client_test.go +++ b/providers/dns/godaddy/internal/client_test.go @@ -58,7 +58,7 @@ func TestClient_GetRecords_errors(t *testing.T) { func TestClient_UpdateTxtRecords(t *testing.T) { client := mockBuilder(). Route("PUT /v1/domains/example.com/records/TXT/lego", nil, - servermock.CheckRequestJSONBodyFromFile("update_records-request.json")). + servermock.CheckRequestJSONBodyFromFixture("update_records-request.json")). Build(t) records := []DNSRecord{ @@ -78,7 +78,7 @@ func TestClient_UpdateTxtRecords_errors(t *testing.T) { client := mockBuilder(). Route("PUT /v1/domains/example.com/records/TXT/lego", servermock.ResponseFromFixture("errors.json").WithStatusCode(http.StatusUnprocessableEntity), - servermock.CheckRequestJSONBodyFromFile("update_records-request.json")). + servermock.CheckRequestJSONBodyFromFixture("update_records-request.json")). Build(t) records := []DNSRecord{ diff --git a/providers/dns/hetzner/internal/client_test.go b/providers/dns/hetzner/internal/client_test.go index d301493a9..ade312a90 100644 --- a/providers/dns/hetzner/internal/client_test.go +++ b/providers/dns/hetzner/internal/client_test.go @@ -53,7 +53,7 @@ func TestClient_CreateRecord(t *testing.T) { client := mockBuilder("myKeyB"). Route("POST /api/v1/records", servermock.ResponseFromFixture("create_txt_record.json"), - servermock.CheckRequestJSONBodyFromFile("create_txt_record-request.json")). + servermock.CheckRequestJSONBodyFromFixture("create_txt_record-request.json")). Build(t) record := DNSRecord{ diff --git a/providers/dns/infomaniak/internal/client_test.go b/providers/dns/infomaniak/internal/client_test.go index 566ed9c34..d846f06b4 100644 --- a/providers/dns/infomaniak/internal/client_test.go +++ b/providers/dns/infomaniak/internal/client_test.go @@ -27,7 +27,7 @@ func TestClient_CreateDNSRecord(t *testing.T) { client := mockBuilder(). Route("POST /1/domain/666/dns/record", servermock.RawStringResponse(`{"result":"success","data": "123"}`), - servermock.CheckRequestJSONBodyFromFile("create_dns_record-request.json")). + servermock.CheckRequestJSONBodyFromFixture("create_dns_record-request.json")). Build(t) domain := &DNSDomain{ diff --git a/providers/dns/internal/hostingde/client_test.go b/providers/dns/internal/hostingde/client_test.go index b735509c0..93e0c76e1 100644 --- a/providers/dns/internal/hostingde/client_test.go +++ b/providers/dns/internal/hostingde/client_test.go @@ -23,7 +23,7 @@ func TestClient_ListZoneConfigs(t *testing.T) { client := servermock.NewBuilder[*Client](setupClient). Route("POST /zoneConfigsFind", servermock.ResponseFromFixture("zoneConfigsFind.json"), - servermock.CheckRequestJSONBodyFromFile("zoneConfigsFind-request.json")). + servermock.CheckRequestJSONBodyFromFixture("zoneConfigsFind-request.json")). Build(t) zonesFind := ZoneConfigsFindRequest{ @@ -88,7 +88,7 @@ func TestClient_UpdateZone(t *testing.T) { client := servermock.NewBuilder[*Client](setupClient). Route("POST /zoneUpdate", servermock.ResponseFromFixture("zoneUpdate.json"), - servermock.CheckRequestJSONBodyFromFile("zoneUpdate-request.json")). + servermock.CheckRequestJSONBodyFromFixture("zoneUpdate-request.json")). Build(t) request := ZoneUpdateRequest{ diff --git a/providers/dns/internal/selectel/client_test.go b/providers/dns/internal/selectel/client_test.go index d67a4b3bf..292f70142 100644 --- a/providers/dns/internal/selectel/client_test.go +++ b/providers/dns/internal/selectel/client_test.go @@ -80,7 +80,7 @@ func TestClient_AddRecord(t *testing.T) { With(tokenHeader, "token")). Route("POST /123/records/", servermock.ResponseFromFixture("add_record.json"), - servermock.CheckRequestJSONBodyFromFile("add_record-request.json")). + servermock.CheckRequestJSONBodyFromFixture("add_record-request.json")). Build(t) record, err := client.AddRecord(t.Context(), 123, Record{ diff --git a/providers/dns/namecheap/namecheap_test.go b/providers/dns/namecheap/namecheap_test.go index fedbc3162..922e48ebb 100644 --- a/providers/dns/namecheap/namecheap_test.go +++ b/providers/dns/namecheap/namecheap_test.go @@ -3,7 +3,6 @@ package namecheap import ( "net/http" "net/http/httptest" - "path/filepath" "testing" "time" @@ -54,7 +53,7 @@ func TestDNSProvider_Present(t *testing.T) { provider := mockBuilder(). Route("GET /", - servermock.ResponseFromFile(filepath.Join("internal", "fixtures", test.getHostsResponse)), + servermock.ResponseFromInternal(test.getHostsResponse), servermock.CheckForm().Strict(). With("ClientIp", "10.0.0.1"). With("Command", "namecheap.domains.dns.getHosts"). @@ -65,7 +64,7 @@ func TestDNSProvider_Present(t *testing.T) { With("ApiUser", "foo"), ). Route("POST /", - servermock.ResponseFromFile(filepath.Join("internal", "fixtures", test.setHostsResponse)), + servermock.ResponseFromInternal(test.setHostsResponse), servermock.CheckForm(). With("ClientIp", "10.0.0.1"). With("Command", "namecheap.domains.dns.setHosts"). @@ -94,7 +93,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { provider := mockBuilder(). Route("GET /", - servermock.ResponseFromFile(filepath.Join("internal", "fixtures", test.getHostsResponse)), + servermock.ResponseFromInternal(test.getHostsResponse), servermock.CheckForm().Strict(). With("ClientIp", "10.0.0.1"). With("Command", "namecheap.domains.dns.getHosts"). @@ -105,7 +104,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { With("ApiUser", "foo"), ). Route("POST /", - servermock.ResponseFromFile(filepath.Join("internal", "fixtures", test.setHostsResponse)), + servermock.ResponseFromInternal(test.setHostsResponse), servermock.CheckForm(). With("ClientIp", "10.0.0.1"). With("Command", "namecheap.domains.dns.setHosts"). diff --git a/providers/dns/netcup/internal/client_test.go b/providers/dns/netcup/internal/client_test.go index a1c91aac4..83c59460e 100644 --- a/providers/dns/netcup/internal/client_test.go +++ b/providers/dns/netcup/internal/client_test.go @@ -131,7 +131,7 @@ func TestGetDNSRecordIdx(t *testing.T) { func TestClient_GetDNSRecords(t *testing.T) { client := mockBuilder(). Route("POST /", servermock.ResponseFromFixture("get_dns_records.json"), - servermock.CheckRequestJSONBodyFromFile("get_dns_records-request.json")). + servermock.CheckRequestJSONBodyFromFixture("get_dns_records-request.json")). Build(t) expected := []DNSRecord{{ diff --git a/providers/dns/netcup/internal/session_test.go b/providers/dns/netcup/internal/session_test.go index 27442b347..7704c2604 100644 --- a/providers/dns/netcup/internal/session_test.go +++ b/providers/dns/netcup/internal/session_test.go @@ -19,7 +19,7 @@ func mockContext(t *testing.T) context.Context { func TestClient_Login(t *testing.T) { client := mockBuilder(). Route("POST /", servermock.ResponseFromFixture("login.json"), - servermock.CheckRequestJSONBodyFromFile("login-request.json")). + servermock.CheckRequestJSONBodyFromFixture("login-request.json")). Build(t) sessionID, err := client.login(t.Context()) @@ -69,7 +69,7 @@ func TestClient_Login_errors(t *testing.T) { func TestClient_Logout(t *testing.T) { client := mockBuilder(). Route("POST /", servermock.ResponseFromFixture("logout.json"), - servermock.CheckRequestJSONBodyFromFile("logout-request.json")). + servermock.CheckRequestJSONBodyFromFixture("logout-request.json")). Build(t) err := client.Logout(mockContext(t)) diff --git a/providers/dns/njalla/internal/client_test.go b/providers/dns/njalla/internal/client_test.go index ec9309078..a7e60aefd 100644 --- a/providers/dns/njalla/internal/client_test.go +++ b/providers/dns/njalla/internal/client_test.go @@ -24,7 +24,7 @@ func TestClient_AddRecord(t *testing.T) { ). Route("POST /", servermock.ResponseFromFixture("add_record.json"), - servermock.CheckRequestJSONBodyFromFile("add_record-request.json")). + servermock.CheckRequestJSONBodyFromFixture("add_record-request.json")). Build(t) record := Record{ @@ -80,7 +80,7 @@ func TestClient_ListRecords(t *testing.T) { ). Route("POST /", servermock.ResponseFromFixture("list_records.json"), - servermock.CheckRequestJSONBodyFromFile("list_records-request.json")). + servermock.CheckRequestJSONBodyFromFixture("list_records-request.json")). Build(t) records, err := client.ListRecords(t.Context(), "example.com") @@ -131,7 +131,7 @@ func TestClient_RemoveRecord(t *testing.T) { ). Route("POST /", servermock.RawStringResponse(`{"jsonrpc":"2.0"}`), - servermock.CheckRequestJSONBodyFromFile("remove_record-request.json")). + servermock.CheckRequestJSONBodyFromFixture("remove_record-request.json")). Build(t) err := client.RemoveRecord(t.Context(), "123", "example.com") diff --git a/providers/dns/otc/otc_test.go b/providers/dns/otc/otc_test.go index 1e53f31cc..c8b32bc78 100644 --- a/providers/dns/otc/otc_test.go +++ b/providers/dns/otc/otc_test.go @@ -3,7 +3,6 @@ package otc import ( "fmt" "net/http/httptest" - "path" "testing" "time" @@ -216,7 +215,7 @@ func TestLiveCleanUp(t *testing.T) { func TestDNSProvider_Present(t *testing.T) { provider := mockBuilder(). Route("GET /v2/zones", - responseFromFixture("zones_GET.json"), + servermock.ResponseFromInternal("zones_GET.json"), servermock.CheckQueryParameter().Strict(). With("name", "example.com.")). Route("/", servermock.DumpRequest()). @@ -229,7 +228,7 @@ func TestDNSProvider_Present(t *testing.T) { func TestDNSProvider_Present_emptyZone(t *testing.T) { provider := mockBuilder(). Route("GET /v2/zones", - responseFromFixture("zones_GET_empty.json"), + servermock.ResponseFromInternal("zones_GET_empty.json"), servermock.CheckQueryParameter().Strict(). With("name", "example.com.")). Route("/", servermock.DumpRequest()). @@ -242,16 +241,16 @@ func TestDNSProvider_Present_emptyZone(t *testing.T) { func TestDNSProvider_Cleanup(t *testing.T) { provider := mockBuilder(). Route("GET /v2/zones", - responseFromFixture("zones_GET.json"), + servermock.ResponseFromInternal("zones_GET.json"), servermock.CheckQueryParameter().Strict(). With("name", "example.com.")). Route("GET /v2/zones/123123/recordsets", - responseFromFixture("zones-recordsets_GET.json"), + servermock.ResponseFromInternal("zones-recordsets_GET.json"), servermock.CheckQueryParameter().Strict(). With("name", "_acme-challenge.example.com."). With("type", "TXT")). Route("DELETE /v2/zones/123123/recordsets/321321", - responseFromFixture("zones-recordsets_DELETE.json")). + servermock.ResponseFromInternal("zones-recordsets_DELETE.json")). Build(t) err := provider.CleanUp("example.com", "", "123d==") @@ -261,11 +260,11 @@ func TestDNSProvider_Cleanup(t *testing.T) { func TestDNSProvider_Cleanup_emptyRecordset(t *testing.T) { provider := mockBuilder(). Route("GET /v2/zones", - responseFromFixture("zones_GET.json"), + servermock.ResponseFromInternal("zones_GET.json"), servermock.CheckQueryParameter().Strict(). With("name", "example.com.")). Route("GET /v2/zones/123123/recordsets", - responseFromFixture("zones-recordsets_GET_empty.json"), + servermock.ResponseFromInternal("zones-recordsets_GET_empty.json"), servermock.CheckQueryParameter().Strict(). With("name", "_acme-challenge.example.com."). With("type", "TXT")). @@ -291,7 +290,3 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { ). Route("POST /v3/auth/token", internal.IdentityHandlerMock()) } - -func responseFromFixture(filename string) *servermock.ResponseFromFileHandler { - return servermock.ResponseFromFile(path.Join("internal", "fixtures", filename)) -} diff --git a/providers/dns/pdns/internal/client_test.go b/providers/dns/pdns/internal/client_test.go index 6d1c48852..17f05095f 100644 --- a/providers/dns/pdns/internal/client_test.go +++ b/providers/dns/pdns/internal/client_test.go @@ -231,7 +231,7 @@ func TestClient_UpdateRecords(t *testing.T) { client := mockBuilder(). Route("PATCH /api/v1/servers/localhost/zones/example.org.", servermock.ResponseFromFixture("zone.json"), - servermock.CheckRequestJSONBodyFromFile("zone-request.json")). + servermock.CheckRequestJSONBodyFromFixture("zone-request.json")). Build(t) client.apiVersion = 1 @@ -266,7 +266,7 @@ func TestClient_UpdateRecords_NonRootApi(t *testing.T) { client := mockBuilder(). Route("PATCH /some/path/api/v1/servers/localhost/zones/example.org.", servermock.ResponseFromFixture("zone.json"), - servermock.CheckRequestJSONBodyFromFile("zone-request.json")). + servermock.CheckRequestJSONBodyFromFixture("zone-request.json")). Build(t) client.Host = client.Host.JoinPath("some", "path") @@ -302,7 +302,7 @@ func TestClient_UpdateRecords_v0(t *testing.T) { client := mockBuilder(). Route("PATCH /servers/localhost/zones/example.org.", servermock.ResponseFromFixture("zone.json"), - servermock.CheckRequestJSONBodyFromFile("zone-request.json")). + servermock.CheckRequestJSONBodyFromFixture("zone-request.json")). Build(t) client.apiVersion = 0 diff --git a/providers/dns/safedns/internal/client_test.go b/providers/dns/safedns/internal/client_test.go index 117a85a9f..f984d2d8f 100644 --- a/providers/dns/safedns/internal/client_test.go +++ b/providers/dns/safedns/internal/client_test.go @@ -29,7 +29,7 @@ func TestClient_AddRecord(t *testing.T) { Route("POST /zones/example.com/records", servermock.ResponseFromFixture("add_record.json"). WithStatusCode(http.StatusCreated), - servermock.CheckRequestJSONBodyFromFile("add_record-request.json")). + servermock.CheckRequestJSONBodyFromFixture("add_record-request.json")). Build(t) record := Record{ diff --git a/providers/dns/versio/internal/client_test.go b/providers/dns/versio/internal/client_test.go index f3bf68c6d..8dfcb4ff8 100644 --- a/providers/dns/versio/internal/client_test.go +++ b/providers/dns/versio/internal/client_test.go @@ -68,7 +68,7 @@ func TestClient_UpdateDomain(t *testing.T) { client := mockBuilder(). Route("POST /domains/example.com/update", servermock.ResponseFromFixture("update-domain.json"), - servermock.CheckRequestJSONBodyFromFile("update-domain-request.json")). + servermock.CheckRequestJSONBodyFromFixture("update-domain-request.json")). Build(t) msg := &DomainInfo{DNSRecords: []Record{ From 7d82b83bfd0734c9309b1c80fab1ef4786c8103c Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 17 Jul 2025 17:38:48 +0200 Subject: [PATCH 129/298] chore: use custom API client constructor for sakuracloud (#2587) --- providers/dns/sakuracloud/sakuracloud.go | 38 +++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/providers/dns/sakuracloud/sakuracloud.go b/providers/dns/sakuracloud/sakuracloud.go index 498f76c42..f12248d42 100644 --- a/providers/dns/sakuracloud/sakuracloud.go +++ b/providers/dns/sakuracloud/sakuracloud.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "strings" "time" "github.com/go-acme/lego/v4/challenge" @@ -13,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internal/useragent" client "github.com/sacloud/api-client-go" "github.com/sacloud/iaas-api-go" + "github.com/sacloud/iaas-api-go/defaults" "github.com/sacloud/iaas-api-go/helper/api" ) @@ -104,7 +106,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } return &DNSProvider{ - client: iaas.NewDNSOp(api.NewCallerWithOptions(api.MergeOptions(defaultOption, options))), + client: iaas.NewDNSOp(newCallerWithOptions(api.MergeOptions(defaultOption, options))), config: config, }, nil } @@ -138,3 +140,37 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } + +// Extracted from https://github.com/sacloud/iaas-api-go/blob/af06b3ccc2c38625d2dc684ad39590d0ae13eed3/helper/api/caller.go#L36-L81 +// Trace and fake are removed. +// Related to https://github.com/sacloud/iaas-api-go/issues/376. +func newCallerWithOptions(opts *api.CallerOptions) iaas.APICaller { + return newCaller(opts) +} + +func newCaller(opts *api.CallerOptions) iaas.APICaller { + if opts.UserAgent == "" { + opts.UserAgent = iaas.DefaultUserAgent + } + + caller := iaas.NewClientWithOptions(opts.Options) + + defaults.DefaultStatePollingTimeout = 72 * time.Hour + + if opts.DefaultZone != "" { + iaas.APIDefaultZone = opts.DefaultZone + } + + if len(opts.Zones) > 0 { + iaas.SakuraCloudZones = opts.Zones + } + + if opts.APIRootURL != "" { + if strings.HasSuffix(opts.APIRootURL, "/") { + opts.APIRootURL = strings.TrimRight(opts.APIRootURL, "/") + } + iaas.SakuraCloudAPIRoot = opts.APIRootURL + } + + return caller +} From d0008c42f5e05e4daf44da3feb11f50ca1b61403 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 17 Jul 2025 21:55:47 +0200 Subject: [PATCH 130/298] tencentcloud: replace tencentcloud-sdk-go with a fork (#2588) --- go.mod | 4 ++-- go.sum | 8 ++++---- providers/dns/tencentcloud/tencentcloud.go | 6 +++--- providers/dns/tencentcloud/wrapper.go | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 638a2a335..dd43972ce 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 github.com/dnsimple/dnsimple-go/v4 v4.0.0 github.com/exoscale/egoscale/v3 v3.1.13 + github.com/go-acme/tencentclouddnspod v1.0.1208 github.com/go-jose/go-jose/v4 v4.0.5 github.com/go-viper/mapstructure/v2 v2.2.1 github.com/google/go-cmp v0.7.0 @@ -75,8 +76,7 @@ require ( github.com/selectel/go-selvpcclient/v4 v4.1.0 github.com/softlayer/softlayer-go v1.1.7 github.com/stretchr/testify v1.10.0 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1128 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1208 github.com/transip/gotransip/v6 v6.26.0 github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec github.com/urfave/cli/v2 v2.27.6 diff --git a/go.sum b/go.sum index 9ce1c0f55..9d1aaa865 100644 --- a/go.sum +++ b/go.sum @@ -313,6 +313,8 @@ github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-acme/tencentclouddnspod v1.0.1208 h1:xAVy1lmg2KcKKeYmFSBQUttwc1o1S++9QTjAotGC+BM= +github.com/go-acme/tencentclouddnspod v1.0.1208/go.mod h1:yxG02mkbbVd7lTb97nOn7oj09djhm7hAwxNQw4B9dpQ= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -891,10 +893,8 @@ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1128 h1:NGnqDc8FQL0YdiCHgTO4Wkso6ToD8rE3JW9VOzoPBNA= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1128/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128 h1:mrJ5Fbkd7sZIJ5F6oRfh5zebPQaudPH9Y0+GUmFytYU= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128/go.mod h1:zbsYIBT+VTX4z4ocjTAdLBIWyNYj3z0BRqd0iPdnjsk= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1208 h1:hE2rM9GoqISu4lgQVbx9+bTlPOB000pdUfbT9lwrC50= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1208/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= diff --git a/providers/dns/tencentcloud/tencentcloud.go b/providers/dns/tencentcloud/tencentcloud.go index 21fb45095..00e41e93e 100644 --- a/providers/dns/tencentcloud/tencentcloud.go +++ b/providers/dns/tencentcloud/tencentcloud.go @@ -11,9 +11,9 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + dnspod "github.com/go-acme/tencentclouddnspod/v20210323" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" - dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323" ) // Environment variables names. @@ -139,7 +139,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { request.Value = common.StringPtr(info.Value) request.TTL = common.Uint64Ptr(uint64(d.config.TTL)) - _, err = d.client.CreateRecordWithContext(ctx, request) + _, err = dnspod.CreateRecordWithContext(ctx, d.client, request) if err != nil { return fmt.Errorf("dnspod: API call failed: %w", err) } @@ -169,7 +169,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { request.DomainId = zone.DomainId request.RecordId = record.RecordId - _, err := d.client.DeleteRecordWithContext(ctx, request) + _, err := dnspod.DeleteRecordWithContext(ctx, d.client, request) if err != nil { return fmt.Errorf("tencentcloud: delete record failed: %w", err) } diff --git a/providers/dns/tencentcloud/wrapper.go b/providers/dns/tencentcloud/wrapper.go index 5ce786515..6fdb7f899 100644 --- a/providers/dns/tencentcloud/wrapper.go +++ b/providers/dns/tencentcloud/wrapper.go @@ -6,9 +6,9 @@ import ( "fmt" "github.com/go-acme/lego/v4/challenge/dns01" + dnspod "github.com/go-acme/tencentclouddnspod/v20210323" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" errorsdk "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" - dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323" "golang.org/x/net/idna" ) @@ -18,7 +18,7 @@ func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*dnspod var domains []*dnspod.DomainListItem for { - response, err := d.client.DescribeDomainListWithContext(ctx, request) + response, err := dnspod.DescribeDomainListWithContext(ctx, d.client, request) if err != nil { return nil, fmt.Errorf("API call failed: %w", err) } @@ -65,7 +65,7 @@ func (d *DNSProvider) findTxtRecords(ctx context.Context, zone *dnspod.DomainLis request.RecordType = common.StringPtr("TXT") request.RecordLine = common.StringPtr("默认") - response, err := d.client.DescribeRecordListWithContext(ctx, request) + response, err := dnspod.DescribeRecordListWithContext(ctx, d.client, request) if err != nil { var sdkError *errorsdk.TencentCloudSDKError if errors.As(err, &sdkError) { From 79f496e11c50dce050fae065617d791f0012f5c9 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 17 Jul 2025 22:15:01 +0200 Subject: [PATCH 131/298] alidns: replace alidns-20150109 with a fork (#2589) --- go.mod | 2 +- go.sum | 4 ++-- providers/dns/alidns/alidns.go | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index dd43972ce..5d926b572 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,6 @@ require ( github.com/BurntSushi/toml v1.5.0 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 - github.com/alibabacloud-go/alidns-20150109/v4 v4.5.10 github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.11 github.com/aliyun/credentials-go v1.4.5 github.com/aws/aws-sdk-go-v2 v1.36.3 @@ -31,6 +30,7 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 github.com/dnsimple/dnsimple-go/v4 v4.0.0 github.com/exoscale/egoscale/v3 v3.1.13 + github.com/go-acme/alidns-20150109/v4 v4.5.10 github.com/go-acme/tencentclouddnspod v1.0.1208 github.com/go-jose/go-jose/v4 v4.0.5 github.com/go-viper/mapstructure/v2 v2.2.1 diff --git a/go.sum b/go.sum index 9d1aaa865..ef2a6f830 100644 --- a/go.sum +++ b/go.sum @@ -118,8 +118,6 @@ github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do2 github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g= -github.com/alibabacloud-go/alidns-20150109/v4 v4.5.10 h1:lEYfSDh8puQigWN2pyOxE1gaI6o2bxFhJSSeX+ZJSf4= -github.com/alibabacloud-go/alidns-20150109/v4 v4.5.10/go.mod h1:EdHRU3Y2j8OXc2ljp00A0zMLQ8sORHxI4yPnODNztRc= github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY= github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI= github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE= @@ -313,6 +311,8 @@ github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-acme/alidns-20150109/v4 v4.5.10 h1:epLD0VaHR5XUpiM6mjm4MzQFICrk+zpuqDz2aO1/R/k= +github.com/go-acme/alidns-20150109/v4 v4.5.10/go.mod h1:qGRq8kD0xVgn82qRSQmhHwh/oWxKRjF4Db5OI4ScV5g= github.com/go-acme/tencentclouddnspod v1.0.1208 h1:xAVy1lmg2KcKKeYmFSBQUttwc1o1S++9QTjAotGC+BM= github.com/go-acme/tencentclouddnspod v1.0.1208/go.mod h1:yxG02mkbbVd7lTb97nOn7oj09djhm7hAwxNQw4B9dpQ= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= diff --git a/providers/dns/alidns/alidns.go b/providers/dns/alidns/alidns.go index 7aa4028b8..660098d4a 100644 --- a/providers/dns/alidns/alidns.go +++ b/providers/dns/alidns/alidns.go @@ -6,9 +6,9 @@ import ( "fmt" "time" - alidns "github.com/alibabacloud-go/alidns-20150109/v4/client" openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" "github.com/aliyun/credentials-go/credentials" + alidns "github.com/go-acme/alidns-20150109/v4/client" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" @@ -162,7 +162,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return err } - _, err = d.client.AddDomainRecord(recordRequest) + _, err = alidns.AddDomainRecord(d.client, recordRequest) if err != nil { return fmt.Errorf("alicloud: API call failed: %w", err) } @@ -188,7 +188,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { RecordId: rec.RecordId, } - _, err = d.client.DeleteDomainRecord(request) + _, err = alidns.DeleteDomainRecord(d.client, request) if err != nil { return fmt.Errorf("alicloud: %w", err) } @@ -206,7 +206,7 @@ func (d *DNSProvider) getHostedZone(domain string) (string, error) { for { request.SetPageNumber(startPage) - response, err := d.client.DescribeDomains(request) + response, err := alidns.DescribeDomains(d.client, request) if err != nil { return "", fmt.Errorf("API call failed: %w", err) } @@ -265,7 +265,7 @@ func (d *DNSProvider) findTxtRecords(fqdn string) ([]*alidns.DescribeDomainRecor var records []*alidns.DescribeDomainRecordsResponseBodyDomainRecordsRecord - result, err := d.client.DescribeDomainRecords(request) + result, err := alidns.DescribeDomainRecords(d.client, request) if err != nil { return records, fmt.Errorf("API call has failed: %w", err) } From cb602702d26ca5ac0079c0f277550b7d4e85069b Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 18 Jul 2025 19:03:47 +0200 Subject: [PATCH 132/298] huaweicloud: lightweight client (#2591) Co-authored-by: Dominik Menke --- .golangci.yml | 3 + providers/dns/huaweicloud/huaweicloud.go | 5 +- providers/dns/huaweicloud/internal/client.go | 92 ++++++++++++++++++++ 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 providers/dns/huaweicloud/internal/client.go diff --git a/.golangci.yml b/.golangci.yml index 42f10adc7..66f3fd9d0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -170,6 +170,9 @@ linters: presets: - comments - std-error-handling + paths: + # Those elements are related to code borrowed from the official HuaweiCloud API client. + - providers/dns/huaweicloud/internal rules: - path: (.+)_test.go linters: diff --git a/providers/dns/huaweicloud/huaweicloud.go b/providers/dns/huaweicloud/huaweicloud.go index fbb594f58..32f4d3446 100644 --- a/providers/dns/huaweicloud/huaweicloud.go +++ b/providers/dns/huaweicloud/huaweicloud.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" + "github.com/go-acme/lego/v4/providers/dns/huaweicloud/internal" "github.com/go-acme/lego/v4/providers/dns/internal/ptr" hwauthbasic "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic" hwconfig "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/config" @@ -62,7 +63,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { config *Config - client *hwdns.DnsClient + client *internal.DnsClient recordIDs map[string]string recordIDsMu sync.Mutex @@ -119,7 +120,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return &DNSProvider{ config: config, - client: hwdns.NewDnsClient(client), + client: internal.NewDnsClient(client), recordIDs: map[string]string{}, }, nil } diff --git a/providers/dns/huaweicloud/internal/client.go b/providers/dns/huaweicloud/internal/client.go new file mode 100644 index 000000000..f10cf2dff --- /dev/null +++ b/providers/dns/huaweicloud/internal/client.go @@ -0,0 +1,92 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2020-present. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package internal is a partial copy of https://github.com/huaweicloud/huaweicloud-sdk-go-v3/blob/v0.1.159/services/dns/v2/dns_client.go +package internal + +import ( + httpclient "github.com/huaweicloud/huaweicloud-sdk-go-v3/core" + hwdns "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/dns/v2" + "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/dns/v2/model" +) + +type DnsClient struct { + HcClient *httpclient.HcHttpClient +} + +func NewDnsClient(hcClient *httpclient.HcHttpClient) *DnsClient { + return &DnsClient{HcClient: hcClient} +} + +func (c *DnsClient) ShowRecordSet(request *model.ShowRecordSetRequest) (*model.ShowRecordSetResponse, error) { + requestDef := hwdns.GenReqDefForShowRecordSet() + + if resp, err := c.HcClient.Sync(request, requestDef); err != nil { + return nil, err + } else { + return resp.(*model.ShowRecordSetResponse), nil + } +} + +func (c *DnsClient) CreateRecordSet(request *model.CreateRecordSetRequest) (*model.CreateRecordSetResponse, error) { + requestDef := hwdns.GenReqDefForCreateRecordSet() + + if resp, err := c.HcClient.Sync(request, requestDef); err != nil { + return nil, err + } else { + return resp.(*model.CreateRecordSetResponse), nil + } +} + +func (c *DnsClient) UpdateRecordSet(request *model.UpdateRecordSetRequest) (*model.UpdateRecordSetResponse, error) { + requestDef := hwdns.GenReqDefForUpdateRecordSet() + + if resp, err := c.HcClient.Sync(request, requestDef); err != nil { + return nil, err + } else { + return resp.(*model.UpdateRecordSetResponse), nil + } +} + +func (c *DnsClient) DeleteRecordSet(request *model.DeleteRecordSetRequest) (*model.DeleteRecordSetResponse, error) { + requestDef := hwdns.GenReqDefForDeleteRecordSet() + + if resp, err := c.HcClient.Sync(request, requestDef); err != nil { + return nil, err + } else { + return resp.(*model.DeleteRecordSetResponse), nil + } +} + +func (c *DnsClient) ListRecordSetsByZone(request *model.ListRecordSetsByZoneRequest) (*model.ListRecordSetsByZoneResponse, error) { + requestDef := hwdns.GenReqDefForListRecordSetsByZone() + + if resp, err := c.HcClient.Sync(request, requestDef); err != nil { + return nil, err + } else { + return resp.(*model.ListRecordSetsByZoneResponse), nil + } +} + +func (c *DnsClient) ListPublicZones(request *model.ListPublicZonesRequest) (*model.ListPublicZonesResponse, error) { + requestDef := hwdns.GenReqDefForListPublicZones() + + if resp, err := c.HcClient.Sync(request, requestDef); err != nil { + return nil, err + } else { + return resp.(*model.ListPublicZonesResponse), nil + } +} From 793f65fed9af80d5887571d9b26482f64aa85532 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 21 Jul 2025 12:56:25 +0200 Subject: [PATCH 133/298] chore: update dependencies (#2592) --- go.mod | 151 ++++++++------- go.sum | 352 ++++++++++++++++++---------------- providers/dns/ovh/ovh_test.go | 6 +- 3 files changed, 267 insertions(+), 242 deletions(-) diff --git a/go.mod b/go.mod index 5d926b572..f2c6a71d0 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/go-acme/lego/v4 go 1.24.0 require ( - cloud.google.com/go/compute/metadata v0.6.0 + cloud.google.com/go/compute/metadata v0.7.0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 @@ -16,145 +16,144 @@ require ( github.com/BurntSushi/toml v1.5.0 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 - github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.11 - github.com/aliyun/credentials-go v1.4.5 - github.com/aws/aws-sdk-go-v2 v1.36.3 - github.com/aws/aws-sdk-go-v2/config v1.29.9 - github.com/aws/aws-sdk-go-v2/credentials v1.17.62 - github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.1 - github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 - github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 + github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.8 + github.com/aliyun/credentials-go v1.4.6 + github.com/aws/aws-sdk-go-v2 v1.36.6 + github.com/aws/aws-sdk-go-v2/config v1.29.18 + github.com/aws/aws-sdk-go-v2/credentials v1.17.71 + github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.5 + github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 github.com/aziontech/azionapi-go-sdk v0.142.0 - github.com/baidubce/bce-sdk-go v0.9.223 + github.com/baidubce/bce-sdk-go v0.9.235 github.com/cenkalti/backoff/v4 v4.3.0 github.com/dnsimple/dnsimple-go/v4 v4.0.0 - github.com/exoscale/egoscale/v3 v3.1.13 + github.com/exoscale/egoscale/v3 v3.1.24 github.com/go-acme/alidns-20150109/v4 v4.5.10 github.com/go-acme/tencentclouddnspod v1.0.1208 - github.com/go-jose/go-jose/v4 v4.0.5 - github.com/go-viper/mapstructure/v2 v2.2.1 + github.com/go-jose/go-jose/v4 v4.1.1 + github.com/go-viper/mapstructure/v2 v2.4.0 github.com/google/go-cmp v0.7.0 github.com/google/go-querystring v1.1.0 github.com/gophercloud/gophercloud v1.14.1 github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/go-version v1.7.0 - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.141 + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.159 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df - github.com/infobloxopen/infoblox-go-client/v2 v2.9.0 + github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 github.com/labbsr0x/bindman-dns-webhook v1.0.2 github.com/ldez/grignotin v0.9.0 - github.com/linode/linodego v1.48.1 + github.com/linode/linodego v1.53.0 github.com/liquidweb/liquidweb-go v1.6.4 github.com/mattn/go-isatty v0.0.20 - github.com/miekg/dns v1.1.64 + github.com/miekg/dns v1.1.67 github.com/mimuret/golang-iij-dpf v0.9.1 github.com/namedotcom/go/v4 v4.0.2 github.com/nrdcg/auroradns v1.1.0 - github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 - github.com/nrdcg/desec v0.10.0 + github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea + github.com/nrdcg/desec v0.11.0 github.com/nrdcg/dnspod-go v0.4.0 github.com/nrdcg/freemyip v0.3.0 github.com/nrdcg/goacmedns v0.2.0 - github.com/nrdcg/goinwx v0.10.0 + github.com/nrdcg/goinwx v0.11.0 github.com/nrdcg/mailinabox v0.2.0 github.com/nrdcg/namesilo v0.2.1 github.com/nrdcg/nodion v0.1.0 - github.com/nrdcg/oci-go-sdk/common/v1065 v1065.95.0 - github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.95.0 + github.com/nrdcg/oci-go-sdk/common/v1065 v1065.95.2 + github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.95.2 github.com/nrdcg/porkbun v0.4.0 github.com/nzdjb/go-metaname v1.0.0 - github.com/ovh/go-ovh v1.7.0 - github.com/pquerna/otp v1.4.0 + github.com/ovh/go-ovh v1.9.0 + github.com/pquerna/otp v1.5.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.14.0 - github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32 + github.com/sacloud/api-client-go v0.3.2 + github.com/sacloud/iaas-api-go v1.16.1 + github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34 github.com/selectel/domains-go v1.1.0 github.com/selectel/go-selvpcclient/v4 v4.1.0 github.com/softlayer/softlayer-go v1.1.7 github.com/stretchr/testify v1.10.0 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1208 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1210 github.com/transip/gotransip/v6 v6.26.0 github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec - github.com/urfave/cli/v2 v2.27.6 + github.com/urfave/cli/v2 v2.27.7 github.com/vinyldns/go-vinyldns v0.9.16 - github.com/volcengine/volc-sdk-golang v1.0.199 - github.com/vultr/govultr/v3 v3.17.0 + github.com/volcengine/volc-sdk-golang v1.0.216 + github.com/vultr/govultr/v3 v3.21.1 github.com/yandex-cloud/go-genproto v0.14.0 - github.com/yandex-cloud/go-sdk/services/dns v0.0.2 - github.com/yandex-cloud/go-sdk/v2 v2.0.6 - golang.org/x/crypto v0.39.0 - golang.org/x/net v0.41.0 - golang.org/x/oauth2 v0.28.0 + github.com/yandex-cloud/go-sdk/services/dns v0.0.3 + github.com/yandex-cloud/go-sdk/v2 v2.0.8 + golang.org/x/crypto v0.40.0 + golang.org/x/net v0.42.0 + golang.org/x/oauth2 v0.30.0 golang.org/x/text v0.27.0 - golang.org/x/time v0.11.0 - google.golang.org/api v0.227.0 - gopkg.in/ns1/ns1-go.v2 v2.13.0 + golang.org/x/time v0.12.0 + google.golang.org/api v0.242.0 + gopkg.in/ns1/ns1-go.v2 v2.14.4 gopkg.in/yaml.v2 v2.4.0 software.sslmate.com/src/go-pkcs12 v0.5.0 ) require ( - cloud.google.com/go/auth v0.15.0 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cloud.google.com/go/auth v0.16.2 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect github.com/alibabacloud-go/debug v1.0.1 // indirect github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect github.com/alibabacloud-go/openapi-util v0.1.1 // indirect - github.com/alibabacloud-go/tea v1.2.2 // indirect - github.com/alibabacloud-go/tea-utils/v2 v2.0.6 // indirect - github.com/alibabacloud-go/tea-xml v1.1.3 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect + github.com/alibabacloud-go/tea v1.3.9 // indirect + github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 // indirect - github.com/aws/smithy-go v1.22.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.37 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.18 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.25.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4 // indirect + github.com/aws/smithy-go v1.22.4 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect - github.com/clbanning/mxj/v2 v2.5.5 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/clbanning/mxj/v2 v2.7.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-errors/errors v1.0.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.16.0 // indirect + github.com/go-playground/validator/v10 v10.23.0 // indirect github.com/go-resty/resty/v2 v2.16.5 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect - github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/googleapis/gax-go/v2 v2.14.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -163,7 +162,7 @@ require ( github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/labbsr0x/goh v1.0.1 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/liquidweb/liquidweb-cli v0.6.9 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -177,8 +176,8 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sacloud/go-http v0.1.8 // indirect - github.com/sacloud/packages-go v0.0.10 // indirect + github.com/sacloud/go-http v0.1.9 // indirect + github.com/sacloud/packages-go v0.0.11 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect @@ -198,12 +197,12 @@ require ( github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/ratelimit v0.3.0 // indirect + go.uber.org/ratelimit v0.3.1 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect golang.org/x/mod v0.25.0 // indirect diff --git a/go.sum b/go.sum index ef2a6f830..6ed16ca42 100644 --- a/go.sum +++ b/go.sum @@ -13,18 +13,18 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= -cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= -cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= -cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4= +cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -42,14 +42,14 @@ github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYs github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw= @@ -85,10 +85,9 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -124,8 +123,9 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= -github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.11 h1:GkVQ9AphMCmgAYakcTpH/OuFz0mQUypO/JiOvo0wgVA= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.11/go.mod h1:wHxkgZT1ClZdcwEVP/pDgYK/9HucsnCfMipmJgCz4xY= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.8 h1:AL+nH363NJFS1NXIjCdmj5MOElgKEqgFeoq7vjje350= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.8/go.mod h1:d+z3ScRqc7PFzg4h9oqE3h8yunRZvAvU7u+iuPYEhpU= github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= @@ -145,19 +145,21 @@ github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeG github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= -github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU= github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= +github.com/alibabacloud-go/tea v1.3.9 h1:bjgt1bvdY780vz/17iWNNtbXl4A77HWntWMeaUF3So0= +github.com/alibabacloud-go/tea v1.3.9/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= -github.com/alibabacloud-go/tea-utils/v2 v2.0.6 h1:ZkmUlhlQbaDC+Eba/GARMPy6hKdCLiSke5RsN5LcyQ0= github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= -github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0= +github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0= +github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= -github.com/aliyun/credentials-go v1.4.5 h1:O76WYKgdy1oQYYiJkERjlA2dxGuvLRrzuO2ScrtGWSk= github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= +github.com/aliyun/credentials-go v1.4.6 h1:CG8rc/nxCNKfXbZWpWDzI9GjF4Tuu3Es14qT8Y0ClOk= +github.com/aliyun/credentials-go v1.4.6/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -167,52 +169,52 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= -github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14= -github.com/aws/aws-sdk-go-v2/config v1.29.9 h1:Kg+fAYNaJeGXp1vmjtidss8O2uXIsXwaRqsQJKXVr+0= -github.com/aws/aws-sdk-go-v2/config v1.29.9/go.mod h1:oU3jj2O53kgOU4TXq/yipt6ryiooYjlkqqVaZk7gY/U= -github.com/aws/aws-sdk-go-v2/credentials v1.17.62 h1:fvtQY3zFzYJ9CfixuAQ96IxDrBajbBWGqjNTCa79ocU= -github.com/aws/aws-sdk-go-v2/credentials v1.17.62/go.mod h1:ElETBxIQqcxej++Cs8GyPBbgMys5DgQPTwo7cUPDKt8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q= +github.com/aws/aws-sdk-go-v2 v1.36.6 h1:zJqGjVbRdTPojeCGWn5IR5pbJwSQSBh5RWFTQcEQGdU= +github.com/aws/aws-sdk-go-v2 v1.36.6/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY= +github.com/aws/aws-sdk-go-v2/config v1.29.18 h1:x4T1GRPnqKV8HMJOMtNktbpQMl3bIsfx8KbqmveUO2I= +github.com/aws/aws-sdk-go-v2/config v1.29.18/go.mod h1:bvz8oXugIsH8K7HLhBv06vDqnFv3NsGDt2Znpk7zmOU= +github.com/aws/aws-sdk-go-v2/credentials v1.17.71 h1:r2w4mQWnrTMJjOyIsZtGp3R3XGY3nqHn8C26C2lQWgA= +github.com/aws/aws-sdk-go-v2/credentials v1.17.71/go.mod h1:E7VF3acIup4GB5ckzbKFrCK0vTvEQxOxgdq4U3vcMCY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 h1:D9ixiWSG4lyUBL2DDNK924Px9V/NBVpML90MHqyTADY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33/go.mod h1:caS/m4DI+cij2paz3rtProRBI4s/+TCiWoaWZuQ9010= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 h1:osMWfm/sC/L4tvEdQ65Gri5ZZDCUpuYJZbTTDrsn4I0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37/go.mod h1:ZV2/1fbjOPr4G4v38G3Ww5TBT4+hmsK45s/rxu1fGy0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 h1:v+X21AvTb2wZ+ycg1gx+orkB/9U6L7AOp93R7qYxsxM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37/go.mod h1:G0uM1kyssELxmJ2VZEfG0q2npObR3BAkF3c1VsfVnfs= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.37 h1:XTZZ0I3SZUHAtBLBU6395ad+VOblE0DwQP6MuaNeics= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.37/go.mod h1:Pi6ksbniAWVwu2S8pEzcYPyhUkAcLaufxN7PfAUQjBk= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 h1:lguz0bmOoGzozP9XfRJR1QIayEYo+2vP/No3OfLF0pU= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.1 h1:0j58UseBtLuBcP6nY2z4SM1qZEvLF0ylyH6+ggnphLg= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.1/go.mod h1:Qy22QnQSdHbZwMZrarsWZBIuK51isPlkD+Z4sztxX0o= -github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0 h1:/nkJHXtJXJeelXHqG0898+fWKgvfaXBhGzbCsSmn9j8= -github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0/go.mod h1:kGYOjvTa0Vw0qxrqrOLut1vMnui6qLxqv/SX3vYeM8Y= -github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 h1:jIiopHEV22b4yQP2q36Y0OmwLbsxNWdWwfZRR5QRRO4= -github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc= -github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 h1:8JdC7Gr9NROg1Rusk25IcZeTO59zLxsKgE0gkh5O6h0= -github.com/aws/aws-sdk-go-v2/service/sso v1.25.1/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 h1:KwuLovgQPcdjNMfFt9OhUd9a2OwcOKhxfvF4glTzLuA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.5 h1:M5/B8JUaCI8+9QD+u3S/f4YHpvqE9RpSkV3rf0Iks2w= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.5/go.mod h1:Bktzci1bwdbpuLiu3AOksiNPMl/LLKmX1TWmqp2xbvs= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 h1:vvbXsA2TVO80/KT7ZqCbx934dt6PY+vQ8hZpUZ/cpYg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18/go.mod h1:m2JJHledjBGNMsLOF1g9gbAxprzq3KjC8e4lxtn+eWg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.18 h1:OS2e0SKqsU2LiJPqL8u9x41tKc6MMEHrWjLVLn3oysg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.18/go.mod h1:+Yrk+MDGzlNGxCXieljNeWpoZTCQUQVL+Jk9hGGJ8qM= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.5 h1:DYQbfSAWcMwRM0LbCDyQkPB1AcaZcLzLoaFrYcpyMag= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.5/go.mod h1:Lav4KLgncVjjrwLWutOccjEgJ4T/RAdY+Ic0hmNIgI0= +github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1 h1:R3nSX1hguRy6MnknHiepSvqnnL8ansFwK2hidPesAYU= +github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1/go.mod h1:fmSiB4OAghn85lQgk7XN9l9bpFg5Bm1v3HuaXKytPEw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1 h1:RkHXU9jP0DptGy7qKI8CBGsUJruWz0v5IgwBa2DwWcU= +github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1/go.mod h1:3xAOf7tdKF+qbb+XpU+EPhNXAdun3Lu1RcDrj8KC24I= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.6 h1:rGtWqkQbPk7Bkwuv3NzpE/scwwL9sC1Ul3tn9x83DUI= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.6/go.mod h1:u4ku9OLv4TO4bCPdxf4fA1upaMaJmP9ZijGk3AAOC6Q= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4 h1:OV/pxyXh+eMA0TExHEC4jyWdumLxNbzz1P0zJoezkJc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4/go.mod h1:8Mm5VGYwtm+r305FfPSuc+aFkrypeylGYhFim6XEPoc= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 h1:aUrLQwJfZtwv3/ZNG2xRtEen+NqI3iesuacjP51Mv1s= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.1/go.mod h1:3wFBZKoWnX3r+Sm7in79i54fBmNfwhdNdQuscCw7QIk= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= -github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw= +github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/aziontech/azionapi-go-sdk v0.142.0 h1:1NOHXlC0/7VgbfbTIGVpsYn1THCugM4/SCOXVdUHQ+A= github.com/aziontech/azionapi-go-sdk v0.142.0/go.mod h1:cA5DY/VP4X5Eu11LpQNzNn83ziKjja7QVMIl4J45feA= -github.com/baidubce/bce-sdk-go v0.9.223 h1:vvDeIemf7ePPP59nLHCntQ/vS++ok2HKbRPgmz1VZKU= -github.com/baidubce/bce-sdk-go v0.9.223/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/baidubce/bce-sdk-go v0.9.235 h1:iAi+seH9w1Go2szFNzyGumahLGDsuYZ3i8hduX3qiM8= +github.com/baidubce/bce-sdk-go v0.9.235/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -242,8 +244,9 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= -github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= +github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -256,8 +259,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= -github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= +github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -284,8 +287,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/exoscale/egoscale/v3 v3.1.13 h1:CAGC7QRjp2AiGj01agsSD0VKCp4OZmW5f51vV2IguNQ= -github.com/exoscale/egoscale/v3 v3.1.13/go.mod h1:t9+MpSEam94na48O/xgvvPFpQPRiwZ3kBN4/UuQtKco= +github.com/exoscale/egoscale/v3 v3.1.24 h1:EUWmjw/JgMj1faX5ojosjrJE5eY0QEWP0KBmLyFU6aE= +github.com/exoscale/egoscale/v3 v3.1.24/go.mod h1:A53enXfm8nhVMpIYw0QxiwQ2P6AdCF4F/nVYChNEzdE= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= @@ -305,10 +308,10 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-acme/alidns-20150109/v4 v4.5.10 h1:epLD0VaHR5XUpiM6mjm4MzQFICrk+zpuqDz2aO1/R/k= @@ -321,8 +324,8 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= -github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= @@ -343,8 +346,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= -github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= +github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -352,8 +355,8 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= @@ -368,8 +371,8 @@ github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -421,6 +424,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= @@ -452,8 +456,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= -github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= +github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw= github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= @@ -520,8 +524,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.141 h1:8i57QAi5u+iPAYze92bkIvZoHiS0J45ndul5glr/NE8= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.141/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.159 h1:6LZysc4iyO4cHB1aJsRklWfSEJr8CEhW7BmcM0SkYcU= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.159/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -529,11 +533,11 @@ github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/infobloxopen/infoblox-go-client/v2 v2.9.0 h1:wS8kTlQVeVbrepeY83s9X+XdSa6Qah5KO+tdW+zRQXU= -github.com/infobloxopen/infoblox-go-client/v2 v2.9.0/go.mod h1:NeNJpz09efw/edzqkVivGv1bWqBXTomqYBRFbP+XBqg= +github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 h1:AKsihjFT/t6Y0keEv3p59DACcOuh0inWXdUB0ZOzYH0= +github.com/infobloxopen/infoblox-go-client/v2 v2.10.0/go.mod h1:NeNJpz09efw/edzqkVivGv1bWqBXTomqYBRFbP+XBqg= github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= -github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= -github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= +github.com/jarcoal/httpmock v1.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k= +github.com/jarcoal/httpmock v1.4.0/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= @@ -561,14 +565,13 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8 github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= -github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= -github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -592,10 +595,10 @@ github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk= github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/linode/linodego v1.48.1 h1:Ojw1S+K5jJr1dggO8/H6r4FINxXnJbOU5GkbpaTfmhU= -github.com/linode/linodego v1.48.1/go.mod h1:fc3t60If8X+yZTFAebhCnNDFrhwQhq9HDU92WnBousQ= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/linode/linodego v1.53.0 h1:UWr7bUUVMtcfsuapC+6blm6+jJLPd7Tf9MZUpdOERnI= +github.com/linode/linodego v1.53.0/go.mod h1:bI949fZaVchjWyKIA08hNyvAcV6BAS+PM2op3p7PAWA= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= @@ -630,8 +633,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ= -github.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck= +github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0= +github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34= github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= @@ -675,28 +678,28 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uY github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo= github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk= -github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 h1:ouZ2JWDl8IW5k1qugYbmpbmW8hn85Ig6buSMBRlz3KI= -github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3/go.mod h1:ZwadWt7mVhMHMbAQ1w8IhDqtWO3eWqWq72W7trnaiE8= -github.com/nrdcg/desec v0.10.0 h1:qrEDiqnsvNU9QE7lXIXi/tIHAfyaFXKxF2/8/52O8uM= -github.com/nrdcg/desec v0.10.0/go.mod h1:5+4vyhMRTs49V9CNoODF/HwT8Mwxv9DJ6j+7NekUnBs= +github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea h1:OSgRS4kqOs/WuxuFOObP2gwrenL4/qiKXQbQugr/Two= +github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea/go.mod h1:IDRRngAngb2eTEaWgpO0hukQFI/vJId46fT1KErMytA= +github.com/nrdcg/desec v0.11.0 h1:XZVHy07sg12y8FozMp+l7XvzPsdzog0AYXuQMaHBsfs= +github.com/nrdcg/desec v0.11.0/go.mod h1:5+4vyhMRTs49V9CNoODF/HwT8Mwxv9DJ6j+7NekUnBs= github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U= github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc= github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM= github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0= github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg= -github.com/nrdcg/goinwx v0.10.0 h1:6W630bjDxQD6OuXKqrFRYVpTt0G/9GXXm3CeOrN0zJM= -github.com/nrdcg/goinwx v0.10.0/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4= +github.com/nrdcg/goinwx v0.11.0 h1:GER0SE3POub7rxARt3Y3jRy1OON1hwF1LRxHz5xsFBw= +github.com/nrdcg/goinwx v0.11.0/go.mod h1:0BXSC0FxVtU4aTjX0Zw3x0DK32tjugLzeNIAGtwXvPQ= github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk= github.com/nrdcg/mailinabox v0.2.0/go.mod h1:0yxqeYOiGyxAu7Sb94eMxHPIOsPYXAjTeA9ZhePhGnc= github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg= github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.95.0 h1:COzvb58Oc7/ADt3699jzLMJd0HOW/v2tG9b+74IFhrA= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.95.0/go.mod h1:O6osg9dPzXq7H2ib/1qzimzG5oXSJFgccR7iawg7SwA= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.95.0 h1:KWGOjA6n7R2VSarQsQNakP8gGCZNFgnv3zxc4edqLjo= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.95.0/go.mod h1:iM3irKzFqd/xGZUWA+k93N3Sy+G+4KBqqQp2GBeBqeg= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.95.2 h1:a7QUZD5c+NkrFrdkdyJUO9cOUo8VQJyRkcIzk9Wh+DI= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.95.2/go.mod h1:O6osg9dPzXq7H2ib/1qzimzG5oXSJFgccR7iawg7SwA= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.95.2 h1:yflYnbQu4ciWH/GEztqlAccLPw4k5mp11uhW++al5ow= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.95.2/go.mod h1:atPDu37gu8HT7TtPpovrkgNmDAgOGM6TVEJ7ANTblMs= github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -726,8 +729,8 @@ github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeD github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= -github.com/ovh/go-ovh v1.7.0 h1:V14nF7FwDjQrZt9g7jzcvAAQ3HN6DNShRFRMC3jLoPw= -github.com/ovh/go-ovh v1.7.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= +github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE= +github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= @@ -755,8 +758,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= -github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= +github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -789,8 +792,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/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= -github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= +github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= +github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= 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= @@ -802,20 +805,20 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sacloud/api-client-go v0.2.10 h1:+rv3jDohD+pkdYwOTBiB+jZsM0xK3AxadXRzhp3q66c= -github.com/sacloud/api-client-go v0.2.10/go.mod h1:Jj3CTy2+O4bcMedVDXlbHuqqche85HEPuVXoQFhLaRc= -github.com/sacloud/go-http v0.1.8 h1:ynreWA/vnM8G2ksbMlmefBHsXURKPz49qlPRqQ9IQdw= -github.com/sacloud/go-http v0.1.8/go.mod h1:7TL7TN1fnPKHsMifIqURDkGujnKViCgEz5Ei/LQdFK8= -github.com/sacloud/iaas-api-go v1.14.0 h1:xjkFWqdo4ilTrKPNNYBNWR/CZ/kVRsJrdAHAad6J/AQ= -github.com/sacloud/iaas-api-go v1.14.0/go.mod h1:C8os2Mnj0TOmMdSllwhaDWKMVG2ysFnpe69kyA4M3V0= -github.com/sacloud/packages-go v0.0.10 h1:UiQGjy8LretewkRhsuna1TBM9Vz/l9FoYpQx+D+AOck= -github.com/sacloud/packages-go v0.0.10/go.mod h1:f8QITBh9z4IZc4yE9j21Q8b0sXEMwRlRmhhjWeDVTYs= +github.com/sacloud/api-client-go v0.3.2 h1:INbdSpQbyGN9Ai4hQ+Gbv3UQcgtRPG2tJrOmqT7HGl0= +github.com/sacloud/api-client-go v0.3.2/go.mod h1:0p3ukcWYXRCc2AUWTl1aA+3sXLvurvvDqhRaLZRLBwo= +github.com/sacloud/go-http v0.1.9 h1:Xa5PY8/pb7XWhwG9nAeXSrYXPbtfBWqawgzxD5co3VE= +github.com/sacloud/go-http v0.1.9/go.mod h1:DpDG+MSyxYaBwPJ7l3aKLMzwYdTVtC5Bo63HActcgoE= +github.com/sacloud/iaas-api-go v1.16.1 h1:B5Lec9WyZkrOCjtGkVuPn5RxDm/zCzazVsHh7BQIjYQ= +github.com/sacloud/iaas-api-go v1.16.1/go.mod h1:QVPHLwYzpECMsuml55I3FWAggsb4XSuzYGE9re/SkrQ= +github.com/sacloud/packages-go v0.0.11 h1:hrRWLmfPM9w7GBs6xb5/ue6pEMl8t1UuDKyR/KfteHo= +github.com/sacloud/packages-go v0.0.11/go.mod h1:XNF5MCTWcHo9NiqWnYctVbASSSZR3ZOmmQORIzcurJ8= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32 h1:4+LP7qmsLSGbmc66m1s5dKRMBwztRppfxFKlYqYte/c= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32/go.mod h1:kzh+BSAvpoyHHdHBCDhmSWtBc1NbLMZ2lWHqnBoxFks= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34 h1:48+VFHsyVcAHIN2v1Ao9v1/RkjJS5AwctFucBrfYNIA= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34/go.mod h1:zFWiHphneiey3s8HOtAEnGrRlWivNaxW5T6d5Xfco7g= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/selectel/domains-go v1.1.0 h1:futG50J43ALLKQAnZk9H9yOtLGnSUh7c5hSvuC5gSHo= github.com/selectel/domains-go v1.1.0/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA= @@ -886,15 +889,15 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1208 h1:hE2rM9GoqISu4lgQVbx9+bTlPOB000pdUfbT9lwrC50= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1208/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1210 h1:waSk2KyI2VvXtR+XQJm0v1lWfgbJg51iSWJh4hWnyeo= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1210/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= @@ -905,14 +908,14 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec h1:2s/ghQ8wKE+UzD/hf3P4Gd1j0JI9ncbxv+nsypPoUYI= github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7Qs3ku1ckpqed8tCRSqTlp8NAeZfAVpfx4OzXMss= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= -github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= +github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= -github.com/volcengine/volc-sdk-golang v1.0.199 h1:zv9QOqTl/IsLwtfC37GlJtcz6vMAHi+pjq8ILWjLYUc= -github.com/volcengine/volc-sdk-golang v1.0.199/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ= -github.com/vultr/govultr/v3 v3.17.0 h1:His5Jh5N8KKqaJxfy3uG6jQbLXy0TmQhNxOiRvkKk00= -github.com/vultr/govultr/v3 v3.17.0/go.mod h1:q34Wd76upKmf+vxFMgaNMH3A8BbsPBmSYZUGC8oZa5w= +github.com/volcengine/volc-sdk-golang v1.0.216 h1:+wAq8RvxpGECveRJaAXZFpzrZoQ33WjMuRyd9iY2Oc0= +github.com/volcengine/volc-sdk-golang v1.0.216/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= +github.com/vultr/govultr/v3 v3.21.1 h1:0cnA8fXiqayPGbAlNHaW+5oCQjpDNkkAm3Nt3LOHplM= +github.com/vultr/govultr/v3 v3.21.1/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= @@ -926,10 +929,10 @@ github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGC github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yandex-cloud/go-genproto v0.14.0 h1:yDqD260mICkjodXyAaDhESfrLr6gIGwwRc9MYE0jvW0= github.com/yandex-cloud/go-genproto v0.14.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= -github.com/yandex-cloud/go-sdk/services/dns v0.0.2 h1:NgnRRqTT/vnbwsiaZjtRBZaDDa6lIP3dFyCHCgclgYg= -github.com/yandex-cloud/go-sdk/services/dns v0.0.2/go.mod h1:IfuJXXIz7bkQIVD1HS6UinfffzpXexJUPMvffVUsPOU= -github.com/yandex-cloud/go-sdk/v2 v2.0.6 h1:RGG+HxLZPnX9FghJzVIxqcWyFgq0Dxk7NrVZAhEMmAY= -github.com/yandex-cloud/go-sdk/v2 v2.0.6/go.mod h1:fhu879XqRmN5hJ1HcYDDIdNdugApKLq6FoEuK8vc4r4= +github.com/yandex-cloud/go-sdk/services/dns v0.0.3 h1:erphTBXKSpm/tETa6FXrw4niSHjhySzAKHUc2/BZKx0= +github.com/yandex-cloud/go-sdk/services/dns v0.0.3/go.mod h1:lbBaFJVouETfVnd3YzNF5vW6vgYR2FVfGLUzLexyGlI= +github.com/yandex-cloud/go-sdk/v2 v2.0.8 h1:wQNIzEZYnClSQyo2fjEgnGEErWjJNBpSAinaKcP+VSg= +github.com/yandex-cloud/go-sdk/v2 v2.0.8/go.mod h1:9Gqpq7d0EUAS+H2OunILtMi3hmMPav+fYoy9rmydM4s= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= @@ -955,20 +958,20 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -982,8 +985,8 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/ratelimit v0.3.0 h1:IdZd9wqvFXnvLvSEBo0KPcGfkoBGNkpTHlrE3Rcjkjw= -go.uber.org/ratelimit v0.3.0/go.mod h1:So5LG7CV1zWpY1sHe+DXTJqQvOx+FFPFaAs2SnoyBaI= +go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= +go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= @@ -1013,13 +1016,16 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1060,6 +1066,9 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1112,12 +1121,15 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1125,8 +1137,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= -golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1140,6 +1152,9 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1217,25 +1232,32 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1252,6 +1274,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1260,8 +1284,8 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1317,6 +1341,8 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1343,8 +1369,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.227.0 h1:QvIHF9IuyG6d6ReE+BNd11kIB8hZvjN8Z5xY5t21zYc= -google.golang.org/api v0.227.0/go.mod h1:EIpaG6MbTgQarWF5xJvX0eOJPK9n/5D4Bynb9j2HXvQ= +google.golang.org/api v0.242.0 h1:7Lnb1nfnpvbkCiZek6IXKdJ0MFuAZNAJKQfA1ws62xg= +google.golang.org/api v0.242.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1383,6 +1409,8 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= +google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= @@ -1441,8 +1469,8 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/ns1/ns1-go.v2 v2.13.0 h1:I5NNqI9Bi1SGK92TVkOvLTwux5LNrix/99H2datVh48= -gopkg.in/ns1/ns1-go.v2 v2.13.0/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= +gopkg.in/ns1/ns1-go.v2 v2.14.4 h1:77eP71rZ24I+9k1gITgjJXRyJzzmflA9oPUkYPB/wyc= +gopkg.in/ns1/ns1-go.v2 v2.14.4/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/providers/dns/ovh/ovh_test.go b/providers/dns/ovh/ovh_test.go index f070f2e85..fcb2300b6 100644 --- a/providers/dns/ovh/ovh_test.go +++ b/providers/dns/ovh/ovh_test.go @@ -201,12 +201,11 @@ func TestNewDNSProviderConfig(t *testing.T) { consumerKey: "D", }, { - desc: "application key: missing api endpoint", + desc: "application key: default api endpoint", apiEndpoint: "", applicationKey: "B", applicationSecret: "C", consumerKey: "D", - expected: "ovh: new client: unknown endpoint '', consider checking 'Endpoints' list or using an URL", }, { desc: "application key: invalid api endpoint", @@ -239,11 +238,10 @@ func TestNewDNSProviderConfig(t *testing.T) { clientSecret: "C", }, { - desc: "oauth2: missing api endpoint", + desc: "oauth2: default api endpoint", apiEndpoint: "", clientID: "B", clientSecret: "C", - expected: "ovh: new client: unknown endpoint '', consider checking 'Endpoints' list or using an URL", }, { desc: "oauth2: invalid api endpoint", From ced6669dcd48a6580eaedbbb8e48c8c14e34a8cb Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 21 Jul 2025 12:58:18 +0200 Subject: [PATCH 134/298] Prepare release v4.25.0 --- CHANGELOG.md | 21 +++++++++++++++++++ acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efccdba6f..a57696ccd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## [v4.25.0](https://github.com/go-acme/lego/releases/tag/v4.25.0) (2025-07-21) + +The binary size of this release is about ~50% smaller compared to previous releases. + +This will also reduce the module cache usage by 320 MB (this will only affect users of lego as a library or who build lego themselves). + +### Added + +- **[dnsprovider]** Add DNS provider for ZoneEdit +- **[cli]** Add an option to define dynamically the renew date +- **[lib,cli]** Add an option to disable common name in CSR + +### Changed +- +- **[dnsprovider]** vinyldns: add an option to add quotes around the TXT record value +- **[dnsprovider]** ionos: increase default propagation timeout + +### Fixed + +- **[cli]** fix: enforce domain into renewal command + ## [v4.24.0](https://github.com/go-acme/lego/releases/tag/v4.24.0) (2025-07-07) ### Added diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 803bbf018..1d97b24da 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.24.0" + ourUserAgent = "xenolf-acme/4.25.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 6e1ca64c8..4c9b0a979 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.24.0+dev-detach" +const defaultVersion = "v4.25.0+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index fed65ee88..061918d10 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.24.0" + ourUserAgent = "goacme-lego/4.25.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From f54baa83ac725058699c26f56038fe4b799166f9 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 21 Jul 2025 12:58:35 +0200 Subject: [PATCH 135/298] Detach v4.25.0 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 1d97b24da..06bfa398c 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 4c9b0a979..af4b8b522 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.25.0+dev-release" +const defaultVersion = "v4.25.0+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 061918d10..1c020f845 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From 833d3b814731e33cbc01b365a7aab8baadf2d397 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 21 Jul 2025 16:26:53 +0200 Subject: [PATCH 136/298] fix: wrong CLI flag type (#2595) --- cmd/flags.go | 2 +- docs/data/zz_cli_help.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/flags.go b/cmd/flags.go index 066517229..4ceccbdd9 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -89,7 +89,7 @@ func CreateFlags(defaultPath string) []cli.Flag { EnvVars: []string{envEmail}, Usage: "Email used for registration and recovery contact.", }, - &cli.StringFlag{ + &cli.BoolFlag{ Name: flgDisableCommonName, EnvVars: []string{flgDisableCommonName}, Usage: "Disable the use of the common name in the CSR.", diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 0ba9b9d8a..95ff0770a 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -23,7 +23,7 @@ GLOBAL OPTIONS: --server value, -s value CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. (default: "https://acme-v02.api.letsencrypt.org/directory") [$LEGO_SERVER] --accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service. (default: false) --email value, -m value Email used for registration and recovery contact. [$LEGO_EMAIL] - --disable-cn value Disable the use of the common name in the CSR. [$disable-cn] + --disable-cn Disable the use of the common name in the CSR. (default: false) [$disable-cn] --csr value, -c value Certificate signing request filename, if an external CSR is to be used. --eab Use External Account Binding for account registration. Requires --kid and --hmac. (default: false) [$LEGO_EAB] --kid value Key identifier from External CA. Used for External Account Binding. [$LEGO_EAB_KID] From 6f54d605994da80b50853afd5952c771ed6086e7 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 21 Jul 2025 18:03:41 +0200 Subject: [PATCH 137/298] Prepare release v4.25.1 --- CHANGELOG.md | 6 ++++++ acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a57696ccd..98c7a97ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [v4.25.1](https://github.com/go-acme/lego/releases/tag/v4.25.1) (2025-07-21) + +### Fixed + +- **[cli]** fix: wrong CLI flag type + ## [v4.25.0](https://github.com/go-acme/lego/releases/tag/v4.25.0) (2025-07-21) The binary size of this release is about ~50% smaller compared to previous releases. diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 06bfa398c..a4c287793 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.25.0" + ourUserAgent = "xenolf-acme/4.25.1" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index af4b8b522..6b3086baa 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.25.0+dev-detach" +const defaultVersion = "v4.25.1+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 1c020f845..f380c05b0 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.25.0" + ourUserAgent = "goacme-lego/4.25.1" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From 8a754758944776e0ea78fd0e3c658f8db4b66a16 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 21 Jul 2025 18:03:54 +0200 Subject: [PATCH 138/298] Detach v4.25.1 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index a4c287793..8524ba996 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 6b3086baa..6eb7c310d 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.25.1+dev-release" +const defaultVersion = "v4.25.1+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index f380c05b0..d0a435638 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From c689b20fee2f107f88668a78b9b7a3d039559621 Mon Sep 17 00:00:00 2001 From: bllfr0g <39714379+bllfr0g@users.noreply.github.com> Date: Tue, 22 Jul 2025 15:47:44 -0700 Subject: [PATCH 139/298] feat: log when dynamic renew date not yet reached (#2597) Co-authored-by: Fernandez Ludovic --- cmd/cmd_renew.go | 13 ++++++++++--- cmd/cmd_renew_test.go | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index 00bdf1b62..9b1be8c95 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -357,7 +357,7 @@ func needRenewal(x509Cert *x509.Certificate, domain string, days int, dynamic bo } if dynamic { - return needRenewalDynamic(x509Cert, time.Now()) + return needRenewalDynamic(x509Cert, domain, time.Now()) } if days < 0 { @@ -375,7 +375,7 @@ func needRenewal(x509Cert *x509.Certificate, domain string, days int, dynamic bo return false } -func needRenewalDynamic(x509Cert *x509.Certificate, now time.Time) bool { +func needRenewalDynamic(x509Cert *x509.Certificate, domain string, now time.Time) bool { lifetime := x509Cert.NotAfter.Sub(x509Cert.NotBefore) var divisor int64 = 3 @@ -385,7 +385,14 @@ func needRenewalDynamic(x509Cert *x509.Certificate, now time.Time) bool { dueDate := x509Cert.NotAfter.Add(-1 * time.Duration(lifetime.Nanoseconds()/divisor)) - return dueDate.Before(now) + if dueDate.Before(now) { + return true + } + + log.Infof("[%s] The certificate expires at %s, the renewal can be performed in %s: no renewal.", + domain, x509Cert.NotAfter.Format(time.RFC3339), dueDate.Sub(now)) + + return false } // getARIRenewalTime checks if the certificate needs to be renewed using the renewalInfo endpoint. diff --git a/cmd/cmd_renew_test.go b/cmd/cmd_renew_test.go index 405dda5fa..2485c5240 100644 --- a/cmd/cmd_renew_test.go +++ b/cmd/cmd_renew_test.go @@ -161,7 +161,7 @@ func Test_needRenewalDynamic(t *testing.T) { NotAfter: test.notAfter, } - ok := needRenewalDynamic(x509Cert, test.now) + ok := needRenewalDynamic(x509Cert, "example.com", test.now) test.expected(t, ok) }) From 137ad86fa408a4eaa3a93965416b7b48db893d62 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 25 Jul 2025 22:56:41 +0200 Subject: [PATCH 140/298] fix: remove wrong env var (#2600) --- cmd/flags.go | 5 ++--- docs/data/zz_cli_help.toml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/flags.go b/cmd/flags.go index 4ceccbdd9..3372de4ff 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -90,9 +90,8 @@ func CreateFlags(defaultPath string) []cli.Flag { Usage: "Email used for registration and recovery contact.", }, &cli.BoolFlag{ - Name: flgDisableCommonName, - EnvVars: []string{flgDisableCommonName}, - Usage: "Disable the use of the common name in the CSR.", + Name: flgDisableCommonName, + Usage: "Disable the use of the common name in the CSR.", }, &cli.StringFlag{ Name: flgCSR, diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 95ff0770a..35f641cd4 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -23,7 +23,7 @@ GLOBAL OPTIONS: --server value, -s value CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client. (default: "https://acme-v02.api.letsencrypt.org/directory") [$LEGO_SERVER] --accept-tos, -a By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service. (default: false) --email value, -m value Email used for registration and recovery contact. [$LEGO_EMAIL] - --disable-cn Disable the use of the common name in the CSR. (default: false) [$disable-cn] + --disable-cn Disable the use of the common name in the CSR. (default: false) --csr value, -c value Certificate signing request filename, if an external CSR is to be used. --eab Use External Account Binding for account registration. Requires --kid and --hmac. (default: false) [$LEGO_EAB] --kid value Key identifier from External CA. Used for External Account Binding. [$LEGO_EAB_KID] From 605d49d500c361e155489b3947d26eb5637adea9 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 26 Jul 2025 11:16:01 +0200 Subject: [PATCH 141/298] chore: clean up tests and code (#2602) --- acme/api/certificate.go | 55 ++---------- acme/api/certificate_test.go | 36 ++------ acme/api/order_test.go | 67 +++++++------- certificate/certificates_test.go | 87 +++++-------------- certificate/renewal_test.go | 31 +++---- challenge/dns01/dns_challenge_test.go | 6 +- challenge/http01/http_challenge_test.go | 8 +- challenge/resolver/prober_test.go | 44 +++++----- challenge/resolver/solver_manager_test.go | 79 +++++++---------- .../tlsalpn01/tls_alpn_challenge_test.go | 6 +- lego/client_test.go | 2 +- platform/tester/api.go | 63 +++++--------- .../dns/constellix/internal/domains_test.go | 14 +-- .../internal/fixtures/domains-GetAll.json | 8 +- .../internal/fixtures/domains-Search.json | 2 +- providers/dns/gcloud/googlecloud_test.go | 26 +++--- .../dns/servercow/internal/client_test.go | 24 ++--- registration/registar_test.go | 21 +++-- 18 files changed, 213 insertions(+), 366 deletions(-) diff --git a/acme/api/certificate.go b/acme/api/certificate.go index 5f31968cf..b42296768 100644 --- a/acme/api/certificate.go +++ b/acme/api/certificate.go @@ -2,15 +2,12 @@ package api import ( "bytes" - "crypto/x509" "encoding/pem" "errors" "io" "net/http" "github.com/go-acme/lego/v4/acme" - "github.com/go-acme/lego/v4/certcrypto" - "github.com/go-acme/lego/v4/log" ) // maxBodySize is the maximum size of body that we will read. @@ -77,62 +74,22 @@ func (c *CertificateService) get(certURL string, bundle bool) (*acme.RawCertific return nil, resp.Header, err } - cert := c.getCertificateChain(data, resp.Header, bundle, certURL) + cert := c.getCertificateChain(data, bundle) return cert, resp.Header, err } // getCertificateChain Returns the certificate and the issuer certificate. -func (c *CertificateService) getCertificateChain(cert []byte, headers http.Header, bundle bool, certURL string) *acme.RawCertificate { +func (c *CertificateService) getCertificateChain(cert []byte, bundle bool) *acme.RawCertificate { // Get issuerCert from bundled response from Let's Encrypt // See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962 _, issuer := pem.Decode(cert) - if issuer != nil { - // If bundle is false, we want to return a single certificate. - // To do this, we remove the issuer cert(s) from the issued cert. - if !bundle { - cert = bytes.TrimSuffix(cert, issuer) - } - return &acme.RawCertificate{Cert: cert, Issuer: issuer} - } - // The issuer certificate link may be supplied via an "up" link - // in the response headers of a new certificate. - // See https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4.2 - up := getLink(headers, "up") - - issuer, err := c.getIssuerFromLink(up) - if err != nil { - // If we fail to acquire the issuer cert, return the issued certificate - do not fail. - log.Warnf("acme: Could not bundle issuer certificate [%s]: %v", certURL, err) - } else if len(issuer) > 0 { - // If bundle is true, we want to return a certificate bundle. - // To do this, we append the issuer cert to the issued cert. - if bundle { - cert = append(cert, issuer...) - } + // If bundle is false, we want to return a single certificate. + // To do this, we remove the issuer cert(s) from the issued cert. + if !bundle { + cert = bytes.TrimSuffix(cert, issuer) } return &acme.RawCertificate{Cert: cert, Issuer: issuer} } - -// getIssuerFromLink requests the issuer certificate. -func (c *CertificateService) getIssuerFromLink(up string) ([]byte, error) { - if up == "" { - return nil, nil - } - - log.Infof("acme: Requesting issuer cert from %s", up) - - cert, _, err := c.get(up, false) - if err != nil { - return nil, err - } - - _, err = x509.ParseCertificate(cert.Cert) - if err != nil { - return nil, err - } - - return certcrypto.PEMEncode(certcrypto.DERCertificateBytes(cert.Cert)), nil -} diff --git a/acme/api/certificate_test.go b/acme/api/certificate_test.go index 9776cccc5..4ff8fec47 100644 --- a/acme/api/certificate_test.go +++ b/acme/api/certificate_test.go @@ -3,11 +3,11 @@ package api import ( "crypto/rand" "crypto/rsa" - "encoding/pem" "net/http" "testing" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -74,25 +74,9 @@ rzFL1KZfz+HZdnFwFW2T2gVW8L3ii1l9AJDuKzlvjUH3p6bgihVq02sjT8mx+GM2 ` func TestCertificateService_Get_issuerRelUp(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - - mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Link", "<"+apiURL+`/issuer>; rel="up"`) - _, err := w.Write([]byte(certResponseMock)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - mux.HandleFunc("/issuer", func(w http.ResponseWriter, _ *http.Request) { - p, _ := pem.Decode([]byte(issuerMock)) - _, err := w.Write(p.Bytes) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + apiURL := tester.MockACMEServer(). + Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") @@ -107,15 +91,9 @@ func TestCertificateService_Get_issuerRelUp(t *testing.T) { } func TestCertificateService_Get_embeddedIssuer(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - - mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { - _, err := w.Write([]byte(certResponseMock)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + apiURL := tester.MockACMEServer(). + Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") diff --git a/acme/api/order_test.go b/acme/api/order_test.go index cf28e517b..ebbb7d7ce 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -11,55 +11,48 @@ import ( "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/go-jose/go-jose/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestOrderService_NewWithOptions(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - // small value keeps test fast privateKey, errK := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, errK, "Could not generate test key") - mux.HandleFunc("/newOrder", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } + apiURL := tester.MockACMEServer(). + Route("POST /newOrder", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + body, err := readSignedBody(req, privateKey) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } - body, err := readSignedBody(r, privateKey) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } + order := acme.Order{} + err = json.Unmarshal(body, &order) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } - order := acme.Order{} - err = json.Unmarshal(body, &order) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - err = tester.WriteJSONResponse(w, acme.Order{ - Status: acme.StatusValid, - Expires: order.Expires, - Identifiers: order.Identifiers, - Profile: order.Profile, - NotBefore: order.NotBefore, - NotAfter: order.NotAfter, - Error: order.Error, - Authorizations: order.Authorizations, - Finalize: order.Finalize, - Certificate: order.Certificate, - Replaces: order.Replaces, - }) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + servermock.JSONEncode(acme.Order{ + Status: acme.StatusValid, + Expires: order.Expires, + Identifiers: order.Identifiers, + Profile: order.Profile, + NotBefore: order.NotBefore, + NotAfter: order.NotAfter, + Error: order.Error, + Authorizations: order.Authorizations, + Finalize: order.Finalize, + Certificate: order.Certificate, + Replaces: order.Replaces, + }).ServeHTTP(rw, req) + })). + Build(t) core, err := New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) diff --git a/certificate/certificates_test.go b/certificate/certificates_test.go index 3be2d606a..ddccd3541 100644 --- a/certificate/certificates_test.go +++ b/certificate/certificates_test.go @@ -3,7 +3,6 @@ package certificate import ( "crypto/rand" "crypto/rsa" - "encoding/pem" "fmt" "net/http" "testing" @@ -12,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -175,15 +175,9 @@ Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ ` func Test_checkResponse(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - - mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { - _, err := w.Write([]byte(certResponseMock)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + apiURL := tester.MockACMEServer(). + Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") @@ -215,25 +209,9 @@ func Test_checkResponse(t *testing.T) { } func Test_checkResponse_issuerRelUp(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - - mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Link", "<"+apiURL+`/issuer>; rel="up"`) - _, err := w.Write([]byte(certResponseMock)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - mux.HandleFunc("/issuer", func(w http.ResponseWriter, _ *http.Request) { - p, _ := pem.Decode([]byte(issuerMock)) - _, err := w.Write(p.Bytes) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + apiURL := tester.MockACMEServer(). + Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") @@ -265,15 +243,9 @@ func Test_checkResponse_issuerRelUp(t *testing.T) { } func Test_checkResponse_no_bundle(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - - mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { - _, err := w.Write([]byte(certResponseMock)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + apiURL := tester.MockACMEServer(). + Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") @@ -305,25 +277,16 @@ func Test_checkResponse_no_bundle(t *testing.T) { } func Test_checkResponse_alternate(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer(). + Route("POST /certificate", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Add("Link", + fmt.Sprintf(`;title="foo";rel="alternate"`, req.Context().Value(http.LocalAddrContextKey))) - mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { - w.Header().Add("Link", fmt.Sprintf(`<%s/certificate/1>;title="foo";rel="alternate"`, apiURL)) - - _, err := w.Write([]byte(certResponseMock)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - mux.HandleFunc("/certificate/1", func(w http.ResponseWriter, _ *http.Request) { - _, err := w.Write([]byte(certResponseMock2)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + servermock.RawStringResponse(certResponseMock).ServeHTTP(rw, req) + })). + Route("/certificate/1", servermock.RawStringResponse(certResponseMock2)). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") @@ -358,15 +321,9 @@ func Test_checkResponse_alternate(t *testing.T) { } func Test_Get(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - - mux.HandleFunc("/acme/cert/test-cert", func(w http.ResponseWriter, _ *http.Request) { - _, err := w.Write([]byte(certResponseMock)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + apiURL := tester.MockACMEServer(). + Route("POST /acme/cert/test-cert", servermock.RawStringResponse(certResponseMock)). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") diff --git a/certificate/renewal_test.go b/certificate/renewal_test.go index 9f20e374e..9cf478600 100644 --- a/certificate/renewal_test.go +++ b/certificate/renewal_test.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -42,26 +43,19 @@ func TestCertifier_GetRenewalInfo(t *testing.T) { require.NoError(t, err) // Test with a fake API. - mux, apiURL := tester.SetupFakeAPI(t) - mux.HandleFunc("/renewalInfo/"+ariLeafCertID, func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Retry-After", "21600") - w.WriteHeader(http.StatusOK) - _, wErr := w.Write([]byte(`{ + apiURL := tester.MockACMEServer(). + Route("GET /renewalInfo/"+ariLeafCertID, + servermock.RawStringResponse(`{ "suggestedWindow": { "start": "2020-03-17T17:51:09Z", "end": "2020-03-17T18:21:09Z" }, - "explanationUrl": "https://aricapable.ca/docs/renewal-advice/" + "explanationUrl": "https://aricapable.ca.example/docs/renewal-advice/" } - }`)) - require.NoError(t, wErr) - }) + }`). + WithHeader("Content-Type", "application/json"). + WithHeader("Retry-After", "21600")). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") @@ -76,7 +70,7 @@ func TestCertifier_GetRenewalInfo(t *testing.T) { require.NotNil(t, ri) assert.Equal(t, "2020-03-17T17:51:09Z", ri.SuggestedWindow.Start.Format(time.RFC3339)) assert.Equal(t, "2020-03-17T18:21:09Z", ri.SuggestedWindow.End.Format(time.RFC3339)) - assert.Equal(t, "https://aricapable.ca/docs/renewal-advice/", ri.ExplanationURL) + assert.Equal(t, "https://aricapable.ca.example/docs/renewal-advice/", ri.ExplanationURL) assert.Equal(t, time.Duration(21600000000000), ri.RetryAfter) } @@ -117,8 +111,9 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - mux, apiURL := tester.SetupFakeAPI(t) - mux.HandleFunc("/renewalInfo/"+ariLeafCertID, test.handler) + apiURL := tester.MockACMEServer(). + Route("GET /renewalInfo/"+ariLeafCertID, test.handler). + Build(t) core, err := api.New(test.httpClient, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) diff --git a/challenge/dns01/dns_challenge_test.go b/challenge/dns01/dns_challenge_test.go index c09273c2a..bb0564a81 100644 --- a/challenge/dns01/dns_challenge_test.go +++ b/challenge/dns01/dns_challenge_test.go @@ -32,7 +32,7 @@ func (p *providerTimeoutMock) CleanUp(domain, token, keyAuth string) error { ret func (p *providerTimeoutMock) Timeout() (time.Duration, time.Duration) { return p.timeout, p.interval } func TestChallenge_PreSolve(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) @@ -114,7 +114,7 @@ func TestChallenge_PreSolve(t *testing.T) { } func TestChallenge_Solve(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) @@ -201,7 +201,7 @@ func TestChallenge_Solve(t *testing.T) { } func TestChallenge_CleanUp(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) diff --git a/challenge/http01/http_challenge_test.go b/challenge/http01/http_challenge_test.go index c29c49068..a5e9cc62f 100644 --- a/challenge/http01/http_challenge_test.go +++ b/challenge/http01/http_challenge_test.go @@ -67,7 +67,7 @@ func TestProviderServer_GetAddress(t *testing.T) { } func TestChallenge(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) providerServer := NewProviderServer("", "23457") @@ -123,7 +123,7 @@ func TestChallengeUnix(t *testing.T) { t.Skip("only for UNIX systems") } - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) dir := t.TempDir() t.Cleanup(func() { _ = os.RemoveAll(dir) }) @@ -188,7 +188,7 @@ func TestChallengeUnix(t *testing.T) { } func TestChallengeInvalidPort(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") @@ -371,7 +371,7 @@ func TestChallengeWithProxy(t *testing.T) { func testServeWithProxy(t *testing.T, header, extra *testProxyHeader, expectError bool) { t.Helper() - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) providerServer := NewProviderServer("localhost", "23457") if header != nil { diff --git a/challenge/resolver/prober_test.go b/challenge/resolver/prober_test.go index 4ee9b1b46..06ff07d2c 100644 --- a/challenge/resolver/prober_test.go +++ b/challenge/resolver/prober_test.go @@ -26,9 +26,9 @@ func TestProber_Solve(t *testing.T) { }, }, authz: []acme.Authorization{ - createStubAuthorizationHTTP01("acme.wtf", acme.StatusProcessing), - createStubAuthorizationHTTP01("lego.wtf", acme.StatusProcessing), - createStubAuthorizationHTTP01("mydomain.wtf", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.com", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.org", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.net", acme.StatusProcessing), }, }, { @@ -41,9 +41,9 @@ func TestProber_Solve(t *testing.T) { }, }, authz: []acme.Authorization{ - createStubAuthorizationHTTP01("acme.wtf", acme.StatusValid), - createStubAuthorizationHTTP01("lego.wtf", acme.StatusValid), - createStubAuthorizationHTTP01("mydomain.wtf", acme.StatusValid), + createStubAuthorizationHTTP01("example.com", acme.StatusValid), + createStubAuthorizationHTTP01("example.org", acme.StatusValid), + createStubAuthorizationHTTP01("example.net", acme.StatusValid), }, }, { @@ -51,23 +51,23 @@ func TestProber_Solve(t *testing.T) { solvers: map[challenge.Type]solver{ challenge.HTTP01: &preSolverMock{ preSolve: map[string]error{ - "acme.wtf": errors.New("preSolve error acme.wtf"), + "example.com": errors.New("preSolve error example.com"), }, solve: map[string]error{ - "acme.wtf": errors.New("solve error acme.wtf"), + "example.com": errors.New("solve error example.com"), }, cleanUp: map[string]error{ - "acme.wtf": errors.New("clean error acme.wtf"), + "example.com": errors.New("clean error example.com"), }, }, }, authz: []acme.Authorization{ - createStubAuthorizationHTTP01("acme.wtf", acme.StatusProcessing), - createStubAuthorizationHTTP01("lego.wtf", acme.StatusProcessing), - createStubAuthorizationHTTP01("mydomain.wtf", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.com", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.org", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.net", acme.StatusProcessing), }, expectedError: `error: one or more domains had a problem: -[acme.wtf] preSolve error acme.wtf +[example.com] preSolve error example.com `, }, { @@ -75,25 +75,25 @@ func TestProber_Solve(t *testing.T) { solvers: map[challenge.Type]solver{ challenge.HTTP01: &preSolverMock{ preSolve: map[string]error{ - "acme.wtf": errors.New("preSolve error acme.wtf"), + "example.com": errors.New("preSolve error example.com"), }, solve: map[string]error{ - "acme.wtf": errors.New("solve error acme.wtf"), - "lego.wtf": errors.New("solve error lego.wtf"), + "example.com": errors.New("solve error example.com"), + "example.org": errors.New("solve error example.org"), }, cleanUp: map[string]error{ - "mydomain.wtf": errors.New("clean error mydomain.wtf"), + "example.net": errors.New("clean error example.net"), }, }, }, authz: []acme.Authorization{ - createStubAuthorizationHTTP01("acme.wtf", acme.StatusProcessing), - createStubAuthorizationHTTP01("lego.wtf", acme.StatusProcessing), - createStubAuthorizationHTTP01("mydomain.wtf", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.com", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.org", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.net", acme.StatusProcessing), }, expectedError: `error: one or more domains had a problem: -[acme.wtf] preSolve error acme.wtf -[lego.wtf] solve error lego.wtf +[example.com] preSolve error example.com +[example.org] solve error example.org `, }, } diff --git a/challenge/resolver/solver_manager_test.go b/challenge/resolver/solver_manager_test.go index b1e198d3c..a0f4630ed 100644 --- a/challenge/resolver/solver_manager_test.go +++ b/challenge/resolver/solver_manager_test.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/go-jose/go-jose/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -32,64 +33,48 @@ func TestByType(t *testing.T) { } func TestValidate(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - var statuses []string privateKey, _ := rsa.GenerateKey(rand.Reader, 1024) - mux.HandleFunc("/chlg", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } + apiURL := tester.MockACMEServer(). + Route("POST /chlg", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if err := validateNoBody(privateKey, req); err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } - if err := validateNoBody(privateKey, r); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } + rw.Header().Set("Link", + fmt.Sprintf(`; rel="up"`, req.Context().Value(http.LocalAddrContextKey))) - w.Header().Set("Link", "<"+apiURL+`/my-authz>; rel="up"`) + st := statuses[0] + statuses = statuses[1:] - st := statuses[0] - statuses = statuses[1:] + chlg := &acme.Challenge{Type: "http-01", Status: st, URL: "http://example.com/", Token: "token"} - chlg := &acme.Challenge{Type: "http-01", Status: st, URL: "http://example.com/", Token: "token"} + servermock.JSONEncode(chlg).ServeHTTP(rw, req) + })). + Route("POST /my-authz", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + st := statuses[0] + statuses = statuses[1:] - err := tester.WriteJSONResponse(w, chlg) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + authorization := acme.Authorization{ + Status: st, + Challenges: []acme.Challenge{}, + } - mux.HandleFunc("/my-authz", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } + if st == acme.StatusInvalid { + chlg := acme.Challenge{ + Status: acme.StatusInvalid, + } + authorization.Challenges = append(authorization.Challenges, chlg) + } - st := statuses[0] - statuses = statuses[1:] - - authorization := acme.Authorization{ - Status: st, - Challenges: []acme.Challenge{}, - } - - if st == acme.StatusInvalid { - chlg := acme.Challenge{ - Status: acme.StatusInvalid, - } - authorization.Challenges = append(authorization.Challenges, chlg) - } - - err := tester.WriteJSONResponse(w, authorization) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + servermock.JSONEncode(authorization).ServeHTTP(rw, req) + })). + Build(t) core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) diff --git a/challenge/tlsalpn01/tls_alpn_challenge_test.go b/challenge/tlsalpn01/tls_alpn_challenge_test.go index 9f65742f3..5e0469d8c 100644 --- a/challenge/tlsalpn01/tls_alpn_challenge_test.go +++ b/challenge/tlsalpn01/tls_alpn_challenge_test.go @@ -21,7 +21,7 @@ import ( ) func TestChallenge(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) domain := "localhost" port := "24457" @@ -93,7 +93,7 @@ func TestChallenge(t *testing.T) { } func TestChallengeInvalidPort(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") @@ -123,7 +123,7 @@ func TestChallengeInvalidPort(t *testing.T) { } func TestChallengeIPaddress(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) domain := "127.0.0.1" port := "24457" diff --git a/lego/client_test.go b/lego/client_test.go index 4f07eb1ea..8a18c47c7 100644 --- a/lego/client_test.go +++ b/lego/client_test.go @@ -13,7 +13,7 @@ import ( ) func TestNewClient(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) key, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") diff --git a/platform/tester/api.go b/platform/tester/api.go index 2084cf1bb..4ffb62a50 100644 --- a/platform/tester/api.go +++ b/platform/tester/api.go @@ -2,53 +2,36 @@ package tester import ( "encoding/json" + "fmt" "net/http" "net/http/httptest" - "testing" "github.com/go-acme/lego/v4/acme" + "github.com/go-acme/lego/v4/platform/tester/servermock" ) -// SetupFakeAPI Minimal stub ACME server for validation. -func SetupFakeAPI(t *testing.T) (*http.ServeMux, string) { - t.Helper() +// MockACMEServer Minimal stub ACME server for validation. +func MockACMEServer() *servermock.Builder[string] { + return servermock.NewBuilder( + func(server *httptest.Server) (string, error) { + return server.URL, nil + }). + Route("GET /dir", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + serverURL := fmt.Sprintf("http://%s", req.Context().Value(http.LocalAddrContextKey)) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/dir", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - err := WriteJSONResponse(w, acme.Directory{ - NewNonceURL: server.URL + "/nonce", - NewAccountURL: server.URL + "/account", - NewOrderURL: server.URL + "/newOrder", - RevokeCertURL: server.URL + "/revokeCert", - KeyChangeURL: server.URL + "/keyChange", - RenewalInfo: server.URL + "/renewalInfo", - }) - - mux.HandleFunc("/nonce", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodHead { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - w.Header().Set("Replay-Nonce", "12345") - w.Header().Set("Retry-After", "0") - }) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - return mux, server.URL + servermock.JSONEncode(acme.Directory{ + NewNonceURL: serverURL + "/nonce", + NewAccountURL: serverURL + "/account", + NewOrderURL: serverURL + "/newOrder", + RevokeCertURL: serverURL + "/revokeCert", + KeyChangeURL: serverURL + "/keyChange", + RenewalInfo: serverURL + "/renewalInfo", + }).ServeHTTP(rw, req) + })). + Route("HEAD /nonce", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("Replay-Nonce", "12345") + rw.Header().Set("Retry-After", "0") + })) } // WriteJSONResponse marshals the body as JSON and writes it to the response. diff --git a/providers/dns/constellix/internal/domains_test.go b/providers/dns/constellix/internal/domains_test.go index 2d92fb8f3..468db4613 100644 --- a/providers/dns/constellix/internal/domains_test.go +++ b/providers/dns/constellix/internal/domains_test.go @@ -30,10 +30,10 @@ func TestDomainService_GetAll(t *testing.T) { require.NoError(t, err) expected := []Domain{ - {ID: 273301, Name: "aaa.wtf", TypeID: 1, Version: 9, Status: "ACTIVE"}, - {ID: 273302, Name: "bbb.wtf", TypeID: 1, Version: 9, Status: "ACTIVE"}, - {ID: 273303, Name: "ccc.wtf", TypeID: 1, Version: 9, Status: "ACTIVE"}, - {ID: 273304, Name: "ddd.wtf", TypeID: 1, Version: 9, Status: "ACTIVE"}, + {ID: 273301, Name: "aaa.example", TypeID: 1, Version: 9, Status: "ACTIVE"}, + {ID: 273302, Name: "bbb.example", TypeID: 1, Version: 9, Status: "ACTIVE"}, + {ID: 273303, Name: "ccc.example", TypeID: 1, Version: 9, Status: "ACTIVE"}, + {ID: 273304, Name: "ddd.example", TypeID: 1, Version: 9, Status: "ACTIVE"}, } assert.Equal(t, expected, data) @@ -44,14 +44,14 @@ func TestDomainService_Search(t *testing.T) { Route("GET /v1/domains/search", servermock.ResponseFromFixture("domains-Search.json"), servermock.CheckQueryParameter().Strict(). - With("exact", "lego.wtf")). + With("exact", "example.com")). Build(t) - data, err := client.Domains.Search(t.Context(), Exact, "lego.wtf") + data, err := client.Domains.Search(t.Context(), Exact, "example.com") require.NoError(t, err) expected := []Domain{ - {ID: 273302, Name: "lego.wtf", TypeID: 1, Version: 9, Status: "ACTIVE"}, + {ID: 273302, Name: "example.com", TypeID: 1, Version: 9, Status: "ACTIVE"}, } assert.Equal(t, expected, data) diff --git a/providers/dns/constellix/internal/fixtures/domains-GetAll.json b/providers/dns/constellix/internal/fixtures/domains-GetAll.json index 5ff2ad41d..8ccb4e52c 100644 --- a/providers/dns/constellix/internal/fixtures/domains-GetAll.json +++ b/providers/dns/constellix/internal/fixtures/domains-GetAll.json @@ -1,7 +1,7 @@ [ { "id": 273301, - "name": "aaa.wtf", + "name": "aaa.example", "soa": { "primaryNameserver": "ns11.constellix.com.", "email": "dns.constellix.com.", @@ -36,7 +36,7 @@ }, { "id": 273302, - "name": "bbb.wtf", + "name": "bbb.example", "soa": { "primaryNameserver": "ns11.constellix.com.", "email": "dns.constellix.com.", @@ -71,7 +71,7 @@ }, { "id": 273303, - "name": "ccc.wtf", + "name": "ccc.example", "soa": { "primaryNameserver": "ns11.constellix.com.", "email": "dns.constellix.com.", @@ -106,7 +106,7 @@ }, { "id": 273304, - "name": "ddd.wtf", + "name": "ddd.example", "soa": { "primaryNameserver": "ns11.constellix.com.", "email": "dns.constellix.com.", diff --git a/providers/dns/constellix/internal/fixtures/domains-Search.json b/providers/dns/constellix/internal/fixtures/domains-Search.json index 5d018a39a..c33272515 100644 --- a/providers/dns/constellix/internal/fixtures/domains-Search.json +++ b/providers/dns/constellix/internal/fixtures/domains-Search.json @@ -1,7 +1,7 @@ [ { "id": 273302, - "name": "lego.wtf", + "name": "example.com", "soa": { "primaryNameserver": "ns11.constellix.com.", "email": "dns.constellix.com.", diff --git a/providers/dns/gcloud/googlecloud_test.go b/providers/dns/gcloud/googlecloud_test.go index 7fda2f8f6..61d5cc4e5 100644 --- a/providers/dns/gcloud/googlecloud_test.go +++ b/providers/dns/gcloud/googlecloud_test.go @@ -52,7 +52,7 @@ func TestNewDNSProvider(t *testing.T) { envServiceAccountFile: "", // as Travis run on GCE, we have to alter env envGoogleApplicationCredentials: "not-a-secret-file", - envMetadataHost: "http://lego.wtf", // defined here to avoid the client cache. + envMetadataHost: "http://example.com", // defined here to avoid the client cache. }, // the error message varies according to the OS used. expected: "googlecloud: unable to get Google Cloud client: google: error getting credentials using GOOGLE_APPLICATION_CREDENTIALS environment variable: ", @@ -63,7 +63,7 @@ func TestNewDNSProvider(t *testing.T) { EnvProject: "", envServiceAccountFile: "", // as Travis run on GCE, we have to alter env - envMetadataHost: "http://lego.wtf", + envMetadataHost: "http://example.com", }, expected: "googlecloud: project name missing", }, @@ -154,7 +154,7 @@ func TestPresentNoExistingRR(t *testing.T) { }, }), servermock.CheckQueryParameter().Strict(). - With("dnsName", "lego.wtf."). + With("dnsName", "example.com."). With("prettyPrint", "false"). With("alt", "json")). // findTxtRecords @@ -163,7 +163,7 @@ func TestPresentNoExistingRR(t *testing.T) { Rrsets: []*dns.ResourceRecordSet{}, }), servermock.CheckQueryParameter().Strict(). - With("name", "_acme-challenge.lego.wtf."). + With("name", "_acme-challenge.example.com."). With("type", "TXT"). With("prettyPrint", "false"). With("alt", "json")). @@ -189,7 +189,7 @@ func TestPresentNoExistingRR(t *testing.T) { With("alt", "json")). Build(t) - domain := "lego.wtf" + domain := "example.com" err := provider.Present(domain, "", "") require.NoError(t, err) @@ -205,21 +205,21 @@ func TestPresentWithExistingRR(t *testing.T) { }, }), servermock.CheckQueryParameter().Strict(). - With("dnsName", "lego.wtf."). + With("dnsName", "example.com."). With("prettyPrint", "false"). With("alt", "json")). // findTxtRecords Route("GET /dns/v1/projects/manhattan/managedZones/test/rrsets", servermock.JSONEncode(&dns.ResourceRecordSetsListResponse{ Rrsets: []*dns.ResourceRecordSet{{ - Name: "_acme-challenge.lego.wtf.", + Name: "_acme-challenge.example.com.", Rrdatas: []string{`"X7DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"`, `"huji"`}, Ttl: 120, Type: "TXT", }}, }), servermock.CheckQueryParameter().Strict(). - With("name", "_acme-challenge.lego.wtf."). + With("name", "_acme-challenge.example.com."). With("type", "TXT"). With("prettyPrint", "false"). With("alt", "json")). @@ -260,7 +260,7 @@ func TestPresentWithExistingRR(t *testing.T) { With("alt", "json")). Build(t) - domain := "lego.wtf" + domain := "example.com" err := provider.Present(domain, "", "") require.NoError(t, err) @@ -276,27 +276,27 @@ func TestPresentSkipExistingRR(t *testing.T) { }, }), servermock.CheckQueryParameter().Strict(). - With("dnsName", "lego.wtf."). + With("dnsName", "example.com."). With("prettyPrint", "false"). With("alt", "json")). // findTxtRecords Route("GET /dns/v1/projects/manhattan/managedZones/test/rrsets", servermock.JSONEncode(&dns.ResourceRecordSetsListResponse{ Rrsets: []*dns.ResourceRecordSet{{ - Name: "_acme-challenge.lego.wtf.", + Name: "_acme-challenge.example.com.", Rrdatas: []string{`"47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"`, `"X7DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"`, `"huji"`}, Ttl: 120, Type: "TXT", }}, }), servermock.CheckQueryParameter().Strict(). - With("name", "_acme-challenge.lego.wtf."). + With("name", "_acme-challenge.example.com."). With("type", "TXT"). With("prettyPrint", "false"). With("alt", "json")). Build(t) - domain := "lego.wtf" + domain := "example.com" err := provider.Present(domain, "", "") require.NoError(t, err) diff --git a/providers/dns/servercow/internal/client_test.go b/providers/dns/servercow/internal/client_test.go index 2092bf907..3733ccad1 100644 --- a/providers/dns/servercow/internal/client_test.go +++ b/providers/dns/servercow/internal/client_test.go @@ -29,10 +29,10 @@ func mockBuilder() *servermock.Builder[*Client] { func TestClient_GetRecords(t *testing.T) { client := mockBuilder(). - Route("GET /lego.wtf", servermock.ResponseFromFixture("records-01.json")). + Route("GET /example.com", servermock.ResponseFromFixture("records-01.json")). Build(t) - records, err := client.GetRecords(t.Context(), "lego.wtf") + records, err := client.GetRecords(t.Context(), "example.com") require.NoError(t, err) recordsJSON, err := json.Marshal(records) @@ -46,10 +46,10 @@ func TestClient_GetRecords(t *testing.T) { func TestClient_GetRecords_error(t *testing.T) { client := mockBuilder(). - Route("GET /lego.wtf", servermock.JSONEncode(Message{ErrorMsg: "authentication failed"})). + Route("GET /example.com", servermock.JSONEncode(Message{ErrorMsg: "authentication failed"})). Build(t) - records, err := client.GetRecords(t.Context(), "lego.wtf") + records, err := client.GetRecords(t.Context(), "example.com") require.Error(t, err) assert.Nil(t, records) @@ -57,7 +57,7 @@ func TestClient_GetRecords_error(t *testing.T) { func TestClient_CreateUpdateRecord(t *testing.T) { client := mockBuilder(). - Route("POST /lego.wtf", + Route("POST /example.com", servermock.JSONEncode(Message{Message: "ok"}), servermock.CheckRequestJSONBody(`{"name":"_acme-challenge.www","type":"TXT","ttl":30,"content":["aaa","bbb"]}`)). Build(t) @@ -69,7 +69,7 @@ func TestClient_CreateUpdateRecord(t *testing.T) { Content: Value{"aaa", "bbb"}, } - msg, err := client.CreateUpdateRecord(t.Context(), "lego.wtf", record) + msg, err := client.CreateUpdateRecord(t.Context(), "example.com", record) require.NoError(t, err) expected := &Message{Message: "ok"} @@ -78,7 +78,7 @@ func TestClient_CreateUpdateRecord(t *testing.T) { func TestClient_CreateUpdateRecord_error(t *testing.T) { client := mockBuilder(). - Route("POST /lego.wtf", + Route("POST /example.com", servermock.JSONEncode(Message{ErrorMsg: "parameter type must be cname, txt, tlsa, caa, a or aaaa"})). Build(t) @@ -86,7 +86,7 @@ func TestClient_CreateUpdateRecord_error(t *testing.T) { Name: "_acme-challenge.www", } - msg, err := client.CreateUpdateRecord(t.Context(), "lego.wtf", record) + msg, err := client.CreateUpdateRecord(t.Context(), "example.com", record) require.Error(t, err) assert.Nil(t, msg) @@ -94,7 +94,7 @@ func TestClient_CreateUpdateRecord_error(t *testing.T) { func TestClient_DeleteRecord(t *testing.T) { client := mockBuilder(). - Route("DELETE /lego.wtf", + Route("DELETE /example.com", servermock.JSONEncode(Message{Message: "ok"}), servermock.CheckRequestJSONBody(`{"name":"_acme-challenge.www","type":"TXT"}`)). Build(t) @@ -104,7 +104,7 @@ func TestClient_DeleteRecord(t *testing.T) { Type: "TXT", } - msg, err := client.DeleteRecord(t.Context(), "lego.wtf", record) + msg, err := client.DeleteRecord(t.Context(), "example.com", record) require.NoError(t, err) expected := &Message{Message: "ok"} @@ -113,7 +113,7 @@ func TestClient_DeleteRecord(t *testing.T) { func TestClient_DeleteRecord_error(t *testing.T) { client := mockBuilder(). - Route("DELETE /lego.wtf", + Route("DELETE /example.com", servermock.JSONEncode(Message{ErrorMsg: "parameter type must be cname, txt, tlsa, caa, a or aaaa"})). Build(t) @@ -121,7 +121,7 @@ func TestClient_DeleteRecord_error(t *testing.T) { Name: "_acme-challenge.www", } - msg, err := client.DeleteRecord(t.Context(), "lego.wtf", record) + msg, err := client.DeleteRecord(t.Context(), "example.com", record) require.Error(t, err) assert.Nil(t, msg) diff --git a/registration/registar_test.go b/registration/registar_test.go index 45fe33e82..0b3f88f98 100644 --- a/registration/registar_test.go +++ b/registration/registar_test.go @@ -3,29 +3,28 @@ package registration import ( "crypto/rand" "crypto/rsa" + "fmt" "net/http" "testing" "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestRegistrar_ResolveAccountByKey(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer(). + Route("/account", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("Location", + fmt.Sprintf("http://%s/account", req.Context().Value(http.LocalAddrContextKey))) - mux.HandleFunc("/account", func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Location", apiURL+"/account") - err := tester.WriteJSONResponse(w, acme.Account{ - Status: "valid", - }) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + servermock.JSONEncode(acme.Account{Status: "valid"}).ServeHTTP(rw, req) + })). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") From b4ddc1e5e2967c1fb3e59e09276d030c3e2baee4 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 28 Jul 2025 09:26:40 +0200 Subject: [PATCH 142/298] tests: use better test domains (#2603) --- .github/workflows/pr.yml | 8 -- certcrypto/crypto_test.go | 27 +++-- challenge/dns01/nameserver_test.go | 14 +-- e2e/challenges_test.go | 148 +++++++++++++++++------- e2e/dnschallenge/dns_challenges_test.go | 17 ++- e2e/fixtures/csr.cert | 16 --- e2e/fixtures/csr.raw | Bin 642 -> 0 bytes e2e/readme.md | 11 -- 8 files changed, 140 insertions(+), 101 deletions(-) delete mode 100644 e2e/fixtures/csr.cert delete mode 100644 e2e/fixtures/csr.raw diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6930ce70a..cb5966f3d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -59,14 +59,6 @@ jobs: - name: Set up a Memcached server uses: niden/actions-memcached@v7 - - name: Setup /etc/hosts - run: | - echo "127.0.0.1 acme.wtf" | sudo tee -a /etc/hosts - echo "127.0.0.1 lego.wtf" | sudo tee -a /etc/hosts - echo "127.0.0.1 acme.lego.wtf" | sudo tee -a /etc/hosts - echo "127.0.0.1 légô.wtf" | sudo tee -a /etc/hosts - echo "127.0.0.1 xn--lg-bja9b.wtf" | sudo tee -a /etc/hosts - - name: Make run: | make diff --git a/certcrypto/crypto_test.go b/certcrypto/crypto_test.go index 0b0ce8f6f..f6178fadd 100644 --- a/certcrypto/crypto_test.go +++ b/certcrypto/crypto_test.go @@ -13,6 +13,13 @@ import ( "github.com/stretchr/testify/require" ) +const ( + testDomain1 = "lego.example" + testDomain2 = "a.lego.example" + testDomain3 = "b.lego.example" + testDomain4 = "c.lego.example" +) + func TestGeneratePrivateKey(t *testing.T) { key, err := GeneratePrivateKey(RSA2048) require.NoError(t, err, "Error generating private key") @@ -39,30 +46,30 @@ func TestGenerateCSR(t *testing.T) { desc: "without SAN (nil)", privateKey: privateKey, opts: CSROptions{ - Domain: "lego.acme", + Domain: testDomain1, MustStaple: true, }, - expected: expected{len: 379}, + expected: expected{len: 382}, }, { desc: "without SAN (empty)", privateKey: privateKey, opts: CSROptions{ - Domain: "lego.acme", + Domain: testDomain1, SAN: []string{}, MustStaple: true, }, - expected: expected{len: 379}, + expected: expected{len: 382}, }, { desc: "with SAN", privateKey: privateKey, opts: CSROptions{ - Domain: "lego.acme", - SAN: []string{"a.lego.acme", "b.lego.acme", "c.lego.acme"}, + Domain: testDomain1, + SAN: []string{testDomain2, testDomain3, testDomain4}, MustStaple: true, }, - expected: expected{len: 430}, + expected: expected{len: 442}, }, { desc: "no domain", @@ -78,16 +85,16 @@ func TestGenerateCSR(t *testing.T) { privateKey: privateKey, opts: CSROptions{ Domain: "", - SAN: []string{"a.lego.acme", "b.lego.acme", "c.lego.acme"}, + SAN: []string{testDomain2, testDomain3, testDomain4}, MustStaple: true, }, - expected: expected{len: 409}, + expected: expected{len: 419}, }, { desc: "private key nil", privateKey: nil, opts: CSROptions{ - Domain: "fizz.buzz", + Domain: testDomain1, MustStaple: true, }, expected: expected{error: true}, diff --git a/challenge/dns01/nameserver_test.go b/challenge/dns01/nameserver_test.go index d5bb0c0a1..4eb7a5f15 100644 --- a/challenge/dns01/nameserver_test.go +++ b/challenge/dns01/nameserver_test.go @@ -52,7 +52,7 @@ func TestLookupNameserversErr(t *testing.T) { }{ { desc: "invalid tld", - fqdn: "_null.n0n0.", + fqdn: "example.invalid.", error: "could not find zone", }, } @@ -106,10 +106,10 @@ var findXByFqdnTestCases = []struct { }, { desc: "NXDOMAIN", - fqdn: "test.lego.zz.", - zone: "lego.zz.", + fqdn: "test.lego.invalid.", + zone: "lego.invalid.", nameservers: []string{"8.8.8.8:53"}, - expectedError: "[fqdn=test.lego.zz.] could not find the start of authority for 'test.lego.zz.' [question='zz. IN SOA', code=NXDOMAIN]", + expectedError: `[fqdn=test.lego.invalid.] could not find the start of authority for 'test.lego.invalid.' [question='invalid. IN SOA', code=NXDOMAIN]`, }, { desc: "several non existent nameservers", @@ -128,10 +128,10 @@ var findXByFqdnTestCases = []struct { }, { desc: "no nameservers", - fqdn: "test.ldez.com.", - zone: "ldez.com.", + fqdn: "test.example.com.", + zone: "example.com.", nameservers: []string{}, - expectedError: "[fqdn=test.ldez.com.] could not find the start of authority for 'test.ldez.com.': empty list of nameservers", + expectedError: "[fqdn=test.example.com.] could not find the start of authority for 'test.example.com.': empty list of nameservers", }, } diff --git a/e2e/challenges_test.go b/e2e/challenges_test.go index 59930923b..174ac2ed9 100644 --- a/e2e/challenges_test.go +++ b/e2e/challenges_test.go @@ -5,8 +5,10 @@ import ( "crypto/rand" "crypto/rsa" "crypto/x509" + "encoding/pem" "fmt" "os" + "path/filepath" "testing" "time" @@ -21,6 +23,18 @@ import ( "github.com/stretchr/testify/require" ) +const ( + testDomain1 = "acme.localhost" + testDomain2 = "lego.localhost" + testDomain3 = "acme.lego.localhost" + testDomain4 = "légô.localhost" +) + +const ( + testEmail1 = "lego@example.com" + testEmail2 = "acme@example.com" +) + var load = loader.EnvLoader{ PebbleOptions: &loader.CmdOption{ HealthCheckURL: "https://localhost:14000/dir", @@ -51,10 +65,10 @@ func TestChallengeHTTP_Run(t *testing.T) { loader.CleanLegoFiles() err := load.RunLego( - "-m", "hubert@hubert.com", + "-m", testEmail1, "--accept-tos", "-s", "https://localhost:14000/dir", - "-d", "acme.wtf", + "-d", testDomain1, "--http", "--http.port", ":5002", "run") @@ -67,10 +81,10 @@ func TestChallengeTLS_Run_Domains(t *testing.T) { loader.CleanLegoFiles() err := load.RunLego( - "-m", "hubert@hubert.com", + "-m", testEmail1, "--accept-tos", "-s", "https://localhost:14000/dir", - "-d", "acme.wtf", + "-d", testDomain1, "--tls", "--tls.port", ":5001", "run") @@ -83,7 +97,7 @@ func TestChallengeTLS_Run_IP(t *testing.T) { loader.CleanLegoFiles() err := load.RunLego( - "-m", "hubert@hubert.com", + "-m", testEmail1, "--accept-tos", "-s", "https://localhost:14000/dir", "-d", "127.0.0.1", @@ -98,11 +112,13 @@ func TestChallengeTLS_Run_IP(t *testing.T) { func TestChallengeTLS_Run_CSR(t *testing.T) { loader.CleanLegoFiles() + csrPath := createTestCSRFile(t, true) + err := load.RunLego( - "-m", "hubert@hubert.com", + "-m", testEmail1, "--accept-tos", "-s", "https://localhost:14000/dir", - "-csr", "./fixtures/csr.raw", + "-csr", csrPath, "--tls", "--tls.port", ":5001", "run") @@ -114,11 +130,13 @@ func TestChallengeTLS_Run_CSR(t *testing.T) { func TestChallengeTLS_Run_CSR_PEM(t *testing.T) { loader.CleanLegoFiles() + csrPath := createTestCSRFile(t, false) + err := load.RunLego( - "-m", "hubert@hubert.com", + "-m", testEmail1, "--accept-tos", "-s", "https://localhost:14000/dir", - "-csr", "./fixtures/csr.cert", + "-csr", csrPath, "--tls", "--tls.port", ":5001", "run") @@ -131,11 +149,11 @@ func TestChallengeTLS_Run_Revoke(t *testing.T) { loader.CleanLegoFiles() err := load.RunLego( - "-m", "hubert@hubert.com", + "-m", testEmail1, "--accept-tos", "-s", "https://localhost:14000/dir", - "-d", "lego.wtf", - "-d", "acme.lego.wtf", + "-d", testDomain2, + "-d", testDomain3, "--tls", "--tls.port", ":5001", "run") @@ -144,10 +162,10 @@ func TestChallengeTLS_Run_Revoke(t *testing.T) { } err = load.RunLego( - "-m", "hubert@hubert.com", + "-m", testEmail1, "--accept-tos", "-s", "https://localhost:14000/dir", - "-d", "lego.wtf", + "-d", testDomain2, "--tls", "--tls.port", ":5001", "revoke") @@ -160,10 +178,10 @@ func TestChallengeTLS_Run_Revoke_Non_ASCII(t *testing.T) { loader.CleanLegoFiles() err := load.RunLego( - "-m", "hubert@hubert.com", + "-m", testEmail1, "--accept-tos", "-s", "https://localhost:14000/dir", - "-d", "légô.wtf", + "-d", testDomain4, "--tls", "--tls.port", ":5001", "run") @@ -172,10 +190,10 @@ func TestChallengeTLS_Run_Revoke_Non_ASCII(t *testing.T) { } err = load.RunLego( - "-m", "hubert@hubert.com", + "-m", testEmail1, "--accept-tos", "-s", "https://localhost:14000/dir", - "-d", "légô.wtf", + "-d", testDomain4, "--tls", "--tls.port", ":5001", "revoke") @@ -207,14 +225,14 @@ func TestChallengeHTTP_Client_Obtain(t *testing.T) { user.registration = reg request := certificate.ObtainRequest{ - Domains: []string{"acme.wtf"}, + Domains: []string{testDomain1}, Bundle: true, } resource, err := client.Certificate.Obtain(request) require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, "acme.wtf", resource.Domain) + assert.Equal(t, testDomain1, resource.Domain) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -245,7 +263,7 @@ func TestChallengeHTTP_Client_Obtain_profile(t *testing.T) { user.registration = reg request := certificate.ObtainRequest{ - Domains: []string{"acme.wtf"}, + Domains: []string{testDomain1}, Bundle: true, Profile: "shortlived", } @@ -253,7 +271,7 @@ func TestChallengeHTTP_Client_Obtain_profile(t *testing.T) { require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, "acme.wtf", resource.Domain) + assert.Equal(t, testDomain1, resource.Domain) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -284,15 +302,15 @@ func TestChallengeHTTP_Client_Obtain_emails_csr(t *testing.T) { user.registration = reg request := certificate.ObtainRequest{ - Domains: []string{"acme.wtf"}, + Domains: []string{testDomain1}, Bundle: true, - EmailAddresses: []string{"foo@example.com"}, + EmailAddresses: []string{testEmail1}, } resource, err := client.Certificate.Obtain(request) require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, "acme.wtf", resource.Domain) + assert.Equal(t, testDomain1, resource.Domain) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -325,7 +343,7 @@ func TestChallengeHTTP_Client_Obtain_notBefore_notAfter(t *testing.T) { now := time.Now().UTC() request := certificate.ObtainRequest{ - Domains: []string{"acme.wtf"}, + Domains: []string{testDomain1}, NotBefore: now.Add(1 * time.Hour), NotAfter: now.Add(2 * time.Hour), Bundle: true, @@ -334,7 +352,7 @@ func TestChallengeHTTP_Client_Obtain_notBefore_notAfter(t *testing.T) { require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, "acme.wtf", resource.Domain) + assert.Equal(t, testDomain1, resource.Domain) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -406,7 +424,7 @@ func TestChallengeTLS_Client_Obtain(t *testing.T) { require.NoError(t, err, "Could not generate test key") request := certificate.ObtainRequest{ - Domains: []string{"acme.wtf"}, + Domains: []string{testDomain1}, Bundle: true, PrivateKey: privateKeyCSR, } @@ -414,7 +432,7 @@ func TestChallengeTLS_Client_Obtain(t *testing.T) { require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, "acme.wtf", resource.Domain) + assert.Equal(t, testDomain1, resource.Domain) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -444,10 +462,7 @@ func TestChallengeTLS_Client_ObtainForCSR(t *testing.T) { require.NoError(t, err) user.registration = reg - csrRaw, err := os.ReadFile("./fixtures/csr.raw") - require.NoError(t, err) - - csr, err := x509.ParseCertificateRequest(csrRaw) + csr, err := x509.ParseCertificateRequest(createTestCSR(t)) require.NoError(t, err) resource, err := client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{ @@ -457,7 +472,7 @@ func TestChallengeTLS_Client_ObtainForCSR(t *testing.T) { require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, "acme.wtf", resource.Domain) + assert.Equal(t, testDomain1, resource.Domain) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -487,10 +502,7 @@ func TestChallengeTLS_Client_ObtainForCSR_profile(t *testing.T) { require.NoError(t, err) user.registration = reg - csrRaw, err := os.ReadFile("./fixtures/csr.raw") - require.NoError(t, err) - - csr, err := x509.ParseCertificateRequest(csrRaw) + csr, err := x509.ParseCertificateRequest(createTestCSR(t)) require.NoError(t, err) resource, err := client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{ @@ -501,7 +513,7 @@ func TestChallengeTLS_Client_ObtainForCSR_profile(t *testing.T) { require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, "acme.wtf", resource.Domain) + assert.Equal(t, testDomain1, resource.Domain) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:14000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -519,7 +531,7 @@ func TestRegistrar_UpdateAccount(t *testing.T) { user := &fakeUser{ privateKey: privateKey, - email: "foo@example.com", + email: testEmail1, } config := lego.NewConfig(user) config.CADirURL = load.PebbleOptions.HealthCheckURL @@ -530,13 +542,13 @@ func TestRegistrar_UpdateAccount(t *testing.T) { regOptions := registration.RegisterOptions{TermsOfServiceAgreed: true} reg, err := client.Registration.Register(regOptions) require.NoError(t, err) - require.Equal(t, []string{"mailto:foo@example.com"}, reg.Body.Contact) + require.Equal(t, []string{"mailto:" + testEmail1}, reg.Body.Contact) user.registration = reg - user.email = "bar@example.com" + user.email = testEmail2 resource, err := client.Registration.UpdateRegistration(regOptions) require.NoError(t, err) - require.Equal(t, []string{"mailto:bar@example.com"}, resource.Body.Contact) + require.Equal(t, []string{"mailto:" + testEmail2}, resource.Body.Contact) require.Equal(t, reg.URI, resource.URI) } @@ -549,3 +561,53 @@ type fakeUser struct { func (f *fakeUser) GetEmail() string { return f.email } func (f *fakeUser) GetRegistration() *registration.Resource { return f.registration } func (f *fakeUser) GetPrivateKey() crypto.PrivateKey { return f.privateKey } + +func createTestCSRFile(t *testing.T, raw bool) string { + t.Helper() + + csr := createTestCSR(t) + + if raw { + filename := filepath.Join(t.TempDir(), "csr.raw") + + fileRaw, err := os.Create(filename) + require.NoError(t, err) + + defer fileRaw.Close() + + _, err = fileRaw.Write(csr) + require.NoError(t, err) + + return filename + } + + filename := filepath.Join(t.TempDir(), "csr.cert") + + file, err := os.Create(filename) + require.NoError(t, err) + + defer file.Close() + + _, err = file.Write(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csr})) + require.NoError(t, err) + + return filename +} + +func createTestCSR(t *testing.T) []byte { + t.Helper() + + privateKey, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(t, err) + + csr, err := certcrypto.CreateCSR(privateKey, certcrypto.CSROptions{ + Domain: testDomain1, + SAN: []string{ + testDomain1, + testDomain2, + }, + }) + require.NoError(t, err) + + return csr +} diff --git a/e2e/dnschallenge/dns_challenges_test.go b/e2e/dnschallenge/dns_challenges_test.go index 9ae07d46a..a44c353b3 100644 --- a/e2e/dnschallenge/dns_challenges_test.go +++ b/e2e/dnschallenge/dns_challenges_test.go @@ -18,6 +18,11 @@ import ( "github.com/stretchr/testify/require" ) +const ( + testDomain1 = "légo.localhost" + testDomain2 = "*.légo.localhost" +) + var load = loader.EnvLoader{ PebbleOptions: &loader.CmdOption{ HealthCheckURL: "https://localhost:15000/dir", @@ -59,8 +64,8 @@ func TestChallengeDNS_Run(t *testing.T) { "--dns.resolvers", ":8053", "--dns.disable-cp", "-s", "https://localhost:15000/dir", - "-d", "*.légo.acme", - "-d", "légo.acme", + "-d", testDomain2, + "-d", testDomain1, "run") if err != nil { t.Fatal(err) @@ -98,7 +103,7 @@ func TestChallengeDNS_Client_Obtain(t *testing.T) { require.NoError(t, err) user.registration = reg - domains := []string{"*.légo.acme", "légo.acme"} + domains := []string{testDomain2, testDomain1} // https://github.com/letsencrypt/pebble/issues/285 privateKeyCSR, err := rsa.GenerateKey(rand.Reader, 2048) @@ -113,7 +118,7 @@ func TestChallengeDNS_Client_Obtain(t *testing.T) { require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, "*.xn--lgo-bma.acme", resource.Domain) + assert.Equal(t, "*.xn--lgo-bma.localhost", resource.Domain) assert.Regexp(t, `https://localhost:15000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:15000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) @@ -152,7 +157,7 @@ func TestChallengeDNS_Client_Obtain_profile(t *testing.T) { require.NoError(t, err) user.registration = reg - domains := []string{"*.légo.acme", "légo.acme"} + domains := []string{testDomain2, testDomain1} // https://github.com/letsencrypt/pebble/issues/285 privateKeyCSR, err := rsa.GenerateKey(rand.Reader, 2048) @@ -168,7 +173,7 @@ func TestChallengeDNS_Client_Obtain_profile(t *testing.T) { require.NoError(t, err) require.NotNil(t, resource) - assert.Equal(t, "*.xn--lgo-bma.acme", resource.Domain) + assert.Equal(t, "*.xn--lgo-bma.localhost", resource.Domain) assert.Regexp(t, `https://localhost:15000/certZ/[\w\d]{14,}`, resource.CertURL) assert.Regexp(t, `https://localhost:15000/certZ/[\w\d]{14,}`, resource.CertStableURL) assert.NotEmpty(t, resource.Certificate) diff --git a/e2e/fixtures/csr.cert b/e2e/fixtures/csr.cert deleted file mode 100644 index cece7ddec..000000000 --- a/e2e/fixtures/csr.cert +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIICfjCCAWYCAQAwEzERMA8GA1UEAxMIYWNtZS53dGYwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDAhXnho1w9OPHWs4YSMahYbG4Ui1K6hsHytBZfhsz0 -09igSWzHMEFZYHZJVuSr60enuJSZRhgwDjfhQWSUgHgKItLPnlNVYM6RhVaW0WfT -w6CpmE2AuH3WuQbrR2he1Nt0xfUJla+VWOFZuW7GhgBiV5iWBvdLv6Ztgh8eATjo -2vG2R+KuSUzrm6h+sb3nUR28OYunZ3vESjNwnL3/D/1th2rFpe3EA3em1HArJdXN -F4eclciun5Js17AS9tdoHEEZMMBWyViiuz3CQlh+YD2qAvqaubanWNa+r+iijMvd -4HlDHC99LTk6TJoSKoL+E/OGKmntLqmBJ1UrCFgvnw3DAgMBAAGgJjAkBgkqhkiG -9w0BCQ4xFzAVMBMGA1UdEQQMMAqCCGFjbWUud3RmMA0GCSqGSIb3DQEBCwUAA4IB -AQAfBLR8njftxf15V49szNsgNaG7Y5UQFwgl8pyiIaanGvX1DE0BtU1RB/w7itzX -wW5W/wjielEbs1XkI2uz3hkebvHVA1QpA7bbrX01WonS18xCkiRDj8ZqFEG4vEGa -HswzGUfq2v0gCOIPpVGE+8Q2Y7In5zwEfev+5DkHox4/vgwMhyPMI+y7jKtdG/dV -U58SFnt/F1raoSmR6vfDcAFXm/L8LXEkxqqefFbhiRHRqQar1Wr15BH//swmNzEW -5SVCCHcyIqreSua8uPjBcJ8aYVLniX6DMRyYv4ij/PSvSQy9xJDewLqR235WfTd/ -tk4hhJaqizKDpsvB+UFod5o5 ------END CERTIFICATE REQUEST----- diff --git a/e2e/fixtures/csr.raw b/e2e/fixtures/csr.raw deleted file mode 100644 index f4bb701cd20b1f5134d891e74ddac8957fcca706..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 642 zcmXqLVyZJ}VoYOVWH1mm6g1#xV-96u7UoDy&P~-TFG&N+DH-sxacZ@Bw0-AgWMpJz zU~XdMX8?+GF*PwVG8|~Fe7HEq*5c!}&22)4D?Ge0agi8NLlW zYOn2Nd+nYPcjb1;(XX6S*H4Xj7`Zd=SQ|r9_>5_6-@W%Q%WaaEW3+g2>*F@}N9#O& zUe8`pw{h?DK-oQ(-OJOfk9Zju%-Q>&|8H)4*3qSJk1&@nyHcR7diAV$`<$sK*3F-k zbA5x*x9b@)j*Fh6q4NFkYr?#XW3FS&;0Gtzm?(rIcIJwm@eF%JXJuPL-o^~MT*OoOMU&yzbQahxT0##qw*)vdn@9FO>y2Dbb@VwSu~{kaWG?dyL}EZG;!+3(}wX;(g@{APF0 z>R9RTp~3To#H#DXqi!wKocQYd;R43+*`NOC7OEUuHLoV@VW;55m29i8W_^7k`2XJ- zHFHC;r>agIEK2||*BhY# diff --git a/e2e/readme.md b/e2e/readme.md index 746b9d726..7a2367c9b 100644 --- a/e2e/readme.md +++ b/e2e/readme.md @@ -1,16 +1,5 @@ # E2E tests -How to run: - -- Add the following entries to your `/etc/hosts`: -``` -127.0.0.1 acme.wtf -127.0.0.1 lego.wtf -127.0.0.1 acme.lego.wtf -127.0.0.1 légô.wtf -127.0.0.1 xn--lg-bja9b.wtf -``` - - Install [Pebble](https://github.com/letsencrypt/pebble): ```bash go install github.com/letsencrypt/pebble/v2/cmd/pebble@main From 238454b5f74f3cfcbb244ff0d0dc914a4ad44b96 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 1 Aug 2025 16:35:07 +0200 Subject: [PATCH 143/298] fix: enforce HTTPS to the ACME server (#2608) --- acme/api/certificate_test.go | 13 ++++--- .../api/internal/nonces/nonce_manager_test.go | 34 +++++++++--------- acme/api/internal/secure/jws_test.go | 34 +++++++++--------- acme/api/internal/sender/sender.go | 27 ++++++++++++++ acme/api/internal/sender/sender_test.go | 14 ++++++-- acme/api/order_test.go | 6 ++-- certificate/certificates_test.go | 32 ++++++++--------- certificate/renewal_test.go | 35 ++++++++++--------- challenge/dns01/dns_challenge_test.go | 13 ++++--- challenge/http01/http_challenge_test.go | 16 ++++----- challenge/resolver/solver_manager_test.go | 8 ++--- .../tlsalpn01/tls_alpn_challenge_test.go | 13 ++++--- lego/client_test.go | 3 +- platform/tester/api.go | 2 +- platform/tester/servermock/builder.go | 12 +++++++ registration/registar_test.go | 6 ++-- 16 files changed, 161 insertions(+), 107 deletions(-) diff --git a/acme/api/certificate_test.go b/acme/api/certificate_test.go index 4ff8fec47..f9e92dc6c 100644 --- a/acme/api/certificate_test.go +++ b/acme/api/certificate_test.go @@ -3,7 +3,6 @@ package api import ( "crypto/rand" "crypto/rsa" - "net/http" "testing" "github.com/go-acme/lego/v4/platform/tester" @@ -74,14 +73,14 @@ rzFL1KZfz+HZdnFwFW2T2gVW8L3ii1l9AJDuKzlvjUH3p6bgihVq02sjT8mx+GM2 ` func TestCertificateService_Get_issuerRelUp(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) cert, issuer, err := core.Certificates.Get(apiURL+"/certificate", true) @@ -91,14 +90,14 @@ func TestCertificateService_Get_issuerRelUp(t *testing.T) { } func TestCertificateService_Get_embeddedIssuer(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) cert, issuer, err := core.Certificates.Get(apiURL+"/certificate", true) diff --git a/acme/api/internal/nonces/nonce_manager_test.go b/acme/api/internal/nonces/nonce_manager_test.go index a172a0b69..097e57587 100644 --- a/acme/api/internal/nonces/nonce_manager_test.go +++ b/acme/api/internal/nonces/nonce_manager_test.go @@ -8,35 +8,37 @@ import ( "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api/internal/sender" - "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" ) func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - time.Sleep(250 * time.Millisecond) - w.Header().Set("Replay-Nonce", "12345") - w.Header().Set("Retry-After", "0") - err := tester.WriteJSONResponse(w, &acme.Challenge{Type: "http-01", Status: "Valid", URL: "http://example.com/", Token: "token"}) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - })) - t.Cleanup(server.Close) + manager, _ := servermock.NewBuilder( + func(server *httptest.Server) (*Manager, error) { + doer := sender.NewDoer(server.Client(), "lego-test") + + return NewManager(doer, server.URL), nil + }). + Route("HEAD /", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(250 * time.Millisecond) + + rw.Header().Set("Replay-Nonce", "12345") + rw.Header().Set("Retry-After", "0") + + servermock.JSONEncode(&acme.Challenge{Type: "http-01", Status: "Valid", URL: "https://example.com/", Token: "token"}).ServeHTTP(rw, req) + })). + BuildHTTPS(t) - doer := sender.NewDoer(http.DefaultClient, "lego-test") - j := NewManager(doer, server.URL) ch := make(chan bool) resultCh := make(chan bool) go func() { - _, errN := j.Nonce() + _, errN := manager.Nonce() if errN != nil { t.Log(errN) } ch <- true }() go func() { - _, errN := j.Nonce() + _, errN := manager.Nonce() if errN != nil { t.Log(errN) } diff --git a/acme/api/internal/secure/jws_test.go b/acme/api/internal/secure/jws_test.go index 2e625f24f..07761fb5e 100644 --- a/acme/api/internal/secure/jws_test.go +++ b/acme/api/internal/secure/jws_test.go @@ -9,35 +9,37 @@ import ( "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api/internal/nonces" "github.com/go-acme/lego/v4/acme/api/internal/sender" - "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" ) func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - time.Sleep(250 * time.Millisecond) - w.Header().Set("Replay-Nonce", "12345") - w.Header().Set("Retry-After", "0") - err := tester.WriteJSONResponse(w, &acme.Challenge{Type: "http-01", Status: "Valid", URL: "http://example.com/", Token: "token"}) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - })) - t.Cleanup(server.Close) + manager, _ := servermock.NewBuilder( + func(server *httptest.Server) (*nonces.Manager, error) { + doer := sender.NewDoer(server.Client(), "lego-test") + + return nonces.NewManager(doer, server.URL), nil + }). + Route("HEAD /", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + time.Sleep(250 * time.Millisecond) + + rw.Header().Set("Replay-Nonce", "12345") + rw.Header().Set("Retry-After", "0") + + servermock.JSONEncode(&acme.Challenge{Type: "http-01", Status: "Valid", URL: "https://example.com/", Token: "token"}).ServeHTTP(rw, req) + })). + BuildHTTPS(t) - doer := sender.NewDoer(http.DefaultClient, "lego-test") - j := nonces.NewManager(doer, server.URL) ch := make(chan bool) resultCh := make(chan bool) go func() { - _, errN := j.Nonce() + _, errN := manager.Nonce() if errN != nil { t.Log(errN) } ch <- true }() go func() { - _, errN := j.Nonce() + _, errN := manager.Nonce() if errN != nil { t.Log(errN) } diff --git a/acme/api/internal/sender/sender.go b/acme/api/internal/sender/sender.go index 2e1bbec8d..e70a4bd9c 100644 --- a/acme/api/internal/sender/sender.go +++ b/acme/api/internal/sender/sender.go @@ -27,6 +27,8 @@ type Doer struct { // NewDoer Creates a new Doer. func NewDoer(client *http.Client, userAgent string) *Doer { + client.Transport = newHTTPSOnly(client) + return &Doer{ httpClient: client, userAgent: userAgent, @@ -150,3 +152,28 @@ func checkError(req *http.Request, resp *http.Response) error { } return nil } + +type httpsOnly struct { + rt http.RoundTripper +} + +func newHTTPSOnly(client *http.Client) *httpsOnly { + if client.Transport == nil { + return &httpsOnly{rt: http.DefaultTransport} + } + + return &httpsOnly{rt: client.Transport} +} + +// RoundTrip ensure HTTPS is used. +// Each ACME function is accomplished by the client sending a sequence of HTTPS requests to the server [RFC2818], +// carrying JSON messages [RFC8259]. +// Use of HTTPS is REQUIRED. +// https://datatracker.ietf.org/doc/html/rfc8555#section-6.1 +func (r *httpsOnly) RoundTrip(req *http.Request) (*http.Response, error) { + if req.URL.Scheme != "https" { + return nil, fmt.Errorf("HTTPS is required: %s", req.URL) + } + + return r.rt.RoundTrip(req) +} diff --git a/acme/api/internal/sender/sender_test.go b/acme/api/internal/sender/sender_test.go index 2fd43c878..69bb8ae4e 100644 --- a/acme/api/internal/sender/sender_test.go +++ b/acme/api/internal/sender/sender_test.go @@ -12,13 +12,13 @@ import ( func TestDo_UserAgentOnAllHTTPMethod(t *testing.T) { var ua, method string - server := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { + server := httptest.NewTLSServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { ua = r.Header.Get("User-Agent") method = r.Method })) t.Cleanup(server.Close) - doer := NewDoer(http.DefaultClient, "") + doer := NewDoer(server.Client(), "") testCases := []struct { method string @@ -65,3 +65,13 @@ func TestDo_CustomUserAgent(t *testing.T) { } assert.Len(t, strings.Split(ua, " "), 5) } + +func TestDo_failWithHTTP(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) + t.Cleanup(server.Close) + + sender := NewDoer(server.Client(), "test") + + _, err := sender.Post(server.URL, strings.NewReader("data"), "text/plain", nil) + require.ErrorContains(t, err, "HTTPS is required: http://") +} diff --git a/acme/api/order_test.go b/acme/api/order_test.go index ebbb7d7ce..da98f4714 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -22,7 +22,7 @@ func TestOrderService_NewWithOptions(t *testing.T) { privateKey, errK := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, errK, "Could not generate test key") - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /newOrder", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { body, err := readSignedBody(req, privateKey) @@ -52,9 +52,9 @@ func TestOrderService_NewWithOptions(t *testing.T) { Replaces: order.Replaces, }).ServeHTTP(rw, req) })). - Build(t) + BuildHTTPS(t) - core, err := New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { diff --git a/certificate/certificates_test.go b/certificate/certificates_test.go index ddccd3541..a016e3b6b 100644 --- a/certificate/certificates_test.go +++ b/certificate/certificates_test.go @@ -175,14 +175,14 @@ Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ ` func Test_checkResponse(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -209,14 +209,14 @@ func Test_checkResponse(t *testing.T) { } func Test_checkResponse_issuerRelUp(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -243,14 +243,14 @@ func Test_checkResponse_issuerRelUp(t *testing.T) { } func Test_checkResponse_no_bundle(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -277,21 +277,21 @@ func Test_checkResponse_no_bundle(t *testing.T) { } func Test_checkResponse_alternate(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /certificate", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.Header().Add("Link", - fmt.Sprintf(`;title="foo";rel="alternate"`, req.Context().Value(http.LocalAddrContextKey))) + fmt.Sprintf(`;title="foo";rel="alternate"`, req.Context().Value(http.LocalAddrContextKey))) servermock.RawStringResponse(certResponseMock).ServeHTTP(rw, req) })). Route("/certificate/1", servermock.RawStringResponse(certResponseMock2)). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -321,14 +321,14 @@ func Test_checkResponse_alternate(t *testing.T) { } func Test_Get(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /acme/cert/test-cert", servermock.RawStringResponse(certResponseMock)). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) diff --git a/certificate/renewal_test.go b/certificate/renewal_test.go index 9cf478600..7280f839f 100644 --- a/certificate/renewal_test.go +++ b/certificate/renewal_test.go @@ -43,7 +43,7 @@ func TestCertifier_GetRenewalInfo(t *testing.T) { require.NoError(t, err) // Test with a fake API. - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("GET /renewalInfo/"+ariLeafCertID, servermock.RawStringResponse(`{ "suggestedWindow": { @@ -55,12 +55,12 @@ func TestCertifier_GetRenewalInfo(t *testing.T) { }`). WithHeader("Content-Type", "application/json"). WithHeader("Retry-After", "21600")). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -82,24 +82,23 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) { require.NoError(t, err, "Could not generate test key") testCases := []struct { - desc string - httpClient *http.Client - request RenewalInfoRequest - handler http.HandlerFunc + desc string + timeout time.Duration + request RenewalInfoRequest + handler http.HandlerFunc }{ { - desc: "API timeout", - httpClient: &http.Client{Timeout: 500 * time.Millisecond}, // HTTP client that times out after 500ms. - request: RenewalInfoRequest{leaf}, + desc: "API timeout", + timeout: 500 * time.Millisecond, // HTTP client that times out after 500ms. + request: RenewalInfoRequest{leaf}, handler: func(w http.ResponseWriter, r *http.Request) { // API that takes 2ms to respond. time.Sleep(2 * time.Millisecond) }, }, { - desc: "API error", - httpClient: http.DefaultClient, - request: RenewalInfoRequest{leaf}, + desc: "API error", + request: RenewalInfoRequest{leaf}, handler: func(w http.ResponseWriter, r *http.Request) { // API that responds with error instead of renewal info. http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) @@ -111,11 +110,15 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("GET /renewalInfo/"+ariLeafCertID, test.handler). - Build(t) + BuildHTTPS(t) - core, err := api.New(test.httpClient, "lego-test", apiURL+"/dir", "", key) + if test.timeout != 0 { + client.Timeout = test.timeout + } + + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) diff --git a/challenge/dns01/dns_challenge_test.go b/challenge/dns01/dns_challenge_test.go index bb0564a81..6bc88ca23 100644 --- a/challenge/dns01/dns_challenge_test.go +++ b/challenge/dns01/dns_challenge_test.go @@ -4,7 +4,6 @@ import ( "crypto/rand" "crypto/rsa" "errors" - "net/http" "testing" "time" @@ -32,12 +31,12 @@ func (p *providerTimeoutMock) CleanUp(domain, token, keyAuth string) error { ret func (p *providerTimeoutMock) Timeout() (time.Duration, time.Duration) { return p.timeout, p.interval } func TestChallenge_PreSolve(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { @@ -114,12 +113,12 @@ func TestChallenge_PreSolve(t *testing.T) { } func TestChallenge_Solve(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { @@ -201,12 +200,12 @@ func TestChallenge_Solve(t *testing.T) { } func TestChallenge_CleanUp(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { diff --git a/challenge/http01/http_challenge_test.go b/challenge/http01/http_challenge_test.go index a5e9cc62f..4693a7f47 100644 --- a/challenge/http01/http_challenge_test.go +++ b/challenge/http01/http_challenge_test.go @@ -67,7 +67,7 @@ func TestProviderServer_GetAddress(t *testing.T) { } func TestChallenge(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) providerServer := NewProviderServer("", "23457") @@ -100,7 +100,7 @@ func TestChallenge(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge(core, validate, providerServer) @@ -123,7 +123,7 @@ func TestChallengeUnix(t *testing.T) { t.Skip("only for UNIX systems") } - apiURL := tester.MockACMEServer().Build(t) + apiURL, httpsClient := tester.MockACMEServer().BuildHTTPS(t) dir := t.TempDir() t.Cleanup(func() { _ = os.RemoveAll(dir) }) @@ -169,7 +169,7 @@ func TestChallengeUnix(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(httpsClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge(core, validate, providerServer) @@ -188,12 +188,12 @@ func TestChallengeUnix(t *testing.T) { } func TestChallengeInvalidPort(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) validate := func(_ *api.Core, _ string, _ acme.Challenge) error { return nil } @@ -371,7 +371,7 @@ func TestChallengeWithProxy(t *testing.T) { func testServeWithProxy(t *testing.T, header, extra *testProxyHeader, expectError bool) { t.Helper() - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) providerServer := NewProviderServer("localhost", "23457") if header != nil { @@ -414,7 +414,7 @@ func testServeWithProxy(t *testing.T, header, extra *testProxyHeader, expectErro privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge(core, validate, providerServer) diff --git a/challenge/resolver/solver_manager_test.go b/challenge/resolver/solver_manager_test.go index a0f4630ed..8337463fd 100644 --- a/challenge/resolver/solver_manager_test.go +++ b/challenge/resolver/solver_manager_test.go @@ -37,7 +37,7 @@ func TestValidate(t *testing.T) { privateKey, _ := rsa.GenerateKey(rand.Reader, 1024) - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("POST /chlg", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { if err := validateNoBody(privateKey, req); err != nil { @@ -46,7 +46,7 @@ func TestValidate(t *testing.T) { } rw.Header().Set("Link", - fmt.Sprintf(`; rel="up"`, req.Context().Value(http.LocalAddrContextKey))) + fmt.Sprintf(`; rel="up"`, req.Context().Value(http.LocalAddrContextKey))) st := statuses[0] statuses = statuses[1:] @@ -74,9 +74,9 @@ func TestValidate(t *testing.T) { servermock.JSONEncode(authorization).ServeHTTP(rw, req) })). - Build(t) + BuildHTTPS(t) - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { diff --git a/challenge/tlsalpn01/tls_alpn_challenge_test.go b/challenge/tlsalpn01/tls_alpn_challenge_test.go index 5e0469d8c..72ccae603 100644 --- a/challenge/tlsalpn01/tls_alpn_challenge_test.go +++ b/challenge/tlsalpn01/tls_alpn_challenge_test.go @@ -8,7 +8,6 @@ import ( "crypto/tls" "encoding/asn1" "net" - "net/http" "testing" "github.com/go-acme/lego/v4/acme" @@ -21,7 +20,7 @@ import ( ) func TestChallenge(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) domain := "localhost" port := "24457" @@ -69,7 +68,7 @@ func TestChallenge(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge( @@ -93,12 +92,12 @@ func TestChallenge(t *testing.T) { } func TestChallengeInvalidPort(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge( @@ -123,7 +122,7 @@ func TestChallengeInvalidPort(t *testing.T) { } func TestChallengeIPaddress(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, client := tester.MockACMEServer().BuildHTTPS(t) domain := "127.0.0.1" port := "24457" @@ -170,7 +169,7 @@ func TestChallengeIPaddress(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge( diff --git a/lego/client_test.go b/lego/client_test.go index 8a18c47c7..4b8a38f5f 100644 --- a/lego/client_test.go +++ b/lego/client_test.go @@ -13,7 +13,7 @@ import ( ) func TestNewClient(t *testing.T) { - apiURL := tester.MockACMEServer().Build(t) + apiURL, httpsClient := tester.MockACMEServer().BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") @@ -26,6 +26,7 @@ func TestNewClient(t *testing.T) { config := NewConfig(user) config.CADirURL = apiURL + "/dir" + config.HTTPClient = httpsClient client, err := NewClient(config) require.NoError(t, err, "Could not create client") diff --git a/platform/tester/api.go b/platform/tester/api.go index 4ffb62a50..5a7c52038 100644 --- a/platform/tester/api.go +++ b/platform/tester/api.go @@ -17,7 +17,7 @@ func MockACMEServer() *servermock.Builder[string] { return server.URL, nil }). Route("GET /dir", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - serverURL := fmt.Sprintf("http://%s", req.Context().Value(http.LocalAddrContextKey)) + serverURL := fmt.Sprintf("https://%s", req.Context().Value(http.LocalAddrContextKey)) servermock.JSONEncode(acme.Directory{ NewNonceURL: serverURL + "/nonce", diff --git a/platform/tester/servermock/builder.go b/platform/tester/servermock/builder.go index e3b41e5c3..d62cec14d 100644 --- a/platform/tester/servermock/builder.go +++ b/platform/tester/servermock/builder.go @@ -70,3 +70,15 @@ func (b *Builder[T]) Build(t *testing.T) T { return client } + +func (b *Builder[T]) BuildHTTPS(t *testing.T) (T, *http.Client) { + t.Helper() + + server := httptest.NewTLSServer(b.mux) + t.Cleanup(server.Close) + + client, err := b.clientBuilder(server) + require.NoError(t, err) + + return client, server.Client() +} diff --git a/registration/registar_test.go b/registration/registar_test.go index 0b3f88f98..2074b8f0f 100644 --- a/registration/registar_test.go +++ b/registration/registar_test.go @@ -16,7 +16,7 @@ import ( ) func TestRegistrar_ResolveAccountByKey(t *testing.T) { - apiURL := tester.MockACMEServer(). + apiURL, client := tester.MockACMEServer(). Route("/account", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.Header().Set("Location", @@ -24,7 +24,7 @@ func TestRegistrar_ResolveAccountByKey(t *testing.T) { servermock.JSONEncode(acme.Account{Status: "valid"}).ServeHTTP(rw, req) })). - Build(t) + BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") @@ -35,7 +35,7 @@ func TestRegistrar_ResolveAccountByKey(t *testing.T) { privatekey: key, } - core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) registrar := NewRegistrar(core, user) From 4d2dc643640160e8d62b1c58a932506d93c88411 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 2 Aug 2025 13:07:45 +0200 Subject: [PATCH 144/298] tests: simplify fake DNS server (rfc2136) (#2609) --- providers/dns/rfc2136/rfc2136_test.go | 95 +++++++++++++++------------ 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/providers/dns/rfc2136/rfc2136_test.go b/providers/dns/rfc2136/rfc2136_test.go index 80fdc69cb..31414a4d4 100644 --- a/providers/dns/rfc2136/rfc2136_test.go +++ b/providers/dns/rfc2136/rfc2136_test.go @@ -3,7 +3,6 @@ package rfc2136 import ( "bytes" "fmt" - "net" "strings" "sync" "testing" @@ -165,12 +164,9 @@ func TestNewDNSProviderConfig(t *testing.T) { func TestCanaryLocalTestServer(t *testing.T) { dns01.ClearFqdnCache() - dns.HandleFunc("example.com.", serverHandlerHello) - defer dns.HandleRemove("example.com.") - server, addr, err := runLocalDNSTestServer(false) - require.NoError(t, err, "Failed to start test server") - defer func() { _ = server.Shutdown() }() + mux, addr := runLocalDNSTestServer(t, false) + mux.HandleFunc("example.com.", serverHandlerHello) c := new(dns.Client) m := new(dns.Msg) @@ -187,12 +183,9 @@ func TestCanaryLocalTestServer(t *testing.T) { func TestServerSuccess(t *testing.T) { dns01.ClearFqdnCache() - dns.HandleFunc(fakeZone, serverHandlerReturnSuccess) - defer dns.HandleRemove(fakeZone) - server, addr, err := runLocalDNSTestServer(false) - require.NoError(t, err, "Failed to start test server") - defer func() { _ = server.Shutdown() }() + mux, addr := runLocalDNSTestServer(t, false) + mux.HandleFunc(fakeZone, serverHandlerReturnSuccess) config := NewDefaultConfig() config.Nameserver = addr @@ -206,12 +199,9 @@ func TestServerSuccess(t *testing.T) { func TestServerError(t *testing.T) { dns01.ClearFqdnCache() - dns.HandleFunc(fakeZone, serverHandlerReturnErr) - defer dns.HandleRemove(fakeZone) - server, addr, err := runLocalDNSTestServer(false) - require.NoError(t, err, "Failed to start test server") - defer func() { _ = server.Shutdown() }() + mux, addr := runLocalDNSTestServer(t, false) + mux.HandleFunc(fakeZone, serverHandlerReturnErr) config := NewDefaultConfig() config.Nameserver = addr @@ -228,12 +218,9 @@ func TestServerError(t *testing.T) { func TestTsigClient(t *testing.T) { dns01.ClearFqdnCache() - dns.HandleFunc(fakeZone, serverHandlerReturnSuccess) - defer dns.HandleRemove(fakeZone) - server, addr, err := runLocalDNSTestServer(true) - require.NoError(t, err, "Failed to start test server") - defer func() { _ = server.Shutdown() }() + mux, addr := runLocalDNSTestServer(t, true) + mux.HandleFunc(fakeZone, serverHandlerReturnSuccess) config := NewDefaultConfig() config.Nameserver = addr @@ -251,19 +238,17 @@ func TestValidUpdatePacket(t *testing.T) { reqChan := make(chan *dns.Msg, 10) dns01.ClearFqdnCache() - dns.HandleFunc(fakeZone, serverHandlerPassBackRequest(reqChan)) - defer dns.HandleRemove(fakeZone) - server, addr, err := runLocalDNSTestServer(false) - require.NoError(t, err, "Failed to start test server") - defer func() { _ = server.Shutdown() }() + mux, addr := runLocalDNSTestServer(t, false) + mux.HandleFunc(fakeZone, serverHandlerPassBackRequest(reqChan)) txtRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN TXT %s", fakeFqdn, fakeTTL, fakeValue)) - rrs := []dns.RR{txtRR} + m := new(dns.Msg) m.SetUpdate(fakeZone) - m.RemoveRRset(rrs) - m.Insert(rrs) + m.RemoveRRset([]dns.RR{txtRR}) + m.Insert([]dns.RR{txtRR}) + expectStr := m.String() expect, err := m.Pack() @@ -294,57 +279,68 @@ func TestValidUpdatePacket(t *testing.T) { } } -func runLocalDNSTestServer(tsig bool) (*dns.Server, string, error) { - pc, err := net.ListenPacket("udp", "127.0.0.1:0") - if err != nil { - return nil, "", err - } +func runLocalDNSTestServer(t *testing.T, tsig bool) (*dns.ServeMux, string) { + t.Helper() + + mux := dns.NewServeMux() server := &dns.Server{ - PacketConn: pc, + Addr: "127.0.0.1:0", + Net: "udp", ReadTimeout: time.Hour, WriteTimeout: time.Hour, MsgAcceptFunc: func(dh dns.Header) dns.MsgAcceptAction { // bypass defaultMsgAcceptFunc to allow dynamic update (https://github.com/miekg/dns/pull/830) return dns.MsgAccept }, + Handler: mux, } + t.Cleanup(func() { + _ = server.Shutdown() + }) + if tsig { server.TsigSecret = map[string]string{fakeTsigKey: fakeTsigSecret} } waitLock := sync.Mutex{} waitLock.Lock() + server.NotifyStartedFunc = waitLock.Unlock go func() { - _ = server.ActivateAndServe() - pc.Close() + err := server.ListenAndServe() + if err != nil { + t.Log(err) + } }() waitLock.Lock() - return server, pc.LocalAddr().String(), nil + + return mux, server.PacketConn.LocalAddr().String() } func serverHandlerHello(w dns.ResponseWriter, req *dns.Msg) { m := new(dns.Msg) m.SetReply(req) + m.Extra = make([]dns.RR, 1) m.Extra[0] = &dns.TXT{ Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0}, Txt: []string{"Hello world"}, } + _ = w.WriteMsg(m) } func serverHandlerReturnSuccess(w dns.ResponseWriter, req *dns.Msg) { m := new(dns.Msg) m.SetReply(req) + if req.Opcode == dns.OpcodeQuery && req.Question[0].Qtype == dns.TypeSOA && req.Question[0].Qclass == dns.ClassINET { // Return SOA to appease findZoneByFqdn() - soaRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN SOA ns1.%s admin.%s 2016022801 28800 7200 2419200 1200", fakeZone, fakeTTL, fakeZone, fakeZone)) - m.Answer = []dns.RR{soaRR} + m.Answer = []dns.RR{fakeSOAAnswer()} } if t := req.IsTsig(); t != nil { @@ -360,6 +356,7 @@ func serverHandlerReturnSuccess(w dns.ResponseWriter, req *dns.Msg) { func serverHandlerReturnErr(w dns.ResponseWriter, req *dns.Msg) { m := new(dns.Msg) m.SetRcode(req, dns.RcodeNotZone) + _ = w.WriteMsg(m) } @@ -367,10 +364,10 @@ func serverHandlerPassBackRequest(reqChan chan *dns.Msg) func(w dns.ResponseWrit return func(w dns.ResponseWriter, req *dns.Msg) { m := new(dns.Msg) m.SetReply(req) + if req.Opcode == dns.OpcodeQuery && req.Question[0].Qtype == dns.TypeSOA && req.Question[0].Qclass == dns.ClassINET { // Return SOA to appease findZoneByFqdn() - soaRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN SOA ns1.%s admin.%s 2016022801 28800 7200 2419200 1200", fakeZone, fakeTTL, fakeZone, fakeZone)) - m.Answer = []dns.RR{soaRR} + m.Answer = []dns.RR{fakeSOAAnswer()} } if t := req.IsTsig(); t != nil { @@ -381,9 +378,23 @@ func serverHandlerPassBackRequest(reqChan chan *dns.Msg) func(w dns.ResponseWrit } _ = w.WriteMsg(m) + if req.Opcode != dns.OpcodeQuery || req.Question[0].Qtype != dns.TypeSOA || req.Question[0].Qclass != dns.ClassINET { // Only talk back when it is not the SOA RR. reqChan <- req } } } + +func fakeSOAAnswer() *dns.SOA { + return &dns.SOA{ + Hdr: dns.RR_Header{Name: fakeZone, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: fakeTTL}, + Ns: "ns1." + fakeZone, + Mbox: "admin." + fakeZone, + Serial: 2016022801, + Refresh: 28800, + Retry: 7200, + Expire: 2419200, + Minttl: 1200, + } +} From c9157f756e054457a8fa63870560fdedee276caa Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 2 Aug 2025 13:41:35 +0200 Subject: [PATCH 145/298] chore: clean up (#2610) --- challenge/dns01/fqdn.go | 8 ++--- challenge/dns01/fqdn_test.go | 28 ---------------- providers/dns/auroradns/auroradns.go | 3 +- providers/dns/gandi/gandi_test.go | 1 - providers/dns/gcloud/googlecloud.go | 33 ++++++++++--------- providers/dns/otc/internal/client_test.go | 5 +-- .../zones-recordsets_POST-request.json | 9 +++++ providers/dns/otc/otc_test.go | 5 +-- providers/dns/safedns/safedns.go | 3 +- providers/dns/selectelv2/selectelv2.go | 5 +-- providers/dns/yandex360/yandex360.go | 5 +-- 11 files changed, 45 insertions(+), 60 deletions(-) create mode 100644 providers/dns/otc/internal/fixtures/zones-recordsets_POST-request.json diff --git a/challenge/dns01/fqdn.go b/challenge/dns01/fqdn.go index 3b94a491a..665804d8f 100644 --- a/challenge/dns01/fqdn.go +++ b/challenge/dns01/fqdn.go @@ -7,12 +7,10 @@ import ( ) // ToFqdn converts the name into a fqdn appending a trailing dot. +// +// Deprecated: Use [github.com/miekg/dns.Fqdn] directly. func ToFqdn(name string) string { - n := len(name) - if n == 0 || name[n-1] == '.' { - return name - } - return name + "." + return dns.Fqdn(name) } // UnFqdn converts the fqdn into a name removing the trailing dot. diff --git a/challenge/dns01/fqdn_test.go b/challenge/dns01/fqdn_test.go index 7a6506d4e..641e39081 100644 --- a/challenge/dns01/fqdn_test.go +++ b/challenge/dns01/fqdn_test.go @@ -7,34 +7,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestToFqdn(t *testing.T) { - testCases := []struct { - desc string - domain string - expected string - }{ - { - desc: "simple", - domain: "foo.example.com", - expected: "foo.example.com.", - }, - { - desc: "already FQDN", - domain: "foo.example.com.", - expected: "foo.example.com.", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - fqdn := ToFqdn(test.domain) - assert.Equal(t, test.expected, fqdn) - }) - } -} - func TestUnFqdn(t *testing.T) { testCases := []struct { desc string diff --git a/providers/dns/auroradns/auroradns.go b/providers/dns/auroradns/auroradns.go index 8a497ffa4..d41b271ed 100644 --- a/providers/dns/auroradns/auroradns.go +++ b/providers/dns/auroradns/auroradns.go @@ -10,6 +10,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/miekg/dns" "github.com/nrdcg/auroradns" ) @@ -161,7 +162,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("aurora: unknown recordID for %q", info.EffectiveFQDN) } - authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(info.EffectiveFQDN)) + authZone, err := dns01.FindZoneByFqdn(dns.Fqdn(info.EffectiveFQDN)) if err != nil { return fmt.Errorf("aurora: could not find zone for domain %q: %w", domain, err) } diff --git a/providers/dns/gandi/gandi_test.go b/providers/dns/gandi/gandi_test.go index 4c37fb00e..6ed38c63c 100644 --- a/providers/dns/gandi/gandi_test.go +++ b/providers/dns/gandi/gandi_test.go @@ -145,7 +145,6 @@ func TestDNSProvider(t *testing.T) { _, errS = io.Copy(rw, strings.NewReader(resp)) require.NoError(t, errS) })). - Route("/", servermock.DumpRequest()). Build(t) fakeKeyAuth := "XXXX" diff --git a/providers/dns/gcloud/googlecloud.go b/providers/dns/gcloud/googlecloud.go index 30a028d61..94cc3df1e 100644 --- a/providers/dns/gcloud/googlecloud.go +++ b/providers/dns/gcloud/googlecloud.go @@ -16,10 +16,11 @@ import ( "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" + "github.com/miekg/dns" "golang.org/x/net/context" "golang.org/x/oauth2" "golang.org/x/oauth2/google" - "google.golang.org/api/dns/v1" + gdns "google.golang.org/api/dns/v1" "google.golang.org/api/googleapi" "google.golang.org/api/impersonate" "google.golang.org/api/option" @@ -74,7 +75,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { config *Config - client *dns.Service + client *gdns.Service } // NewDNSProvider returns a DNSProvider instance configured for Google Cloud DNS. @@ -170,7 +171,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("googlecloud: unable to create Google Cloud DNS service: client is nil") } - svc, err := dns.NewService(context.Background(), option.WithHTTPClient(config.HTTPClient)) + svc, err := gdns.NewService(context.Background(), option.WithHTTPClient(config.HTTPClient)) if err != nil { return nil, fmt.Errorf("googlecloud: unable to create Google Cloud DNS service: %w", err) } @@ -209,12 +210,12 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // Attempt to delete the existing records before adding the new one. if len(existingRrSet) > 0 { - if err = d.applyChanges(zone, &dns.Change{Deletions: existingRrSet}); err != nil { + if err = d.applyChanges(zone, &gdns.Change{Deletions: existingRrSet}); err != nil { return fmt.Errorf("googlecloud: %w", err) } } - rec := &dns.ResourceRecordSet{ + rec := &gdns.ResourceRecordSet{ Name: info.EffectiveFQDN, Rrdatas: []string{info.Value}, Ttl: int64(d.config.TTL), @@ -230,8 +231,8 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { } } - change := &dns.Change{ - Additions: []*dns.ResourceRecordSet{rec}, + change := &gdns.Change{ + Additions: []*gdns.ResourceRecordSet{rec}, } if err = d.applyChanges(zone, change); err != nil { @@ -241,7 +242,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return nil } -func (d *DNSProvider) applyChanges(zone string, change *dns.Change) error { +func (d *DNSProvider) applyChanges(zone string, change *gdns.Change) error { if d.config.Debug { data, _ := json.Marshal(change) log.Printf("change (Create): %s", string(data)) @@ -303,7 +304,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } - _, err = d.client.Changes.Create(d.config.Project, zone, &dns.Change{Deletions: records}).Do() + _, err = d.client.Changes.Create(d.config.Project, zone, &gdns.Change{Deletions: records}).Do() if err != nil { return fmt.Errorf("googlecloud: %w", err) } @@ -352,7 +353,7 @@ func (d *DNSProvider) getHostedZone(domain string) (string, error) { // (gcloud projects get-iam-policy $project_id) (a role with permission dns.managedZones.list) // // If we force a zone list to succeed, we demand more permissions than needed. -func (d *DNSProvider) lookupHostedZoneID(domain string) (string, []*dns.ManagedZone, error) { +func (d *DNSProvider) lookupHostedZoneID(domain string) (string, []*gdns.ManagedZone, error) { // GCE_ZONE_ID override for service accounts to avoid needing zones-list permission if d.config.ZoneID != "" { zone, err := d.client.ManagedZones.Get(d.config.Project, d.config.ZoneID).Do() @@ -360,10 +361,10 @@ func (d *DNSProvider) lookupHostedZoneID(domain string) (string, []*dns.ManagedZ return "", nil, fmt.Errorf("API call ManagedZones.Get for explicit zone ID %q in project %q failed: %w", d.config.ZoneID, d.config.Project, err) } - return zone.DnsName, []*dns.ManagedZone{zone}, nil + return zone.DnsName, []*gdns.ManagedZone{zone}, nil } - authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain)) + authZone, err := dns01.FindZoneByFqdn(dns.Fqdn(domain)) if err != nil { return "", nil, fmt.Errorf("could not find zone: %w", err) } @@ -379,7 +380,7 @@ func (d *DNSProvider) lookupHostedZoneID(domain string) (string, []*dns.ManagedZ return authZone, zones.ManagedZones, nil } -func (d *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSet, error) { +func (d *DNSProvider) findTxtRecords(zone, fqdn string) ([]*gdns.ResourceRecordSet, error) { recs, err := d.client.ResourceRecordSets.List(d.config.Project, zone).Name(fqdn).Type("TXT").Do() if err != nil { return nil, err @@ -398,7 +399,7 @@ func newClientFromCredentials(ctx context.Context, config *Config) (*http.Client return newImpersonateClient(ctx, config.ImpersonateServiceAccount, ts) } - client, err := google.DefaultClient(ctx, dns.NdevClouddnsReadwriteScope) + client, err := google.DefaultClient(ctx, gdns.NdevClouddnsReadwriteScope) if err != nil { return nil, fmt.Errorf("unable to get Google Cloud client: %w", err) } @@ -416,7 +417,7 @@ func newClientFromServiceAccountKey(ctx context.Context, config *Config, saKey [ return newImpersonateClient(ctx, config.ImpersonateServiceAccount, conf.TokenSource(ctx)) } - conf, err := google.JWTConfigFromJSON(saKey, dns.NdevClouddnsReadwriteScope) + conf, err := google.JWTConfigFromJSON(saKey, gdns.NdevClouddnsReadwriteScope) if err != nil { return nil, fmt.Errorf("unable to acquire config: %w", err) } @@ -427,7 +428,7 @@ func newClientFromServiceAccountKey(ctx context.Context, config *Config, saKey [ func newImpersonateClient(ctx context.Context, impersonateServiceAccount string, ts oauth2.TokenSource) (*http.Client, error) { impersonatedTS, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{ TargetPrincipal: impersonateServiceAccount, - Scopes: []string{dns.NdevClouddnsReadwriteScope}, + Scopes: []string{gdns.NdevClouddnsReadwriteScope}, }, option.WithTokenSource(ts)) if err != nil { return nil, fmt.Errorf("unable to create impersonated credentials: %w", err) diff --git a/providers/dns/otc/internal/client_test.go b/providers/dns/otc/internal/client_test.go index ea3835a56..a69cd1d45 100644 --- a/providers/dns/otc/internal/client_test.go +++ b/providers/dns/otc/internal/client_test.go @@ -84,7 +84,8 @@ func TestClient_GetRecordSetID_error(t *testing.T) { func TestClient_CreateRecordSet(t *testing.T) { client := mockBuilder(). Route("POST /zones/123123/recordsets", - servermock.ResponseFromFixture("zones-recordsets_POST.json")). + servermock.ResponseFromFixture("zones-recordsets_POST.json"), + servermock.CheckRequestJSONBodyFromFixture("zones-recordsets_POST-request.json")). Build(t) rs := RecordSets{ @@ -92,7 +93,7 @@ func TestClient_CreateRecordSet(t *testing.T) { Description: "Added TXT record for ACME dns-01 challenge using lego client", Type: "TXT", TTL: 300, - Records: []string{strconv.Quote("w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI")}, + Records: []string{strconv.Quote("ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY")}, } err := client.CreateRecordSet(context.Background(), "123123", rs) require.NoError(t, err) diff --git a/providers/dns/otc/internal/fixtures/zones-recordsets_POST-request.json b/providers/dns/otc/internal/fixtures/zones-recordsets_POST-request.json new file mode 100644 index 000000000..41cab72a8 --- /dev/null +++ b/providers/dns/otc/internal/fixtures/zones-recordsets_POST-request.json @@ -0,0 +1,9 @@ +{ + "name": "_acme-challenge.example.com.", + "description": "Added TXT record for ACME dns-01 challenge using lego client", + "type": "TXT", + "ttl": 300, + "records": [ + "\"ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY\"" + ] +} diff --git a/providers/dns/otc/otc_test.go b/providers/dns/otc/otc_test.go index c8b32bc78..2174726c5 100644 --- a/providers/dns/otc/otc_test.go +++ b/providers/dns/otc/otc_test.go @@ -218,7 +218,9 @@ func TestDNSProvider_Present(t *testing.T) { servermock.ResponseFromInternal("zones_GET.json"), servermock.CheckQueryParameter().Strict(). With("name", "example.com.")). - Route("/", servermock.DumpRequest()). + Route("POST /v2/zones/123123/recordsets", + servermock.Noop(), + servermock.CheckRequestJSONBodyFromInternal("zones-recordsets_POST-request.json")). Build(t) err := provider.Present("example.com", "", "123d==") @@ -231,7 +233,6 @@ func TestDNSProvider_Present_emptyZone(t *testing.T) { servermock.ResponseFromInternal("zones_GET_empty.json"), servermock.CheckQueryParameter().Strict(). With("name", "example.com.")). - Route("/", servermock.DumpRequest()). Build(t) err := provider.Present("example.com", "", "123d==") diff --git a/providers/dns/safedns/safedns.go b/providers/dns/safedns/safedns.go index 5066db59f..d979108a6 100644 --- a/providers/dns/safedns/safedns.go +++ b/providers/dns/safedns/safedns.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/safedns/internal" + "github.com/miekg/dns" ) // Environment variables. @@ -106,7 +107,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - zone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(info.EffectiveFQDN)) + zone, err := dns01.FindZoneByFqdn(dns.Fqdn(info.EffectiveFQDN)) if err != nil { return fmt.Errorf("safedns: could not find zone for domain %q: %w", domain, err) } diff --git a/providers/dns/selectelv2/selectelv2.go b/providers/dns/selectelv2/selectelv2.go index ca0a9107d..1b5d55394 100644 --- a/providers/dns/selectelv2/selectelv2.go +++ b/providers/dns/selectelv2/selectelv2.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" + "github.com/miekg/dns" selectelapi "github.com/selectel/domains-go/pkg/v2" "github.com/selectel/go-selvpcclient/v4/selvpcclient" "golang.org/x/net/idna" @@ -266,7 +267,7 @@ func (w *clientWrapper) getZone(ctx context.Context, name string) (*selectelapi. } for _, zone := range zones.GetItems() { - if zone.Name == dns01.ToFqdn(unicodeName) { + if zone.Name == dns.Fqdn(unicodeName) { return zone, nil } } @@ -295,7 +296,7 @@ func (w *clientWrapper) getRRset(ctx context.Context, name, zoneID string) (*sel } for _, rrset := range resp.GetItems() { - if rrset.Name == dns01.ToFqdn(unicodeName) { + if rrset.Name == dns.Fqdn(unicodeName) { return rrset, nil } } diff --git a/providers/dns/yandex360/yandex360.go b/providers/dns/yandex360/yandex360.go index e2ee7beb2..aa749cf8f 100644 --- a/providers/dns/yandex360/yandex360.go +++ b/providers/dns/yandex360/yandex360.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/yandex360/internal" + "github.com/miekg/dns" ) // Environment variables names. @@ -108,7 +109,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(info.EffectiveFQDN)) + authZone, err := dns01.FindZoneByFqdn(dns.Fqdn(info.EffectiveFQDN)) if err != nil { return fmt.Errorf("yandex360: could not find zone for domain %q: %w", domain, err) } @@ -143,7 +144,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(info.EffectiveFQDN)) + authZone, err := dns01.FindZoneByFqdn(dns.Fqdn(info.EffectiveFQDN)) if err != nil { return fmt.Errorf("yandex360: could not find zone for domain %q: %w", domain, err) } From 756d5ade0ec53a78e0d189fb89f455c610e918aa Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 4 Aug 2025 12:21:14 +0200 Subject: [PATCH 146/298] tests: change the signature of the method BuildHTTPS (#2611) --- acme/api/certificate_test.go | 12 +++---- .../api/internal/nonces/nonce_manager_test.go | 2 +- acme/api/internal/secure/jws_test.go | 2 +- acme/api/order_test.go | 4 +-- certificate/certificates_test.go | 34 +++++++++---------- certificate/renewal_test.go | 10 +++--- challenge/dns01/dns_challenge_test.go | 12 +++---- challenge/http01/http_challenge_test.go | 16 ++++----- challenge/resolver/solver_manager_test.go | 6 ++-- .../tlsalpn01/tls_alpn_challenge_test.go | 12 +++---- lego/client_test.go | 6 ++-- platform/tester/api.go | 6 ++-- platform/tester/servermock/builder.go | 4 +-- registration/registar_test.go | 4 +-- 14 files changed, 66 insertions(+), 64 deletions(-) diff --git a/acme/api/certificate_test.go b/acme/api/certificate_test.go index f9e92dc6c..7220ca1b9 100644 --- a/acme/api/certificate_test.go +++ b/acme/api/certificate_test.go @@ -73,34 +73,34 @@ rzFL1KZfz+HZdnFwFW2T2gVW8L3ii1l9AJDuKzlvjUH3p6bgihVq02sjT8mx+GM2 ` func TestCertificateService_Get_issuerRelUp(t *testing.T) { - apiURL, client := tester.MockACMEServer(). + server := tester.MockACMEServer(). Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := New(client, "lego-test", apiURL+"/dir", "", key) + core, err := New(server.Client(), "lego-test", server.URL+"/dir", "", key) require.NoError(t, err) - cert, issuer, err := core.Certificates.Get(apiURL+"/certificate", true) + cert, issuer, err := core.Certificates.Get(server.URL+"/certificate", true) require.NoError(t, err) assert.Equal(t, certResponseMock, string(cert), "Certificate") assert.Equal(t, issuerMock, string(issuer), "IssuerCertificate") } func TestCertificateService_Get_embeddedIssuer(t *testing.T) { - apiURL, client := tester.MockACMEServer(). + server := tester.MockACMEServer(). Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := New(client, "lego-test", apiURL+"/dir", "", key) + core, err := New(server.Client(), "lego-test", server.URL+"/dir", "", key) require.NoError(t, err) - cert, issuer, err := core.Certificates.Get(apiURL+"/certificate", true) + cert, issuer, err := core.Certificates.Get(server.URL+"/certificate", true) require.NoError(t, err) assert.Equal(t, certResponseMock, string(cert), "Certificate") assert.Equal(t, issuerMock, string(issuer), "IssuerCertificate") diff --git a/acme/api/internal/nonces/nonce_manager_test.go b/acme/api/internal/nonces/nonce_manager_test.go index 097e57587..abaff02f5 100644 --- a/acme/api/internal/nonces/nonce_manager_test.go +++ b/acme/api/internal/nonces/nonce_manager_test.go @@ -12,7 +12,7 @@ import ( ) func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) { - manager, _ := servermock.NewBuilder( + manager := servermock.NewBuilder( func(server *httptest.Server) (*Manager, error) { doer := sender.NewDoer(server.Client(), "lego-test") diff --git a/acme/api/internal/secure/jws_test.go b/acme/api/internal/secure/jws_test.go index 07761fb5e..6a9dc5459 100644 --- a/acme/api/internal/secure/jws_test.go +++ b/acme/api/internal/secure/jws_test.go @@ -13,7 +13,7 @@ import ( ) func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) { - manager, _ := servermock.NewBuilder( + manager := servermock.NewBuilder( func(server *httptest.Server) (*nonces.Manager, error) { doer := sender.NewDoer(server.Client(), "lego-test") diff --git a/acme/api/order_test.go b/acme/api/order_test.go index da98f4714..e2fcbdf2c 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -22,7 +22,7 @@ func TestOrderService_NewWithOptions(t *testing.T) { privateKey, errK := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, errK, "Could not generate test key") - apiURL, client := tester.MockACMEServer(). + server := tester.MockACMEServer(). Route("POST /newOrder", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { body, err := readSignedBody(req, privateKey) @@ -54,7 +54,7 @@ func TestOrderService_NewWithOptions(t *testing.T) { })). BuildHTTPS(t) - core, err := New(client, "lego-test", apiURL+"/dir", "", privateKey) + core, err := New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { diff --git a/certificate/certificates_test.go b/certificate/certificates_test.go index a016e3b6b..c0e35e795 100644 --- a/certificate/certificates_test.go +++ b/certificate/certificates_test.go @@ -175,14 +175,14 @@ Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ ` func Test_checkResponse(t *testing.T) { - apiURL, client := tester.MockACMEServer(). + server := tester.MockACMEServer(). Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -190,7 +190,7 @@ func Test_checkResponse(t *testing.T) { order := acme.ExtendedOrder{ Order: acme.Order{ Status: acme.StatusValid, - Certificate: apiURL + "/certificate", + Certificate: server.URL + "/certificate", }, } certRes := &Resource{} @@ -209,14 +209,14 @@ func Test_checkResponse(t *testing.T) { } func Test_checkResponse_issuerRelUp(t *testing.T) { - apiURL, client := tester.MockACMEServer(). + server := tester.MockACMEServer(). Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -224,7 +224,7 @@ func Test_checkResponse_issuerRelUp(t *testing.T) { order := acme.ExtendedOrder{ Order: acme.Order{ Status: acme.StatusValid, - Certificate: apiURL + "/certificate", + Certificate: server.URL + "/certificate", }, } certRes := &Resource{} @@ -243,14 +243,14 @@ func Test_checkResponse_issuerRelUp(t *testing.T) { } func Test_checkResponse_no_bundle(t *testing.T) { - apiURL, client := tester.MockACMEServer(). + server := tester.MockACMEServer(). Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -258,7 +258,7 @@ func Test_checkResponse_no_bundle(t *testing.T) { order := acme.ExtendedOrder{ Order: acme.Order{ Status: acme.StatusValid, - Certificate: apiURL + "/certificate", + Certificate: server.URL + "/certificate", }, } certRes := &Resource{} @@ -277,7 +277,7 @@ func Test_checkResponse_no_bundle(t *testing.T) { } func Test_checkResponse_alternate(t *testing.T) { - apiURL, client := tester.MockACMEServer(). + server := tester.MockACMEServer(). Route("POST /certificate", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.Header().Add("Link", @@ -291,7 +291,7 @@ func Test_checkResponse_alternate(t *testing.T) { key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -299,7 +299,7 @@ func Test_checkResponse_alternate(t *testing.T) { order := acme.ExtendedOrder{ Order: acme.Order{ Status: acme.StatusValid, - Certificate: apiURL + "/certificate", + Certificate: server.URL + "/certificate", }, } certRes := &Resource{ @@ -321,25 +321,25 @@ func Test_checkResponse_alternate(t *testing.T) { } func Test_Get(t *testing.T) { - apiURL, client := tester.MockACMEServer(). + server := tester.MockACMEServer(). Route("POST /acme/cert/test-cert", servermock.RawStringResponse(certResponseMock)). BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) - certRes, err := certifier.Get(apiURL+"/acme/cert/test-cert", true) + certRes, err := certifier.Get(server.URL+"/acme/cert/test-cert", true) require.NoError(t, err) assert.NotNil(t, certRes) assert.Equal(t, "acme.wtf", certRes.Domain) - assert.Equal(t, apiURL+"/acme/cert/test-cert", certRes.CertStableURL) - assert.Equal(t, apiURL+"/acme/cert/test-cert", certRes.CertURL) + assert.Equal(t, server.URL+"/acme/cert/test-cert", certRes.CertStableURL) + assert.Equal(t, server.URL+"/acme/cert/test-cert", certRes.CertURL) assert.Nil(t, certRes.CSR) assert.Nil(t, certRes.PrivateKey) assert.Equal(t, certResponseMock, string(certRes.Certificate), "Certificate") diff --git a/certificate/renewal_test.go b/certificate/renewal_test.go index 7280f839f..6ce43e0aa 100644 --- a/certificate/renewal_test.go +++ b/certificate/renewal_test.go @@ -43,7 +43,7 @@ func TestCertifier_GetRenewalInfo(t *testing.T) { require.NoError(t, err) // Test with a fake API. - apiURL, client := tester.MockACMEServer(). + server := tester.MockACMEServer(). Route("GET /renewalInfo/"+ariLeafCertID, servermock.RawStringResponse(`{ "suggestedWindow": { @@ -60,7 +60,7 @@ func TestCertifier_GetRenewalInfo(t *testing.T) { key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") - core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) @@ -110,15 +110,17 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - apiURL, client := tester.MockACMEServer(). + server := tester.MockACMEServer(). Route("GET /renewalInfo/"+ariLeafCertID, test.handler). BuildHTTPS(t) + client := server.Client() + if test.timeout != 0 { client.Timeout = test.timeout } - core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(client, "lego-test", server.URL+"/dir", "", key) require.NoError(t, err) certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) diff --git a/challenge/dns01/dns_challenge_test.go b/challenge/dns01/dns_challenge_test.go index 6bc88ca23..48bd9986c 100644 --- a/challenge/dns01/dns_challenge_test.go +++ b/challenge/dns01/dns_challenge_test.go @@ -31,12 +31,12 @@ func (p *providerTimeoutMock) CleanUp(domain, token, keyAuth string) error { ret func (p *providerTimeoutMock) Timeout() (time.Duration, time.Duration) { return p.timeout, p.interval } func TestChallenge_PreSolve(t *testing.T) { - apiURL, client := tester.MockACMEServer().BuildHTTPS(t) + server := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) - core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { @@ -113,12 +113,12 @@ func TestChallenge_PreSolve(t *testing.T) { } func TestChallenge_Solve(t *testing.T) { - apiURL, client := tester.MockACMEServer().BuildHTTPS(t) + server := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) - core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { @@ -200,12 +200,12 @@ func TestChallenge_Solve(t *testing.T) { } func TestChallenge_CleanUp(t *testing.T) { - apiURL, client := tester.MockACMEServer().BuildHTTPS(t) + server := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) - core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { diff --git a/challenge/http01/http_challenge_test.go b/challenge/http01/http_challenge_test.go index 4693a7f47..1b322491a 100644 --- a/challenge/http01/http_challenge_test.go +++ b/challenge/http01/http_challenge_test.go @@ -67,7 +67,7 @@ func TestProviderServer_GetAddress(t *testing.T) { } func TestChallenge(t *testing.T) { - apiURL, client := tester.MockACMEServer().BuildHTTPS(t) + server := tester.MockACMEServer().BuildHTTPS(t) providerServer := NewProviderServer("", "23457") @@ -100,7 +100,7 @@ func TestChallenge(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge(core, validate, providerServer) @@ -123,7 +123,7 @@ func TestChallengeUnix(t *testing.T) { t.Skip("only for UNIX systems") } - apiURL, httpsClient := tester.MockACMEServer().BuildHTTPS(t) + server := tester.MockACMEServer().BuildHTTPS(t) dir := t.TempDir() t.Cleanup(func() { _ = os.RemoveAll(dir) }) @@ -169,7 +169,7 @@ func TestChallengeUnix(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(httpsClient, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge(core, validate, providerServer) @@ -188,12 +188,12 @@ func TestChallengeUnix(t *testing.T) { } func TestChallengeInvalidPort(t *testing.T) { - apiURL, client := tester.MockACMEServer().BuildHTTPS(t) + server := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) require.NoError(t, err) validate := func(_ *api.Core, _ string, _ acme.Challenge) error { return nil } @@ -371,7 +371,7 @@ func TestChallengeWithProxy(t *testing.T) { func testServeWithProxy(t *testing.T, header, extra *testProxyHeader, expectError bool) { t.Helper() - apiURL, client := tester.MockACMEServer().BuildHTTPS(t) + server := tester.MockACMEServer().BuildHTTPS(t) providerServer := NewProviderServer("localhost", "23457") if header != nil { @@ -414,7 +414,7 @@ func testServeWithProxy(t *testing.T, header, extra *testProxyHeader, expectErro privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge(core, validate, providerServer) diff --git a/challenge/resolver/solver_manager_test.go b/challenge/resolver/solver_manager_test.go index 8337463fd..f2875912a 100644 --- a/challenge/resolver/solver_manager_test.go +++ b/challenge/resolver/solver_manager_test.go @@ -37,7 +37,7 @@ func TestValidate(t *testing.T) { privateKey, _ := rsa.GenerateKey(rand.Reader, 1024) - apiURL, client := tester.MockACMEServer(). + server := tester.MockACMEServer(). Route("POST /chlg", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { if err := validateNoBody(privateKey, req); err != nil { @@ -76,7 +76,7 @@ func TestValidate(t *testing.T) { })). BuildHTTPS(t) - core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) require.NoError(t, err) testCases := []struct { @@ -118,7 +118,7 @@ func TestValidate(t *testing.T) { t.Run(test.name, func(t *testing.T) { statuses = test.statuses - err := validate(core, "example.com", acme.Challenge{Type: "http-01", Token: "token", URL: apiURL + "/chlg"}) + err := validate(core, "example.com", acme.Challenge{Type: "http-01", Token: "token", URL: server.URL + "/chlg"}) if test.want == "" { require.NoError(t, err) } else { diff --git a/challenge/tlsalpn01/tls_alpn_challenge_test.go b/challenge/tlsalpn01/tls_alpn_challenge_test.go index 72ccae603..eec2cb152 100644 --- a/challenge/tlsalpn01/tls_alpn_challenge_test.go +++ b/challenge/tlsalpn01/tls_alpn_challenge_test.go @@ -20,7 +20,7 @@ import ( ) func TestChallenge(t *testing.T) { - apiURL, client := tester.MockACMEServer().BuildHTTPS(t) + server := tester.MockACMEServer().BuildHTTPS(t) domain := "localhost" port := "24457" @@ -68,7 +68,7 @@ func TestChallenge(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge( @@ -92,12 +92,12 @@ func TestChallenge(t *testing.T) { } func TestChallengeInvalidPort(t *testing.T) { - apiURL, client := tester.MockACMEServer().BuildHTTPS(t) + server := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge( @@ -122,7 +122,7 @@ func TestChallengeInvalidPort(t *testing.T) { } func TestChallengeIPaddress(t *testing.T) { - apiURL, client := tester.MockACMEServer().BuildHTTPS(t) + server := tester.MockACMEServer().BuildHTTPS(t) domain := "127.0.0.1" port := "24457" @@ -169,7 +169,7 @@ func TestChallengeIPaddress(t *testing.T) { privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") - core, err := api.New(client, "lego-test", apiURL+"/dir", "", privateKey) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", privateKey) require.NoError(t, err) solver := NewChallenge( diff --git a/lego/client_test.go b/lego/client_test.go index 4b8a38f5f..63d3b0ad1 100644 --- a/lego/client_test.go +++ b/lego/client_test.go @@ -13,7 +13,7 @@ import ( ) func TestNewClient(t *testing.T) { - apiURL, httpsClient := tester.MockACMEServer().BuildHTTPS(t) + server := tester.MockACMEServer().BuildHTTPS(t) key, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") @@ -25,8 +25,8 @@ func TestNewClient(t *testing.T) { } config := NewConfig(user) - config.CADirURL = apiURL + "/dir" - config.HTTPClient = httpsClient + config.CADirURL = server.URL + "/dir" + config.HTTPClient = server.Client() client, err := NewClient(config) require.NoError(t, err, "Could not create client") diff --git a/platform/tester/api.go b/platform/tester/api.go index 5a7c52038..410fb1401 100644 --- a/platform/tester/api.go +++ b/platform/tester/api.go @@ -11,10 +11,10 @@ import ( ) // MockACMEServer Minimal stub ACME server for validation. -func MockACMEServer() *servermock.Builder[string] { +func MockACMEServer() *servermock.Builder[*httptest.Server] { return servermock.NewBuilder( - func(server *httptest.Server) (string, error) { - return server.URL, nil + func(server *httptest.Server) (*httptest.Server, error) { + return server, nil }). Route("GET /dir", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { serverURL := fmt.Sprintf("https://%s", req.Context().Value(http.LocalAddrContextKey)) diff --git a/platform/tester/servermock/builder.go b/platform/tester/servermock/builder.go index d62cec14d..b5a9d909b 100644 --- a/platform/tester/servermock/builder.go +++ b/platform/tester/servermock/builder.go @@ -71,7 +71,7 @@ func (b *Builder[T]) Build(t *testing.T) T { return client } -func (b *Builder[T]) BuildHTTPS(t *testing.T) (T, *http.Client) { +func (b *Builder[T]) BuildHTTPS(t *testing.T) T { t.Helper() server := httptest.NewTLSServer(b.mux) @@ -80,5 +80,5 @@ func (b *Builder[T]) BuildHTTPS(t *testing.T) (T, *http.Client) { client, err := b.clientBuilder(server) require.NoError(t, err) - return client, server.Client() + return client } diff --git a/registration/registar_test.go b/registration/registar_test.go index 2074b8f0f..43df1d648 100644 --- a/registration/registar_test.go +++ b/registration/registar_test.go @@ -16,7 +16,7 @@ import ( ) func TestRegistrar_ResolveAccountByKey(t *testing.T) { - apiURL, client := tester.MockACMEServer(). + server := tester.MockACMEServer(). Route("/account", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.Header().Set("Location", @@ -35,7 +35,7 @@ func TestRegistrar_ResolveAccountByKey(t *testing.T) { privatekey: key, } - core, err := api.New(client, "lego-test", apiURL+"/dir", "", key) + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", key) require.NoError(t, err) registrar := NewRegistrar(core, user) From fc21d23f7f15b11bdc0eb4583137e24643d05e83 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 4 Aug 2025 14:46:37 +0200 Subject: [PATCH 147/298] tests: clean up code (#2612) --- providers/dns/acmedns/acmedns_test.go | 32 +++---- .../{request-body.json => fetch-request.json} | 0 .../{fetch-all.json => fetch_all.json} | 0 .../dns/acmedns/internal/http_storage_test.go | 6 +- .../dns/allinkl/internal/identity_test.go | 1 + providers/dns/bindman/bindman_test.go | 92 +++++++++---------- .../bindman/fixtures/add_record-request.json | 5 + providers/dns/bindman/fixtures/error.json | 5 + providers/dns/civo/civo_test.go | 1 + providers/dns/cloudflare/cloudflare_test.go | 1 + providers/dns/gandi/gandi_test.go | 1 + providers/dns/gandiv5/gandiv5_test.go | 1 + providers/dns/gcloud/googlecloud_test.go | 2 +- providers/dns/httpreq/httpreq_test.go | 3 + providers/dns/lightsail/lightsail_test.go | 1 + providers/dns/loopia/internal/client_test.go | 1 + providers/dns/namecheap/namecheap_test.go | 4 +- providers/dns/otc/otc_test.go | 1 + providers/dns/rackspace/rackspace_test.go | 1 + providers/dns/regru/internal/client_test.go | 1 + providers/dns/route53/route53_test.go | 1 + providers/dns/safedns/internal/client_test.go | 1 + providers/dns/versio/versio_test.go | 9 +- providers/dns/vinyldns/vinyldns_test.go | 9 +- providers/dns/vultr/vultr_test.go | 2 +- providers/dns/zoneee/zoneee_test.go | 1 + 26 files changed, 106 insertions(+), 76 deletions(-) rename providers/dns/acmedns/internal/fixtures/{request-body.json => fetch-request.json} (100%) rename providers/dns/acmedns/internal/fixtures/{fetch-all.json => fetch_all.json} (100%) create mode 100644 providers/dns/bindman/fixtures/add_record-request.json create mode 100644 providers/dns/bindman/fixtures/error.json diff --git a/providers/dns/acmedns/acmedns_test.go b/providers/dns/acmedns/acmedns_test.go index e50c89d56..a3ab59d59 100644 --- a/providers/dns/acmedns/acmedns_test.go +++ b/providers/dns/acmedns/acmedns_test.go @@ -167,11 +167,11 @@ func TestPresent_httpStorage(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - config := servermock.NewBuilder(func(server *httptest.Server) (*Config, error) { - cfg := NewDefaultConfig() - cfg.StorageBaseURL = server.URL + provider := servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.StorageBaseURL = server.URL - return cfg, nil + return NewDNSProviderConfig(config) }). // Fetch Route("GET /example.com", servermock.Noop().WithStatusCode(http.StatusNotFound)). @@ -179,15 +179,12 @@ func TestPresent_httpStorage(t *testing.T) { Route("POST /example.com", servermock.Noop().WithStatusCode(test.StatusCode)). Build(t) - p, err := NewDNSProviderConfig(config) - require.NoError(t, err) - client := newMockClient().WithRegisterAccount(egTestAccount) - p.client = client + provider.client = client - err = p.Present(egDomain, "foo", egKeyAuth) + err := provider.Present(egDomain, "foo", egKeyAuth) if test.ExpectedError != nil { - assert.Equal(t, test.ExpectedError, err) + assert.EqualError(t, err, test.ExpectedError.Error()) assert.True(t, client.registerAccountCalled) assert.False(t, client.updateTXTRecordCalled) } else { @@ -222,22 +219,19 @@ func TestRegister_httpStorage(t *testing.T) { for _, test := range testCases { t.Run(test.Name, func(t *testing.T) { - config := servermock.NewBuilder(func(server *httptest.Server) (*Config, error) { - cfg := NewDefaultConfig() - cfg.StorageBaseURL = server.URL + provider := servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.StorageBaseURL = server.URL - return cfg, nil + return NewDNSProviderConfig(config) }). // Put Route("POST /example.com", servermock.Noop().WithStatusCode(test.StatusCode)). Build(t) - p, err := NewDNSProviderConfig(config) - require.NoError(t, err) + provider.client = newMockClient().WithRegisterAccount(egTestAccount) - p.client = newMockClient().WithRegisterAccount(egTestAccount) - - acc, err := p.register(t.Context(), egDomain, egFQDN) + acc, err := provider.register(t.Context(), egDomain, egFQDN) if test.ExpectedError != nil { assert.Equal(t, test.ExpectedError, err) } else { diff --git a/providers/dns/acmedns/internal/fixtures/request-body.json b/providers/dns/acmedns/internal/fixtures/fetch-request.json similarity index 100% rename from providers/dns/acmedns/internal/fixtures/request-body.json rename to providers/dns/acmedns/internal/fixtures/fetch-request.json diff --git a/providers/dns/acmedns/internal/fixtures/fetch-all.json b/providers/dns/acmedns/internal/fixtures/fetch_all.json similarity index 100% rename from providers/dns/acmedns/internal/fixtures/fetch-all.json rename to providers/dns/acmedns/internal/fixtures/fetch_all.json diff --git a/providers/dns/acmedns/internal/http_storage_test.go b/providers/dns/acmedns/internal/http_storage_test.go index abc3c0cde..5c166b47f 100644 --- a/providers/dns/acmedns/internal/http_storage_test.go +++ b/providers/dns/acmedns/internal/http_storage_test.go @@ -58,7 +58,7 @@ func TestHTTPStorage_Fetch_error(t *testing.T) { func TestHTTPStorage_FetchAll(t *testing.T) { storage := mockBuilder(). - Route("GET /", servermock.ResponseFromFixture("fetch-all.json")). + Route("GET /", servermock.ResponseFromFixture("fetch_all.json")). Build(t) account, err := storage.FetchAll(t.Context()) @@ -98,7 +98,7 @@ func TestHTTPStorage_FetchAll_error(t *testing.T) { func TestHTTPStorage_Put(t *testing.T) { storage := mockBuilder(). Route("POST /example.com", nil, - servermock.CheckRequestJSONBodyFromFixture("request-body.json")). + servermock.CheckRequestJSONBodyFromFixture("fetch-request.json")). Build(t) account := goacmedns.Account{ @@ -137,7 +137,7 @@ func TestHTTPStorage_Put_CNAME_created(t *testing.T) { Route("POST /example.com", servermock.Noop(). WithStatusCode(http.StatusCreated), - servermock.CheckRequestJSONBodyFromFixture("request-body.json")). + servermock.CheckRequestJSONBodyFromFixture("fetch-request.json")). Build(t) account := goacmedns.Account{ diff --git a/providers/dns/allinkl/internal/identity_test.go b/providers/dns/allinkl/internal/identity_test.go index dc55506f2..7b93b7688 100644 --- a/providers/dns/allinkl/internal/identity_test.go +++ b/providers/dns/allinkl/internal/identity_test.go @@ -13,6 +13,7 @@ import ( func setupIdentifierClient(server *httptest.Server) (*Identifier, error) { client := NewIdentifier("user", "secret") client.authEndpoint = server.URL + client.HTTPClient = server.Client() return client, nil } diff --git a/providers/dns/bindman/bindman_test.go b/providers/dns/bindman/bindman_test.go index a0db025e7..1f339dae8 100644 --- a/providers/dns/bindman/bindman_test.go +++ b/providers/dns/bindman/bindman_test.go @@ -2,13 +2,13 @@ package bindman import ( - "errors" "net/http" + "net/http/httptest" "testing" "time" "github.com/go-acme/lego/v4/platform/tester" - bindmanClient "github.com/labbsr0x/bindman-dns-webhook/src/client" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) @@ -106,10 +106,24 @@ func TestNewDNSProviderConfig(t *testing.T) { } } +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.BaseURL = server.URL + config.HTTPClient = server.Client() + + return NewDNSProviderConfig(config) + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With("User-Agent", "bindman-dns-webhook-client")) +} + func TestDNSProvider_Present(t *testing.T) { testCases := []struct { name string - client *bindmanClient.DNSWebhookClient + mock *servermock.Builder[*DNSProvider] domain string token string keyAuth string @@ -117,28 +131,31 @@ func TestDNSProvider_Present(t *testing.T) { }{ { name: "success when add record function return no error", - client: &bindmanClient.DNSWebhookClient{ - ClientAPI: &MockHTTPClientAPI{Status: http.StatusNoContent}, - }, - domain: "hello.test.com", + mock: mockBuilder(). + Route("POST /records", + servermock.Noop().WithStatusCode(http.StatusNoContent), + servermock.CheckRequestJSONBodyFromFixture("add_record-request.json"), + ), + domain: "example.com", keyAuth: "szDTG4zmM0GsKG91QAGO2M4UYOJMwU8oFpWOP7eTjCw", expectError: false, }, { name: "error when add record function return an error", - client: &bindmanClient.DNSWebhookClient{ - ClientAPI: &MockHTTPClientAPI{Error: errors.New("error adding record")}, - }, - domain: "hello.test.com", + mock: mockBuilder(). + Route("POST /records", + servermock.ResponseFromFixture("error.json"), + ), + domain: "example.com", keyAuth: "szDTG4zmM0GsKG91QAGO2M4UYOJMwU8oFpWOP7eTjCw", expectError: true, }, } for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - d := &DNSProvider{client: test.client} + provider := test.mock.Build(t) - err := d.Present(test.domain, test.token, test.keyAuth) + err := provider.Present(test.domain, test.token, test.keyAuth) if test.expectError { require.Error(t, err) } else { @@ -151,7 +168,7 @@ func TestDNSProvider_Present(t *testing.T) { func TestDNSProvider_CleanUp(t *testing.T) { testCases := []struct { name string - client *bindmanClient.DNSWebhookClient + mock *servermock.Builder[*DNSProvider] domain string token string keyAuth string @@ -159,30 +176,33 @@ func TestDNSProvider_CleanUp(t *testing.T) { }{ { name: "success when remove record function return no error", - client: &bindmanClient.DNSWebhookClient{ - ClientAPI: &MockHTTPClientAPI{Status: http.StatusNoContent}, - }, - domain: "hello.test.com", + mock: mockBuilder(). + Route("DELETE /records/_acme-challenge.example.com./TXT", + servermock.Noop().WithStatusCode(http.StatusNoContent), + ), + domain: "example.com", keyAuth: "szDTG4zmM0GsKG91QAGO2M4UYOJMwU8oFpWOP7eTjCw", expectError: false, }, { name: "error when remove record function return an error", - client: &bindmanClient.DNSWebhookClient{ - ClientAPI: &MockHTTPClientAPI{Error: errors.New("error adding record")}, - }, - domain: "hello.test.com", + mock: mockBuilder(). + Route("DELETE /records/_acme-challenge.example.com./TXT", + servermock.ResponseFromFixture("error.json"), + ), + domain: "example.com", keyAuth: "szDTG4zmM0GsKG91QAGO2M4UYOJMwU8oFpWOP7eTjCw", expectError: true, }, } + for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - d := &DNSProvider{client: test.client} + provider := test.mock.Build(t) - err := d.CleanUp(test.domain, test.token, test.keyAuth) + err := provider.CleanUp(test.domain, test.token, test.keyAuth) if test.expectError { - require.Error(t, err) + require.ErrorContains(t, err, "bindman: ERROR (400): bar; ") } else { require.NoError(t, err) } @@ -217,25 +237,3 @@ func TestLiveCleanUp(t *testing.T) { err = provider.CleanUp(envTest.GetDomain(), "", "123d==") require.NoError(t, err) } - -type MockHTTPClientAPI struct { - Data []byte - Status int - Error error -} - -func (m *MockHTTPClientAPI) Put(url string, data []byte) (*http.Response, []byte, error) { - return &http.Response{StatusCode: m.Status}, m.Data, m.Error -} - -func (m *MockHTTPClientAPI) Post(url string, data []byte) (*http.Response, []byte, error) { - return &http.Response{StatusCode: m.Status}, m.Data, m.Error -} - -func (m *MockHTTPClientAPI) Get(url string) (*http.Response, []byte, error) { - return &http.Response{StatusCode: m.Status}, m.Data, m.Error -} - -func (m *MockHTTPClientAPI) Delete(url string) (*http.Response, []byte, error) { - return &http.Response{StatusCode: m.Status}, m.Data, m.Error -} diff --git a/providers/dns/bindman/fixtures/add_record-request.json b/providers/dns/bindman/fixtures/add_record-request.json new file mode 100644 index 000000000..9585565b8 --- /dev/null +++ b/providers/dns/bindman/fixtures/add_record-request.json @@ -0,0 +1,5 @@ +{ + "name": "_acme-challenge.example.com.", + "value": "_EYMkjukXEMcXbnvpT6WLESzfYhxH190NKTBo3cpu-E", + "type": "TXT" +} diff --git a/providers/dns/bindman/fixtures/error.json b/providers/dns/bindman/fixtures/error.json new file mode 100644 index 000000000..c8a014510 --- /dev/null +++ b/providers/dns/bindman/fixtures/error.json @@ -0,0 +1,5 @@ +{ + "message": "bar", + "code": 400, + "details": ["foo"] +} diff --git a/providers/dns/civo/civo_test.go b/providers/dns/civo/civo_test.go index eb215fbcb..9b9e98b38 100644 --- a/providers/dns/civo/civo_test.go +++ b/providers/dns/civo/civo_test.go @@ -132,6 +132,7 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() + config.HTTPClient = server.Client() config.Token = "secret" p, err := NewDNSProviderConfig(config) diff --git a/providers/dns/cloudflare/cloudflare_test.go b/providers/dns/cloudflare/cloudflare_test.go index 10e96503a..14d20ef2d 100644 --- a/providers/dns/cloudflare/cloudflare_test.go +++ b/providers/dns/cloudflare/cloudflare_test.go @@ -304,6 +304,7 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { config.AuthEmail = "foo@example.com" config.AuthKey = "secret" config.BaseURL = server.URL + config.HTTPClient = server.Client() return NewDNSProviderConfig(config) }, diff --git a/providers/dns/gandi/gandi_test.go b/providers/dns/gandi/gandi_test.go index 6ed38c63c..46697190a 100644 --- a/providers/dns/gandi/gandi_test.go +++ b/providers/dns/gandi/gandi_test.go @@ -126,6 +126,7 @@ func TestDNSProvider(t *testing.T) { func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() config.BaseURL = server.URL + "/" + config.HTTPClient = server.Client() config.APIKey = "123412341234123412341234" return NewDNSProviderConfig(config) diff --git a/providers/dns/gandiv5/gandiv5_test.go b/providers/dns/gandiv5/gandiv5_test.go index 451b1b683..e0cdf86ff 100644 --- a/providers/dns/gandiv5/gandiv5_test.go +++ b/providers/dns/gandiv5/gandiv5_test.go @@ -96,6 +96,7 @@ func TestDNSProvider(t *testing.T) { config := NewDefaultConfig() config.PersonalAccessToken = "123412341234123412341234" config.BaseURL = server.URL + config.HTTPClient = server.Client() return NewDNSProviderConfig(config) }, diff --git a/providers/dns/gcloud/googlecloud_test.go b/providers/dns/gcloud/googlecloud_test.go index 61d5cc4e5..00d6c6c9a 100644 --- a/providers/dns/gcloud/googlecloud_test.go +++ b/providers/dns/gcloud/googlecloud_test.go @@ -352,7 +352,7 @@ func TestLiveCleanUp(t *testing.T) { func mockBuilder() *servermock.Builder[*DNSProvider] { return servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() - config.HTTPClient = &http.Client{Timeout: 10 * time.Second} + config.HTTPClient = server.Client() config.Project = "manhattan" p, err := NewDNSProviderConfig(config) diff --git a/providers/dns/httpreq/httpreq_test.go b/providers/dns/httpreq/httpreq_test.go index 038b21b1a..a7571de22 100644 --- a/providers/dns/httpreq/httpreq_test.go +++ b/providers/dns/httpreq/httpreq_test.go @@ -227,6 +227,7 @@ func mockBuilder(mode string) *servermock.Builder[*DNSProvider] { return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() + config.HTTPClient = server.Client() config.Endpoint, _ = url.Parse(server.URL) config.Mode = mode @@ -238,6 +239,7 @@ func mockBuilderWithPathPrefix(mode, prefix string) *servermock.Builder[*DNSProv return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() + config.HTTPClient = server.Client() config.Endpoint, _ = url.Parse(server.URL + prefix) config.Mode = mode @@ -249,6 +251,7 @@ func mockBuilderWithBasicAuth(username, password string) *servermock.Builder[*DN return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() + config.HTTPClient = server.Client() config.Endpoint, _ = url.Parse(server.URL) config.Username = username config.Password = password diff --git a/providers/dns/lightsail/lightsail_test.go b/providers/dns/lightsail/lightsail_test.go index 010e794a9..adac03d5d 100644 --- a/providers/dns/lightsail/lightsail_test.go +++ b/providers/dns/lightsail/lightsail_test.go @@ -60,6 +60,7 @@ func TestDNSProvider_Present(t *testing.T) { func(server *httptest.Server) (*DNSProvider, error) { return &DNSProvider{ client: lightsail.NewFromConfig(aws.Config{ + HTTPClient: server.Client(), Credentials: credentials.NewStaticCredentialsProvider("abc", "123", " "), Region: "mock-region", BaseEndpoint: aws.String(server.URL), diff --git a/providers/dns/loopia/internal/client_test.go b/providers/dns/loopia/internal/client_test.go index 63962b06e..fed7d94f1 100644 --- a/providers/dns/loopia/internal/client_test.go +++ b/providers/dns/loopia/internal/client_test.go @@ -15,6 +15,7 @@ func mockBuilder(password string) *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { client := NewClient("apiuser", password) + client.HTTPClient = server.Client() client.BaseURL = server.URL + "/" return client, nil diff --git a/providers/dns/namecheap/namecheap_test.go b/providers/dns/namecheap/namecheap_test.go index 922e48ebb..e0c947095 100644 --- a/providers/dns/namecheap/namecheap_test.go +++ b/providers/dns/namecheap/namecheap_test.go @@ -1,10 +1,8 @@ package namecheap import ( - "net/http" "net/http/httptest" "testing" - "time" "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" @@ -179,11 +177,11 @@ func Test_newPseudoRecord_domainSplit(t *testing.T) { func mockBuilder() *servermock.Builder[*DNSProvider] { return servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() + config.HTTPClient = server.Client() config.BaseURL = server.URL config.APIUser = envTestUser config.APIKey = envTestKey config.ClientIP = envTestClientIP - config.HTTPClient = &http.Client{Timeout: 60 * time.Second} return NewDNSProviderConfig(config) }) diff --git a/providers/dns/otc/otc_test.go b/providers/dns/otc/otc_test.go index 2174726c5..b5cb3a9d4 100644 --- a/providers/dns/otc/otc_test.go +++ b/providers/dns/otc/otc_test.go @@ -279,6 +279,7 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() + config.HTTPClient = server.Client() config.UserName = "user" config.Password = "secret" config.DomainName = "example.com" diff --git a/providers/dns/rackspace/rackspace_test.go b/providers/dns/rackspace/rackspace_test.go index cefb46134..4f270ee6c 100644 --- a/providers/dns/rackspace/rackspace_test.go +++ b/providers/dns/rackspace/rackspace_test.go @@ -113,6 +113,7 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() + config.HTTPClient = server.Client() config.APIUser = "testUser" config.APIKey = "testKey" config.HTTPClient = server.Client() diff --git a/providers/dns/regru/internal/client_test.go b/providers/dns/regru/internal/client_test.go index 0779f0d5f..002da0185 100644 --- a/providers/dns/regru/internal/client_test.go +++ b/providers/dns/regru/internal/client_test.go @@ -13,6 +13,7 @@ func mockBuilder() *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { client := NewClient("user", "secret") + client.HTTPClient = server.Client() client.baseURL, _ = url.Parse(server.URL) return client, nil diff --git a/providers/dns/route53/route53_test.go b/providers/dns/route53/route53_test.go index 6079bb4e6..a0fac49e8 100644 --- a/providers/dns/route53/route53_test.go +++ b/providers/dns/route53/route53_test.go @@ -146,6 +146,7 @@ func TestDNSProvider_Present(t *testing.T) { provider := servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { cfg := aws.Config{ + HTTPClient: server.Client(), Credentials: credentials.NewStaticCredentialsProvider("abc", "123", " "), Region: "mock-region", BaseEndpoint: aws.String(server.URL), diff --git a/providers/dns/safedns/internal/client_test.go b/providers/dns/safedns/internal/client_test.go index f984d2d8f..161a9f078 100644 --- a/providers/dns/safedns/internal/client_test.go +++ b/providers/dns/safedns/internal/client_test.go @@ -17,6 +17,7 @@ func mockBuilder() *servermock.Builder[*Client] { func(server *httptest.Server) (*Client, error) { client := NewClient("secret") client.baseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() return client, nil }, diff --git a/providers/dns/versio/versio_test.go b/providers/dns/versio/versio_test.go index ea1ccc221..9e3db8b0d 100644 --- a/providers/dns/versio/versio_test.go +++ b/providers/dns/versio/versio_test.go @@ -252,6 +252,13 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { EnvEndpoint: server.URL, }) - return NewDNSProvider() + provider, err := NewDNSProvider() + if err != nil { + return nil, err + } + + provider.client.HTTPClient = server.Client() + + return provider, nil }) } diff --git a/providers/dns/vinyldns/vinyldns_test.go b/providers/dns/vinyldns/vinyldns_test.go index 6f5b9b328..05a6cf0df 100644 --- a/providers/dns/vinyldns/vinyldns_test.go +++ b/providers/dns/vinyldns/vinyldns_test.go @@ -163,7 +163,14 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { config.SecretKey = "bar" config.Host = server.URL - return NewDNSProviderConfig(config) + provider, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + provider.client.HTTPClient = server.Client() + + return provider, nil }) } diff --git a/providers/dns/vultr/vultr_test.go b/providers/dns/vultr/vultr_test.go index 9be1a19b0..f8b8fcd1f 100644 --- a/providers/dns/vultr/vultr_test.go +++ b/providers/dns/vultr/vultr_test.go @@ -164,7 +164,7 @@ func TestDNSProvider_getHostedZone(t *testing.T) { provider := servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { - client := govultr.NewClient(nil) + client := govultr.NewClient(server.Client()) err := client.SetBaseURL(server.URL) require.NoError(t, err) diff --git a/providers/dns/zoneee/zoneee_test.go b/providers/dns/zoneee/zoneee_test.go index 6f50cf36e..32090a41b 100644 --- a/providers/dns/zoneee/zoneee_test.go +++ b/providers/dns/zoneee/zoneee_test.go @@ -273,6 +273,7 @@ func mockBuilder(username, apiKey string) *servermock.Builder[*DNSProvider] { return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() + config.HTTPClient = server.Client() config.Endpoint, _ = url.Parse(server.URL) config.Username = username config.APIKey = apiKey From 8737b36c859540bf102df044bbb8e237c6de814f Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 6 Aug 2025 14:42:49 +0200 Subject: [PATCH 148/298] Prepare release v4.25.2 --- CHANGELOG.md | 11 +++++++++++ acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98c7a97ac..5eb967c56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [v4.25.2](https://github.com/go-acme/lego/releases/tag/v4.25.2) (2025-08-06) + +### Changed +- +- **[cli,log]** log when dynamic renew date not yet reached + +### Fixed + +- **[cli]** fix: remove wrong env var +- **[lib,cli]** fix: enforce HTTPS to the ACME server + ## [v4.25.1](https://github.com/go-acme/lego/releases/tag/v4.25.1) (2025-07-21) ### Fixed diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 8524ba996..79a346032 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.25.1" + ourUserAgent = "xenolf-acme/4.25.2" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 6eb7c310d..68609bf12 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.25.1+dev-detach" +const defaultVersion = "v4.25.2+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index d0a435638..730f48d83 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.25.1" + ourUserAgent = "goacme-lego/4.25.2" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From f37aaa788c33fe7f4bee5d2c0b67b165717db5f0 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 6 Aug 2025 14:43:01 +0200 Subject: [PATCH 149/298] Detach v4.25.2 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 79a346032..d0994497d 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 68609bf12..3a89e1924 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.25.2+dev-release" +const defaultVersion = "v4.25.2+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 730f48d83..ce4242a51 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From 8521cbc9771b74ec0a0c0a4031bcef43b7662f84 Mon Sep 17 00:00:00 2001 From: Cleiton Nunes <49418438+cleitinif@users.noreply.github.com> Date: Wed, 6 Aug 2025 11:54:15 -0300 Subject: [PATCH 150/298] oraclecloud: handle instance_principal authentication (#2599) Co-authored-by: Fernandez Ludovic --- cmd/zz_gen_cmd_dnshelp.go | 13 +-- docs/content/dns/zz_gen_oraclecloud.md | 20 +++-- ...igprovider.go => configurationprovider.go} | 20 ++--- providers/dns/oraclecloud/fixtures/cert.pem | 19 +++++ providers/dns/oraclecloud/fixtures/key.pem | 28 ++++++ providers/dns/oraclecloud/oraclecloud.go | 46 +++++++--- providers/dns/oraclecloud/oraclecloud.toml | 20 +++-- providers/dns/oraclecloud/oraclecloud_test.go | 85 ++++++++++++++++++- 8 files changed, 210 insertions(+), 41 deletions(-) rename providers/dns/oraclecloud/{configprovider.go => configurationprovider.go} (72%) create mode 100644 providers/dns/oraclecloud/fixtures/cert.pem create mode 100644 providers/dns/oraclecloud/fixtures/key.pem diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index b1772e863..73c162b21 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -2473,15 +2473,16 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Credentials:`) ew.writeln(` - "OCI_COMPARTMENT_OCID": Compartment OCID`) - ew.writeln(` - "OCI_PRIVKEY_FILE": Private key file`) - ew.writeln(` - "OCI_PRIVKEY_PASS": Private key password`) - ew.writeln(` - "OCI_PUBKEY_FINGERPRINT": Public key fingerprint`) - ew.writeln(` - "OCI_REGION": Region`) - ew.writeln(` - "OCI_TENANCY_OCID": Tenancy OCID`) - ew.writeln(` - "OCI_USER_OCID": User OCID`) + ew.writeln(` - "OCI_PRIVKEY_FILE": Private key file (ignored if OCI_AUTH_TYPE=instance_principal)`) + ew.writeln(` - "OCI_PRIVKEY_PASS": Private key password (ignored if OCI_AUTH_TYPE=instance_principal)`) + ew.writeln(` - "OCI_PUBKEY_FINGERPRINT": Public key fingerprint (ignored if OCI_AUTH_TYPE=instance_principal)`) + ew.writeln(` - "OCI_REGION": Region (can be empty if OCI_AUTH_TYPE=instance_principal)`) + ew.writeln(` - "OCI_TENANCY_OCID": Tenancy OCID (ignored if OCI_AUTH_TYPE=instance_principal)`) + ew.writeln(` - "OCI_USER_OCID": User OCID (ignored if OCI_AUTH_TYPE=instance_principal)`) ew.writeln() ew.writeln(`Additional Configuration:`) + ew.writeln(` - "OCI_AUTH_TYPE": Authorization type. Possible values: 'instance_principal', '' (Default: '')`) ew.writeln(` - "OCI_HTTP_TIMEOUT": API request timeout in seconds (Default: 60)`) ew.writeln(` - "OCI_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "OCI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) diff --git a/docs/content/dns/zz_gen_oraclecloud.md b/docs/content/dns/zz_gen_oraclecloud.md index 1adf58088..974466601 100644 --- a/docs/content/dns/zz_gen_oraclecloud.md +++ b/docs/content/dns/zz_gen_oraclecloud.md @@ -26,6 +26,7 @@ Configuration for [Oracle Cloud](https://cloud.oracle.com/home). Here is an example bash command using the Oracle Cloud provider: ```bash +# Using API Key authentication: OCI_PRIVKEY_FILE="~/.oci/oci_api_key.pem" \ OCI_PRIVKEY_PASS="secret" \ OCI_TENANCY_OCID="ocid1.tenancy.oc1..secret" \ @@ -34,6 +35,12 @@ OCI_PUBKEY_FINGERPRINT="00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00" \ OCI_REGION="us-phoenix-1" \ OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com run + +# Using Instance Principal authentication (when running on OCI compute instances): +# https://docs.oracle.com/en-us/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm +OCI_AUTH_TYPE="instance_principal" \ +OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ +lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com run ``` @@ -44,12 +51,12 @@ lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com | Environment Variable Name | Description | |-----------------------|-------------| | `OCI_COMPARTMENT_OCID` | Compartment OCID | -| `OCI_PRIVKEY_FILE` | Private key file | -| `OCI_PRIVKEY_PASS` | Private key password | -| `OCI_PUBKEY_FINGERPRINT` | Public key fingerprint | -| `OCI_REGION` | Region | -| `OCI_TENANCY_OCID` | Tenancy OCID | -| `OCI_USER_OCID` | User OCID | +| `OCI_PRIVKEY_FILE` | Private key file (ignored if OCI_AUTH_TYPE=instance_principal) | +| `OCI_PRIVKEY_PASS` | Private key password (ignored if OCI_AUTH_TYPE=instance_principal) | +| `OCI_PUBKEY_FINGERPRINT` | Public key fingerprint (ignored if OCI_AUTH_TYPE=instance_principal) | +| `OCI_REGION` | Region (can be empty if OCI_AUTH_TYPE=instance_principal) | +| `OCI_TENANCY_OCID` | Tenancy OCID (ignored if OCI_AUTH_TYPE=instance_principal) | +| `OCI_USER_OCID` | User OCID (ignored if OCI_AUTH_TYPE=instance_principal) | 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" %}}). @@ -59,6 +66,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| +| `OCI_AUTH_TYPE` | Authorization type. Possible values: 'instance_principal', '' (Default: '') | | `OCI_HTTP_TIMEOUT` | API request timeout in seconds (Default: 60) | | `OCI_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `OCI_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | diff --git a/providers/dns/oraclecloud/configprovider.go b/providers/dns/oraclecloud/configurationprovider.go similarity index 72% rename from providers/dns/oraclecloud/configprovider.go rename to providers/dns/oraclecloud/configurationprovider.go index 7a51811a6..b18af50e4 100644 --- a/providers/dns/oraclecloud/configprovider.go +++ b/providers/dns/oraclecloud/configurationprovider.go @@ -11,19 +11,19 @@ import ( "github.com/nrdcg/oci-go-sdk/common/v1065" ) -type configProvider struct { +type environmentConfigurationProvider struct { values map[string]string privateKeyPassphrase string } -func newConfigProvider(values map[string]string) *configProvider { - return &configProvider{ +func newEnvironmentConfigurationProvider(values map[string]string) *environmentConfigurationProvider { + return &environmentConfigurationProvider{ values: values, privateKeyPassphrase: env.GetOrFile(EnvPrivKeyPass), } } -func (p *configProvider) PrivateRSAKey() (*rsa.PrivateKey, error) { +func (p *environmentConfigurationProvider) PrivateRSAKey() (*rsa.PrivateKey, error) { privateKey, err := getPrivateKey(envPrivKey) if err != nil { return nil, err @@ -32,7 +32,7 @@ func (p *configProvider) PrivateRSAKey() (*rsa.PrivateKey, error) { return common.PrivateKeyFromBytesWithPassword(privateKey, []byte(p.privateKeyPassphrase)) } -func (p *configProvider) KeyID() (string, error) { +func (p *environmentConfigurationProvider) KeyID() (string, error) { tenancy, err := p.TenancyOCID() if err != nil { return "", err @@ -51,23 +51,23 @@ func (p *configProvider) KeyID() (string, error) { return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil } -func (p *configProvider) TenancyOCID() (value string, err error) { +func (p *environmentConfigurationProvider) TenancyOCID() (value string, err error) { return p.values[EnvTenancyOCID], nil } -func (p *configProvider) UserOCID() (string, error) { +func (p *environmentConfigurationProvider) UserOCID() (string, error) { return p.values[EnvUserOCID], nil } -func (p *configProvider) KeyFingerprint() (string, error) { +func (p *environmentConfigurationProvider) KeyFingerprint() (string, error) { return p.values[EnvPubKeyFingerprint], nil } -func (p *configProvider) Region() (string, error) { +func (p *environmentConfigurationProvider) Region() (string, error) { return p.values[EnvRegion], nil } -func (p *configProvider) AuthType() (common.AuthConfig, error) { +func (p *environmentConfigurationProvider) AuthType() (common.AuthConfig, error) { // Inspired by https://github.com/oracle/oci-go-sdk/blob/e7635c292e60d0a9dcdd3a1e7de180d7c99b1eee/common/configuration.go#L231-L234 return common.AuthConfig{AuthType: common.UnknownAuthenticationType}, errors.New("unsupported, keep the interface") } diff --git a/providers/dns/oraclecloud/fixtures/cert.pem b/providers/dns/oraclecloud/fixtures/cert.pem new file mode 100644 index 000000000..fc1dcfb53 --- /dev/null +++ b/providers/dns/oraclecloud/fixtures/cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDHzCCAgegAwIBAgIQKIExaCLIXtXecrT1dWGLszANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw +MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAwM4wEPHOGAu8tZNNWx3cH6AMuqKwAmB2RwbA3OK034MzhydOjnDm +igw93eUc4nd3dnICyNpb2rbP9FgGlAuMlJ8raHQkG4DSXF1Bf14neOhLpfBItaX9 ++EB3oO0NupKZhaHrsTKzLGD7bauAPX6PDXuAPp3u5mgGGuZjpLZoKqg3//WImb/2 +xEMVsmvPKTb5FxS/tAMtywjGSUtCTCrudUEh4Gnj6IboVdwYmt539ETDK/Rerxf3 +/GsmEbuOkDUdBixQwLo0U+UAoMOw4zoyQDrrtyUmvffDxI50RAdZDFyFtqZ0ZQa8 +lQqrMdQdf+x1Wb7BKozSktAw4igRP/mknQIDAQABo28wbTAOBgNVHQ8BAf8EBAMC +AqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUcetTliVbYxxutNS8JRkotRY4DRkwFgYDVR0RBA8wDYILZXhhbXBsZS5vcmcw +DQYJKoZIhvcNAQELBQADggEBAEJP74/XB+12aGQ+EMERIX2Pn6YaaBLt6rTLqV7A +zFxI9YGIc4xlGa0qkpDhpz6RSypTQG6HN5aZ5b8dz3foMleUVP2cXd8zduc8GQCb +p4/8PpEhSl6dQb5+mg/qyHGUAaDl40VAbTLXHtn98dhacaJc+TKuXVJAgYRU3Sm3 +wFJxULZSnx+aGdE9s2brOGhvz1fVWnhvWzDvJSM+8xDURz8UiEnimTpV6m3CKItz +2GatNjM8ADKC7MHQI4I5v4fEwronN/g3NfPfFSmnOKk+lPSAW42WEvhFol+2VvdX +3p5X2QracSLCIj/DUBebZP9110C8Lj/YfFtOjFokqtQ9Fh4= +-----END CERTIFICATE----- diff --git a/providers/dns/oraclecloud/fixtures/key.pem b/providers/dns/oraclecloud/fixtures/key.pem new file mode 100644 index 000000000..1a56bb5a4 --- /dev/null +++ b/providers/dns/oraclecloud/fixtures/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDAzjAQ8c4YC7y1 +k01bHdwfoAy6orACYHZHBsDc4rTfgzOHJ06OcOaKDD3d5Rzid3d2cgLI2lvats/0 +WAaUC4yUnytodCQbgNJcXUF/Xid46Eul8Ei1pf34QHeg7Q26kpmFoeuxMrMsYPtt +q4A9fo8Ne4A+ne7maAYa5mOktmgqqDf/9YiZv/bEQxWya88pNvkXFL+0Ay3LCMZJ +S0JMKu51QSHgaePohuhV3Bia3nf0RMMr9F6vF/f8ayYRu46QNR0GLFDAujRT5QCg +w7DjOjJAOuu3JSa998PEjnREB1kMXIW2pnRlBryVCqsx1B1/7HVZvsEqjNKS0DDi +KBE/+aSdAgMBAAECggEAWl2pWJ/ErS9/HIl0NbMKk0YEAUuz/AEzHnoTVdPp22KW +eY+aOZe/7c7sBj7WqWw98SVhmbsCV0HcuNSzDJtXIedyRGw+6icYMVNCGgzKqlgR +8K3snjq1DLBGgYXpq9r/Got4ON6e7LttzIqXufrB2JtcUbzbFmGGDwCRjkcyDl9l +M8ufwD/Xgcd2L8jainU43d2pVxvxUIpRlRdoupCCSlkRYPsXiWlqav7YO4F/Txos +z3gJyzkXzc3WwfNZdQtEMYwBwozO+Dp2p4TUBr0Ta3MbfrKfDoTs4XT/Ce9IwJJS +/h6E9cxZD8t5oMT50quFjwhHBKodMiUqIlh2YQEAbwKBgQDIULzo/tgDgTwveyEn +L9n8yVbEh/SfrE9QtXcjkDB5+tYmIsIaz16NRWlAqnJVGZvcanrCq7ZTxgUcs/hW +Ag+sfWkeg7lmfeJAkiZ6kmi1h2qJjXMOBri+Cm6MTOsE6qdIc3eT4PnYkNpV7o6S +70hWNncVadXLV4Thm9BLAbMbQwKBgQD2ZwKe/2zRQcbuBe1loF0HWIsJPxcKQ3LH +hVf7f0YLQlIuzOhK8TQXgM0G4hxLlk1XeLjgf3z4Ju7hfh2JQLor1QYPRGUj66SX +KTE5eDwE0yEX1c9m5PW6M+f8vkOU4LQ/OtPw5OrKyYxpLf9dp42nmDYY/8IvUk96 +iKZNY1sSnwKBgQC27tS2SxVmjf0yt1WdfdurOQueSzKhJzD/2djFh4Zdvy8WgKOW +7E3C4eKvBXmIMezeq/cUFNBbTPmaLtjZYuSBd74p+c20xb17jnzJby9kqBgpKh4q +bwUDuG8gfZYbVVgTmC9ZwxkoJ5Dc7RETKqZ65R53VcHDA1f82Nitxw2UFQKBgBDl +c2qPvViEGC4OPf8wBfERA0e5Cc1sXpyL6kKWsajn/Va0OmGZNKc/788/Bg2w2tDa +uGK8m0cw9ESGL2RQCfQjgWzelcjmybyL2JJGSmdSSvylbrlxjeAc2xWbvmqhFfsX +/5yPNgJ926ECxHYZnT8W0u7X6urvy/9tC2pXG9GlAoGBAKOAfij4fMbHY+Z1m825 +VhY110FDnePYFJWmExP8GAVqOzhCs0mzyCnYh6nvS/OY8moH2LOuwPUlDfF3IzyT +hTUuXnykWT3w40eYQXXIaXEGhue+guL8ch16vEEJy5ltwEdIPNMTErbqAAk2W6Ps +NB46HzETzEIWnzoamX6iQVWj +-----END PRIVATE KEY----- diff --git a/providers/dns/oraclecloud/oraclecloud.go b/providers/dns/oraclecloud/oraclecloud.go index 2fb392e3e..a5e97baf8 100644 --- a/providers/dns/oraclecloud/oraclecloud.go +++ b/providers/dns/oraclecloud/oraclecloud.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/nrdcg/oci-go-sdk/common/v1065" + "github.com/nrdcg/oci-go-sdk/common/v1065/auth" "github.com/nrdcg/oci-go-sdk/dns/v1065" ) @@ -19,14 +20,17 @@ import ( const ( envNamespace = "OCI_" - EnvCompartmentOCID = envNamespace + "COMPARTMENT_OCID" + EnvAuthType = envNamespace + "AUTH_TYPE" + + EnvCompartmentOCID = envNamespace + "COMPARTMENT_OCID" + EnvRegion = envNamespace + "REGION" + envPrivKey = envNamespace + "PRIVKEY" EnvPrivKeyFile = envPrivKey + "_FILE" EnvPrivKeyPass = envPrivKey + "_PASS" EnvTenancyOCID = envNamespace + "TENANCY_OCID" EnvUserOCID = envNamespace + "USER_OCID" EnvPubKeyFingerprint = envNamespace + "PUBKEY_FINGERPRINT" - EnvRegion = envNamespace + "REGION" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -38,8 +42,9 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - CompartmentID string - OCIConfigProvider common.ConfigurationProvider + CompartmentID string + OCIConfigProvider common.ConfigurationProvider + PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -66,14 +71,33 @@ type DNSProvider struct { // NewDNSProvider returns a DNSProvider instance configured for OracleCloud. func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(envPrivKey, EnvTenancyOCID, EnvUserOCID, EnvPubKeyFingerprint, EnvRegion, EnvCompartmentOCID) - if err != nil { - return nil, fmt.Errorf("oraclecloud: %w", err) - } - config := NewDefaultConfig() - config.CompartmentID = values[EnvCompartmentOCID] - config.OCIConfigProvider = newConfigProvider(values) + + switch env.GetOrFile(EnvAuthType) { + case string(common.InstancePrincipal): + values, err := env.Get(EnvCompartmentOCID) + if err != nil { + return nil, fmt.Errorf("oraclecloud: %w", err) + } + + config.CompartmentID = values[EnvCompartmentOCID] + + configurationProvider, err := auth.InstancePrincipalConfigurationProviderForRegion(common.Region(env.GetOrFile(EnvRegion))) + if err != nil { + return nil, fmt.Errorf("oraclecloud: %w", err) + } + + config.OCIConfigProvider = configurationProvider + + default: + values, err := env.Get(envPrivKey, EnvTenancyOCID, EnvUserOCID, EnvPubKeyFingerprint, EnvRegion, EnvCompartmentOCID) + if err != nil { + return nil, fmt.Errorf("oraclecloud: %w", err) + } + + config.CompartmentID = values[EnvCompartmentOCID] + config.OCIConfigProvider = newEnvironmentConfigurationProvider(values) + } return NewDNSProviderConfig(config) } diff --git a/providers/dns/oraclecloud/oraclecloud.toml b/providers/dns/oraclecloud/oraclecloud.toml index 8c756a374..b02333219 100644 --- a/providers/dns/oraclecloud/oraclecloud.toml +++ b/providers/dns/oraclecloud/oraclecloud.toml @@ -5,6 +5,7 @@ Code = "oraclecloud" Since = "v2.3.0" Example = ''' +# Using API Key authentication: OCI_PRIVKEY_FILE="~/.oci/oci_api_key.pem" \ OCI_PRIVKEY_PASS="secret" \ OCI_TENANCY_OCID="ocid1.tenancy.oc1..secret" \ @@ -13,18 +14,25 @@ OCI_PUBKEY_FINGERPRINT="00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00" \ OCI_REGION="us-phoenix-1" \ OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com run + +# Using Instance Principal authentication (when running on OCI compute instances): +# https://docs.oracle.com/en-us/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm +OCI_AUTH_TYPE="instance_principal" \ +OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ +lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com run ''' [Configuration] [Configuration.Credentials] - OCI_PRIVKEY_FILE = "Private key file" - OCI_PRIVKEY_PASS = "Private key password" - OCI_TENANCY_OCID = "Tenancy OCID" - OCI_USER_OCID = "User OCID" - OCI_PUBKEY_FINGERPRINT = "Public key fingerprint" - OCI_REGION = "Region" OCI_COMPARTMENT_OCID = "Compartment OCID" + OCI_REGION = "Region (can be empty if OCI_AUTH_TYPE=instance_principal)" + OCI_PRIVKEY_FILE = "Private key file (ignored if OCI_AUTH_TYPE=instance_principal)" + OCI_PRIVKEY_PASS = "Private key password (ignored if OCI_AUTH_TYPE=instance_principal)" + OCI_TENANCY_OCID = "Tenancy OCID (ignored if OCI_AUTH_TYPE=instance_principal)" + OCI_USER_OCID = "User OCID (ignored if OCI_AUTH_TYPE=instance_principal)" + OCI_PUBKEY_FINGERPRINT = "Public key fingerprint (ignored if OCI_AUTH_TYPE=instance_principal)" [Configuration.Additional] + OCI_AUTH_TYPE = "Authorization type. Possible values: 'instance_principal', '' (Default: '')" OCI_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" OCI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" OCI_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" diff --git a/providers/dns/oraclecloud/oraclecloud_test.go b/providers/dns/oraclecloud/oraclecloud_test.go index 5d35c01a8..473fcd7ed 100644 --- a/providers/dns/oraclecloud/oraclecloud_test.go +++ b/providers/dns/oraclecloud/oraclecloud_test.go @@ -6,19 +6,30 @@ import ( "crypto/x509" "encoding/base64" "encoding/pem" + "net/http/httptest" "os" "testing" "time" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/nrdcg/oci-go-sdk/common/v1065" "github.com/stretchr/testify/require" ) const envDomain = envNamespace + "DOMAIN" +// Used by Instance Principal authentication. +const ( + envMetadataBaseURL = "OCI_METADATA_BASE_URL" + envSDKAuthClientRegionURL = "OCI_SDK_AUTH_CLIENT_REGION_URL" +) + var envTest = tester.NewEnvTest( envPrivKey, + EnvAuthType, + envMetadataBaseURL, + envSDKAuthClientRegionURL, EnvPrivKeyFile, EnvPrivKeyPass, EnvTenancyOCID, @@ -197,6 +208,76 @@ func TestNewDNSProvider(t *testing.T) { } } +func TestNewDNSProvider_instance_principal(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvAuthType: "instance_principal", + EnvCompartmentOCID: "123", + }, + }, + { + desc: "missing CompartmentID", + envVars: map[string]string{ + EnvAuthType: "instance_principal", + }, + expected: "oraclecloud: some credentials information are missing: OCI_COMPARTMENT_OCID", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + defer func() { + envTest.RestoreEnv() + }() + + envTest.ClearEnv() + + serverURL := servermock.NewBuilder( + func(server *httptest.Server) (string, error) { + return server.URL, nil + }). + Route("GET /instance/region", servermock.RawStringResponse("oc1")). + // To generate fake certificates: + // go run `go env GOROOT`/src/crypto/tls/generate_cert.go --host example.org --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h + Route("GET /identity/cert.pem", servermock.ResponseFromFixture("cert.pem")). + Route("GET /identity/key.pem", servermock.ResponseFromFixture("key.pem")). + Route("GET /identity/intermediate.pem", servermock.ResponseFromFixture("cert.pem")). + // https://github.com/oracle/oci-go-sdk/blob/413a2f277f95c5eb76e26a0e0833c396a518bf50/common/auth/jwt_test.go#L12 + Route("POST /v1/x509", servermock.RawStringResponse(`{"token":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImFzdyIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJvcGMub3JhY2xlLmNvbSIsImV4cCI6MTUxMTgzODc5MywiaWF0IjoxNTExODE3MTkzLCJpc3MiOiJhdXRoU2VydmljZS5vcmFjbGUuY29tIiwib3BjLWNlcnR0eXBlIjoiaW5zdGFuY2UiLCJvcGMtY29tcGFydG1lbnQiOiJvY2lkMS5jb21wYXJ0bWVudC5vYzEuLmJsdWhibHVoYmx1aCIsIm9wYy1pbnN0YW5jZSI6Im9jaWQxLmluc3RhbmNlLm9jMS5waHguYmx1aGJsdWhibHVoIiwib3BjLXRlbmFudCI6Im9jaWR2MTp0ZW5hbmN5Om9jMTpwaHg6MTIzNDU2Nzg5MDpibHVoYmx1aGJsdWgiLCJwdHlwZSI6Imluc3RhbmNlIiwic3ViIjoib2NpZDEuaW5zdGFuY2Uub2MxLnBoeC5ibHVoYmx1aGJsdWgiLCJ0ZW5hbnQiOiJvY2lkdjE6dGVuYW5jeTpvYzE6cGh4OjEyMzQ1Njc4OTA6Ymx1aGJsdWhibHVoIiwidHR5cGUiOiJ4NTA5In0.zen7q2yJSpMjzH4ym_H7VEwZA0-vTT4Wcild-HRfLxX6A1ej4tlpACa7A24j5JoZYI4mHooZVJ8e7ZezFenK0zZx5j8RbIjsqJKwroYXExOiBXLCUwMWOLXIndEsUzzGLqnPfKHXd80vrhMLmtkVTCJqBMzvPUSYkH_ciWgmjP9m0YETdQ9ifghkADhZGt9IlnOswg0s3Bx9ASwxFZEtom0BmU9GwEuITTTZfKvndk785BlNeZMOjhovaD97-LYpv5B_PiWEz8zialK5zxjijLCw06zyA8CQRQqmVCagNUPilfz_BcPyImzvFDuzQcPyDkTcsB7weX35tafHmA_Ul"}`)). + Build(t) + + envVars := map[string]string{ + envMetadataBaseURL: serverURL, + envSDKAuthClientRegionURL: serverURL, + } + + for k, v := range test.envVars { + envVars[k] = v + } + + envTest.Apply(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.Error(t, err) + require.Contains(t, err.Error(), test.expected) + } + }) + } +} + func TestNewDNSProviderConfig(t *testing.T) { envTest.ClearEnv() defer envTest.RestoreEnv() @@ -273,12 +354,12 @@ func TestLiveCleanUp(t *testing.T) { require.NoError(t, err) } -func mockConfigurationProvider(keyPassphrase string) *configProvider { +func mockConfigurationProvider(keyPassphrase string) *environmentConfigurationProvider { envTest.Apply(map[string]string{ envPrivKey: mustGeneratePrivateKey("secret"), }) - return &configProvider{ + return &environmentConfigurationProvider{ values: map[string]string{ EnvCompartmentOCID: "test", EnvPrivKeyPass: "test", From 0ec467f0750e8d89b55b3561d8bdfc7edf895dd2 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 6 Aug 2025 16:54:29 +0200 Subject: [PATCH 151/298] bump: github.com/akamai/AkamaiOPEN-edgegrid-golang to v11 (#2524) --- go.mod | 8 +- go.sum | 29 ++- providers/dns/edgedns/edgedns.go | 154 +++++++++++----- .../dns/edgedns/edgedns_integration_test.go | 19 +- providers/dns/edgedns/edgedns_test.go | 171 +++++++++--------- 5 files changed, 229 insertions(+), 152 deletions(-) diff --git a/go.mod b/go.mod index f2c6a71d0..c5c328d41 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/Azure/go-autorest/autorest/to v0.4.1 github.com/BurntSushi/toml v1.5.0 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 - github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 + github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.0.0 github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.8 github.com/aliyun/credentials-go v1.4.6 github.com/aws/aws-sdk-go-v2 v1.36.6 @@ -128,7 +128,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sso v1.25.6 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4 // indirect github.com/aws/smithy-go v1.22.4 // indirect - github.com/benbjohnson/clock v1.3.0 // indirect + github.com/benbjohnson/clock v1.3.5 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect @@ -142,6 +142,7 @@ require ( github.com/go-errors/errors v1.0.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.23.0 // indirect @@ -169,7 +170,6 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/peterhellberg/link v1.2.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect @@ -187,7 +187,7 @@ require ( github.com/sony/gobreaker v1.0.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.18.2 // indirect github.com/stretchr/objx v0.5.2 // indirect diff --git a/go.sum b/go.sum index 6ed16ca42..f6b2aceee 100644 --- a/go.sum +++ b/go.sum @@ -105,8 +105,8 @@ github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 h1:F1j7z+/DKEsYqZNoxC6wvfmaiDneLsQOFQmuq9NADSY= -github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2/go.mod h1:QlXr/TrICfQ/ANa76sLeQyhAJyNR9sEcfNuZBkY9jgY= +github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.0.0 h1:ot8yMzEm0Kx2LCTOzlM7zh4ASLFZ6H0iYhtIKolSujs= +github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.0.0/go.mod h1:rvh3imDA6EaQi+oM/GQHkQAOHbXPKJ7EWJvfjuw141Q= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -166,6 +166,9 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= @@ -216,8 +219,8 @@ github.com/aziontech/azionapi-go-sdk v0.142.0/go.mod h1:cA5DY/VP4X5Eu11LpQNzNn83 github.com/baidubce/bce-sdk-go v0.9.235 h1:iAi+seH9w1Go2szFNzyGumahLGDsuYZ3i8hduX3qiM8= github.com/baidubce/bce-sdk-go v0.9.235/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -340,6 +343,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -447,7 +452,6 @@ github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -476,8 +480,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -674,7 +676,6 @@ github.com/nats-io/nats.go v1.12.1/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/ github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo= github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk= @@ -733,8 +734,6 @@ github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE= github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= @@ -858,8 +857,8 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -921,9 +920,6 @@ github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+ github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= @@ -1460,10 +1456,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0= -gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= diff --git a/providers/dns/edgedns/edgedns.go b/providers/dns/edgedns/edgedns.go index 10898006a..1a92a967f 100644 --- a/providers/dns/edgedns/edgedns.go +++ b/providers/dns/edgedns/edgedns.go @@ -2,14 +2,17 @@ package edgedns import ( + "context" "errors" "fmt" + "net/http" "slices" "strings" "time" - configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid" + edgegriddns "github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/dns" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/session" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" @@ -20,14 +23,8 @@ import ( const ( envNamespace = "AKAMAI_" - EnvEdgeRc = envNamespace + "EDGERC" - EnvEdgeRcSection = envNamespace + "EDGERC_SECTION" - - EnvHost = envNamespace + "HOST" - EnvClientToken = envNamespace + "CLIENT_TOKEN" - EnvClientSecret = envNamespace + "CLIENT_SECRET" - EnvAccessToken = envNamespace + "ACCESS_TOKEN" - + EnvEdgeRc = envNamespace + "EDGERC" + EnvEdgeRcSection = envNamespace + "EDGERC_SECTION" EnvAccountSwitchKey = envNamespace + "ACCOUNT_SWITCH_KEY" EnvTTL = envNamespace + "TTL" @@ -35,6 +32,15 @@ const ( EnvPollingInterval = envNamespace + "POLLING_INTERVAL" ) +// Test Environment variables names (unused). +// TODO(ldez): must be moved into test files. +const ( + EnvHost = envNamespace + "HOST" + EnvClientToken = envNamespace + "CLIENT_TOKEN" + EnvClientSecret = envNamespace + "CLIENT_SECRET" + EnvAccessToken = envNamespace + "ACCESS_TOKEN" +) + const ( defaultPropagationTimeout = 3 * time.Minute defaultPollInterval = 15 * time.Second @@ -46,7 +52,7 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - edgegrid.Config + *edgegrid.Config PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -58,7 +64,7 @@ func NewDefaultConfig() *Config { TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, defaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, defaultPollInterval), - Config: edgegrid.Config{MaxBody: maxBody}, + Config: &edgegrid.Config{MaxBody: maxBody}, } } @@ -73,27 +79,27 @@ type DNSProvider struct { // 1. Section-specific environment variables `AKAMAI_{SECTION}_HOST`, `AKAMAI_{SECTION}_ACCESS_TOKEN`, `AKAMAI_{SECTION}_CLIENT_TOKEN`, `AKAMAI_{SECTION}_CLIENT_SECRET` where `{SECTION}` is specified using `AKAMAI_EDGERC_SECTION` // 2. If `AKAMAI_EDGERC_SECTION` is not defined or is set to `default`: Environment variables `AKAMAI_HOST`, `AKAMAI_ACCESS_TOKEN`, `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET` // 3. .edgerc file located at `AKAMAI_EDGERC` (defaults to `~/.edgerc`, sections can be specified using `AKAMAI_EDGERC_SECTION`) -// 4. Default environment variables: `AKAMAI_HOST`, `AKAMAI_ACCESS_TOKEN`, `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET` // // See also: https://developer.akamai.com/api/getting-started func NewDNSProvider() (*DNSProvider, error) { - config := NewDefaultConfig() - - rcPath := env.GetOrDefaultString(EnvEdgeRc, "") - rcSection := env.GetOrDefaultString(EnvEdgeRcSection, "") - accountSwitchKey := env.GetOrDefaultString(EnvAccountSwitchKey, "") - - conf, err := edgegrid.Init(rcPath, rcSection) + conf, err := edgegrid.New( + edgegrid.WithEnv(true), + edgegrid.WithFile(env.GetOrDefaultString(EnvEdgeRc, "~/.edgerc")), + edgegrid.WithSection(env.GetOrDefaultString(EnvEdgeRcSection, "default")), + ) if err != nil { return nil, fmt.Errorf("edgedns: %w", err) } conf.MaxBody = maxBody + accountSwitchKey := env.GetOrDefaultString(EnvAccountSwitchKey, "") + if accountSwitchKey != "" { conf.AccountKey = accountSwitchKey } + config := NewDefaultConfig() config.Config = conf return NewDNSProviderConfig(config) @@ -105,7 +111,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("edgedns: the configuration of the DNS provider is nil") } - configdns.Init(config.Config) + err := config.Validate() + if err != nil { + return nil, fmt.Errorf("edgedns: %w", err) + } return &DNSProvider{config: config}, nil } @@ -118,14 +127,27 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) + sess, err := session.New(session.WithSigner(d.config)) + if err != nil { + return fmt.Errorf("edgedns: %w", err) + } + + client := edgegriddns.Client(sess) + zone, err := getZone(info.EffectiveFQDN) if err != nil { return fmt.Errorf("edgedns: %w", err) } - record, err := configdns.GetRecord(zone, info.EffectiveFQDN, "TXT") + record, err := client.GetRecord(ctx, edgegriddns.GetRecordRequest{ + Zone: zone, + Name: info.EffectiveFQDN, + RecordType: "TXT", + }) if err != nil && !isNotFound(err) { return fmt.Errorf("edgedns: %w", err) } @@ -145,7 +167,16 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { record.Target = append(record.Target, `"`+info.Value+`"`) record.TTL = d.config.TTL - err = record.Update(zone) + err = client.UpdateRecord(ctx, edgegriddns.UpdateRecordRequest{ + Record: &edgegriddns.RecordBody{ + Name: record.Name, + RecordType: record.RecordType, + TTL: record.TTL, + Active: record.Active, + Target: record.Target, + }, + Zone: zone, + }) if err != nil { return fmt.Errorf("edgedns: %w", err) } @@ -153,14 +184,16 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return nil } - record = &configdns.RecordBody{ - Name: info.EffectiveFQDN, - RecordType: "TXT", - TTL: d.config.TTL, - Target: []string{`"` + info.Value + `"`}, - } - - err = record.Save(zone) + err = client.CreateRecord(ctx, edgegriddns.CreateRecordRequest{ + Record: &edgegriddns.RecordBody{ + Name: info.EffectiveFQDN, + RecordType: "TXT", + TTL: d.config.TTL, + Target: []string{`"` + info.Value + `"`}, + }, + Zone: zone, + RecLock: nil, + }) if err != nil { return fmt.Errorf("edgedns: %w", err) } @@ -170,14 +203,27 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) + sess, err := session.New(session.WithSigner(d.config)) + if err != nil { + return fmt.Errorf("edgedns: %w", err) + } + + client := edgegriddns.Client(sess) + zone, err := getZone(info.EffectiveFQDN) if err != nil { return fmt.Errorf("edgedns: %w", err) } - existingRec, err := configdns.GetRecord(zone, info.EffectiveFQDN, "TXT") + existingRec, err := client.GetRecord(ctx, edgegriddns.GetRecordRequest{ + Zone: zone, + Name: info.EffectiveFQDN, + RecordType: "TXT", + }) if err != nil { if isNotFound(err) { return nil @@ -197,19 +243,21 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } - var newRData []string - for _, val := range existingRec.Target { - val = strings.Trim(val, `"`) - if val == info.Value { - continue - } - newRData = append(newRData, val) - } + newRData := filterRData(existingRec, info) if len(newRData) > 0 { existingRec.Target = newRData - err = existingRec.Update(zone) + err = client.UpdateRecord(ctx, edgegriddns.UpdateRecordRequest{ + Record: &edgegriddns.RecordBody{ + Name: existingRec.Name, + RecordType: existingRec.RecordType, + TTL: existingRec.TTL, + Active: existingRec.Active, + Target: existingRec.Target, + }, + Zone: zone, + }) if err != nil { return fmt.Errorf("edgedns: %w", err) } @@ -217,7 +265,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } - err = existingRec.Delete(zone) + err = client.DeleteRecord(ctx, edgegriddns.DeleteRecordRequest{ + Zone: zone, + Name: existingRec.Name, + RecordType: "TXT", + RecLock: nil, + }) if err != nil { return fmt.Errorf("edgedns: %w", err) } @@ -245,6 +298,19 @@ func isNotFound(err error) bool { return false } - var e configdns.ConfigDNSError - return errors.As(err, &e) && e.NotFound() + var e *edgegriddns.Error + return errors.As(err, &e) && e.StatusCode == http.StatusNotFound +} + +func filterRData(existingRec *edgegriddns.GetRecordResponse, info dns01.ChallengeInfo) []string { + var newRData []string + for _, val := range existingRec.Target { + val = strings.Trim(val, `"`) + if val == info.Value { + continue + } + newRData = append(newRData, val) + } + + return newRData } diff --git a/providers/dns/edgedns/edgedns_integration_test.go b/providers/dns/edgedns/edgedns_integration_test.go index e1b3bb7cf..c740aba4b 100644 --- a/providers/dns/edgedns/edgedns_integration_test.go +++ b/providers/dns/edgedns/edgedns_integration_test.go @@ -1,11 +1,13 @@ package edgedns import ( + "context" "fmt" "testing" "time" - configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2" + edgegriddns "github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/dns" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/session" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -69,10 +71,21 @@ func TestLiveTTL(t *testing.T) { zone, err := getZone(fqdn) require.NoError(t, err) - resourceRecordSets, err := configdns.GetRecordList(zone, fqdn, "TXT") + ctx := context.Background() + + sess, err := session.New(session.WithSigner(provider.config)) require.NoError(t, err) - for i, rrset := range resourceRecordSets.Recordsets { + client := edgegriddns.Client(sess) + + resourceRecordSets, err := client.GetRecordList(ctx, edgegriddns.GetRecordListRequest{ + Zone: zone, + RecordType: "TXT", + }) + + require.NoError(t, err) + + for i, rrset := range resourceRecordSets.RecordSets { if rrset.Name != fqdn { continue } diff --git a/providers/dns/edgedns/edgedns_test.go b/providers/dns/edgedns/edgedns_test.go index 3ac55a4a4..2dafe9c82 100644 --- a/providers/dns/edgedns/edgedns_test.go +++ b/providers/dns/edgedns/edgedns_test.go @@ -1,12 +1,10 @@ package edgedns import ( - "os" "testing" "time" - configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v11/pkg/edgegrid" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/tester" "github.com/stretchr/testify/require" @@ -21,6 +19,9 @@ const ( ) var envTest = tester.NewEnvTest( + EnvTTL, + EnvPollingInterval, + EnvPropagationTimeout, EnvHost, EnvClientToken, EnvClientSecret, @@ -35,7 +36,7 @@ var envTest = tester.NewEnvTest( WithDomain(envDomain). WithLiveTestRequirements(EnvHost, EnvClientToken, EnvClientSecret, EnvAccessToken, envDomain) -func TestNewDNSProvider_FromEnv(t *testing.T) { +func TestNewDNSProvider(t *testing.T) { testCases := []struct { desc string envVars map[string]string @@ -50,13 +51,13 @@ func TestNewDNSProvider_FromEnv(t *testing.T) { EnvClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", EnvAccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", }, - expectedConfig: &edgegrid.Config{ - Host: "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net", - ClientToken: "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", - ClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - AccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", - MaxBody: maxBody, - }, + expectedConfig: newEdgeConfig(func(config *edgegrid.Config) { + config.Host = "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net" + config.ClientToken = "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx" + config.ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + config.AccessToken = "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx" + config.MaxBody = maxBody + }, edgegrid.WithEnv(true), edgegrid.WithFile("/dev/null")), }, { desc: "with account switch key", @@ -67,14 +68,14 @@ func TestNewDNSProvider_FromEnv(t *testing.T) { EnvAccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", EnvAccountSwitchKey: "F-AC-1234", }, - expectedConfig: &edgegrid.Config{ - Host: "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net", - ClientToken: "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", - ClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - AccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", - MaxBody: maxBody, - AccountKey: "F-AC-1234", - }, + expectedConfig: newEdgeConfig(func(config *edgegrid.Config) { + config.Host = "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net" + config.ClientToken = "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx" + config.ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + config.AccessToken = "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx" + config.MaxBody = maxBody + config.AccountKey = "F-AC-1234" + }, edgegrid.WithEnv(true), edgegrid.WithFile("/dev/null")), }, { desc: "with section", @@ -85,17 +86,17 @@ func TestNewDNSProvider_FromEnv(t *testing.T) { envTestClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", envTestAccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", }, - expectedConfig: &edgegrid.Config{ - Host: "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net", - ClientToken: "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", - ClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - AccessToken: "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx", - MaxBody: maxBody, - }, + expectedConfig: newEdgeConfig(func(config *edgegrid.Config) { + config.Host = "akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net" + config.ClientToken = "akab-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx" + config.ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + config.AccessToken = "akac-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx" + config.MaxBody = maxBody + }, edgegrid.WithEnv(true), edgegrid.WithFile("/dev/null"), edgegrid.WithSection("test")), }, { desc: "missing credentials", - expectedErr: "edgedns: Unable to create instance using environment or .edgerc file", + expectedErr: `edgedns: unable to load config from environment or .edgerc file`, }, { desc: "missing host", @@ -105,7 +106,7 @@ func TestNewDNSProvider_FromEnv(t *testing.T) { EnvClientSecret: "C", EnvAccessToken: "D", }, - expectedErr: "edgedns: Unable to create instance using environment or .edgerc file", + expectedErr: `edgedns: unable to load config from environment or .edgerc file`, }, { desc: "missing client token", @@ -115,7 +116,7 @@ func TestNewDNSProvider_FromEnv(t *testing.T) { EnvClientSecret: "C", EnvAccessToken: "D", }, - expectedErr: "edgedns: Fatal missing required environment variables: [AKAMAI_CLIENT_TOKEN]", + expectedErr: `edgedns: unable to load config from environment or .edgerc file`, }, { desc: "missing client secret", @@ -125,7 +126,7 @@ func TestNewDNSProvider_FromEnv(t *testing.T) { EnvClientSecret: "", EnvAccessToken: "D", }, - expectedErr: "edgedns: Fatal missing required environment variables: [AKAMAI_CLIENT_SECRET]", + expectedErr: `edgedns: unable to load config from environment or .edgerc file`, }, { desc: "missing access token", @@ -135,7 +136,7 @@ func TestNewDNSProvider_FromEnv(t *testing.T) { EnvClientSecret: "C", EnvAccessToken: "", }, - expectedErr: "edgedns: Fatal missing required environment variables: [AKAMAI_ACCESS_TOKEN]", + expectedErr: `edgedns: unable to load config from environment or .edgerc file`, }, } @@ -147,6 +148,7 @@ func TestNewDNSProvider_FromEnv(t *testing.T) { if test.envVars == nil { test.envVars = map[string]string{} } + test.envVars[EnvEdgeRc] = "/dev/null" envTest.Apply(test.envVars) @@ -154,7 +156,7 @@ func TestNewDNSProvider_FromEnv(t *testing.T) { p, err := NewDNSProvider() if test.expectedErr != "" { - require.EqualError(t, err, test.expectedErr) + require.ErrorContains(t, err, test.expectedErr) return } @@ -163,13 +165,62 @@ func TestNewDNSProvider_FromEnv(t *testing.T) { require.NotNil(t, p.config) if test.expectedConfig != nil { - require.Equal(t, *test.expectedConfig, configdns.Config) + require.Equal(t, test.expectedConfig, p.config.Config) } }) } } -func TestDNSProvider_findZone(t *testing.T) { +func TestNewDefaultConfig(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected *Config + }{ + { + desc: "default configuration", + expected: &Config{ + TTL: dns01.DefaultTTL, + PropagationTimeout: 3 * time.Minute, + PollingInterval: 15 * time.Second, + Config: &edgegrid.Config{ + MaxBody: maxBody, + }, + }, + }, + { + desc: "custom values", + envVars: map[string]string{ + EnvTTL: "99", + EnvPropagationTimeout: "60", + EnvPollingInterval: "60", + }, + expected: &Config{ + TTL: 99, + PropagationTimeout: 60 * time.Second, + PollingInterval: 60 * time.Second, + Config: &edgegrid.Config{ + MaxBody: maxBody, + }, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + defer envTest.RestoreEnv() + envTest.ClearEnv() + + envTest.Apply(test.envVars) + + config := NewDefaultConfig() + + require.Equal(t, test.expected, config) + }) + } +} + +func Test_findZone(t *testing.T) { testCases := []struct { desc string domain string @@ -198,53 +249,7 @@ func TestDNSProvider_findZone(t *testing.T) { } } -func TestNewDefaultConfig(t *testing.T) { - defer envTest.RestoreEnv() - - testCases := []struct { - desc string - envVars map[string]string - expected *Config - }{ - { - desc: "default configuration", - expected: &Config{ - TTL: dns01.DefaultTTL, - PropagationTimeout: 3 * time.Minute, - PollingInterval: 15 * time.Second, - Config: edgegrid.Config{ - MaxBody: maxBody, - }, - }, - }, - { - desc: "custom values", - envVars: map[string]string{ - EnvTTL: "99", - EnvPropagationTimeout: "60", - EnvPollingInterval: "60", - }, - expected: &Config{ - TTL: 99, - PropagationTimeout: 60 * time.Second, - PollingInterval: 60 * time.Second, - Config: edgegrid.Config{ - MaxBody: maxBody, - }, - }, - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - envTest.ClearEnv() - for key, value := range test.envVars { - os.Setenv(key, value) - } - - config := NewDefaultConfig() - - require.Equal(t, test.expected, config) - }) - } +func newEdgeConfig(opts ...edgegrid.Option) *edgegrid.Config { + config, _ := edgegrid.New(opts...) + return config } From ddce5cff4a7fb5a2fc1a0a2a319610f1fbfd8825 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 6 Aug 2025 17:00:24 +0200 Subject: [PATCH 152/298] Add DNS provider for Tencent EdgeOne (#2606) --- README.md | 14 +- cmd/zz_gen_cmd_dnshelp.go | 24 +++ docs/content/dns/zz_gen_edgeone.md | 72 +++++++ docs/content/dns/zz_gen_tencentcloud.md | 4 +- docs/data/zz_cli_help.toml | 2 +- go.mod | 3 +- go.sum | 6 +- providers/dns/edgeone/edgeone.go | 188 +++++++++++++++++++ providers/dns/edgeone/edgeone.toml | 27 +++ providers/dns/edgeone/edgeone_test.go | 147 +++++++++++++++ providers/dns/edgeone/wrapper.go | 50 +++++ providers/dns/tencentcloud/tencentcloud.toml | 2 +- providers/dns/zz_gen_dns_providers.go | 3 + 13 files changed, 528 insertions(+), 14 deletions(-) create mode 100644 docs/content/dns/zz_gen_edgeone.md create mode 100644 providers/dns/edgeone/edgeone.go create mode 100644 providers/dns/edgeone/edgeone.toml create mode 100644 providers/dns/edgeone/edgeone_test.go create mode 100644 providers/dns/edgeone/wrapper.go diff --git a/README.md b/README.md index 59b62a8de..66ed2e4d6 100644 --- a/README.md +++ b/README.md @@ -221,37 +221,37 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Stackpath Technitium Tencent Cloud DNS - Timeweb Cloud + Tencent EdgeOne + 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 - West.cn/西部数码 + West.cn/西部数码 Yandex 360 Yandex Cloud Yandex PDD - Zone.ee + Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 73c162b21..1aacdc7af 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -60,6 +60,7 @@ func allDNSCodes() string { "dynu", "easydns", "edgedns", + "edgeone", "efficientip", "epik", "exec", @@ -1206,6 +1207,29 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/edgedns`) + case "edgeone": + // generated from: providers/dns/edgeone/edgeone.toml + ew.writeln(`Configuration for Tencent EdgeOne.`) + ew.writeln(`Code: 'edgeone'`) + ew.writeln(`Since: 'v4.26.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "EDGEONE_SECRET_ID": Access key ID`) + ew.writeln(` - "EDGEONE_SECRET_KEY": Access Key secret`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "EDGEONE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "EDGEONE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 30)`) + ew.writeln(` - "EDGEONE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 1200)`) + ew.writeln(` - "EDGEONE_REGION": Region`) + ew.writeln(` - "EDGEONE_SESSION_TOKEN": Access Key token`) + ew.writeln(` - "EDGEONE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/edgeone`) + case "efficientip": // generated from: providers/dns/efficientip/efficientip.toml ew.writeln(`Configuration for Efficient IP.`) diff --git a/docs/content/dns/zz_gen_edgeone.md b/docs/content/dns/zz_gen_edgeone.md new file mode 100644 index 000000000..b7b5b1eec --- /dev/null +++ b/docs/content/dns/zz_gen_edgeone.md @@ -0,0 +1,72 @@ +--- +title: "Tencent EdgeOne" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: edgeone +dnsprovider: + since: "v4.26.0" + code: "edgeone" + url: "https://edgeone.ai" +--- + + + + + + +Configuration for [Tencent EdgeOne](https://edgeone.ai). + + + + +- Code: `edgeone` +- Since: v4.26.0 + + +Here is an example bash command using the Tencent EdgeOne provider: + +```bash +EDGEONE_SECRET_ID=abcdefghijklmnopqrstuvwx \ +EDGEONE_SECRET_KEY=your-secret-key \ +lego --email you@example.com --dns edgeone -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `EDGEONE_SECRET_ID` | Access key ID | +| `EDGEONE_SECRET_KEY` | Access Key secret | + +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 | +|--------------------------------|-------------| +| `EDGEONE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `EDGEONE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 30) | +| `EDGEONE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 1200) | +| `EDGEONE_REGION` | Region | +| `EDGEONE_SESSION_TOKEN` | Access Key token | +| `EDGEONE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | + +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://edgeone.ai/document/50454#dns-record-apis) +- [Go client](https://github.com/tencentcloud/tencentcloud-sdk-go) + + + + diff --git a/docs/content/dns/zz_gen_tencentcloud.md b/docs/content/dns/zz_gen_tencentcloud.md index bc08c43ce..ef1e6cdf8 100644 --- a/docs/content/dns/zz_gen_tencentcloud.md +++ b/docs/content/dns/zz_gen_tencentcloud.md @@ -6,7 +6,7 @@ slug: tencentcloud dnsprovider: since: "v4.6.0" code: "tencentcloud" - url: "https://cloud.tencent.com/product/cns" + url: "https://cloud.tencent.com/product/dns" --- @@ -14,7 +14,7 @@ dnsprovider: -Configuration for [Tencent Cloud DNS](https://cloud.tencent.com/product/cns). +Configuration for [Tencent Cloud DNS](https://cloud.tencent.com/product/dns). diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 35f641cd4..62e422217 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, efficientip, epik, exec, exoscale, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/go.mod b/go.mod index c5c328d41..47ce27ebe 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/exoscale/egoscale/v3 v3.1.24 github.com/go-acme/alidns-20150109/v4 v4.5.10 github.com/go-acme/tencentclouddnspod v1.0.1208 + github.com/go-acme/tencentedgdeone v1.0.1212 github.com/go-jose/go-jose/v4 v4.1.1 github.com/go-viper/mapstructure/v2 v2.4.0 github.com/google/go-cmp v0.7.0 @@ -76,7 +77,7 @@ require ( github.com/selectel/go-selvpcclient/v4 v4.1.0 github.com/softlayer/softlayer-go v1.1.7 github.com/stretchr/testify v1.10.0 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1210 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1212 github.com/transip/gotransip/v6 v6.26.0 github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec github.com/urfave/cli/v2 v2.27.7 diff --git a/go.sum b/go.sum index f6b2aceee..f99d60e97 100644 --- a/go.sum +++ b/go.sum @@ -321,6 +321,8 @@ github.com/go-acme/alidns-20150109/v4 v4.5.10 h1:epLD0VaHR5XUpiM6mjm4MzQFICrk+zp github.com/go-acme/alidns-20150109/v4 v4.5.10/go.mod h1:qGRq8kD0xVgn82qRSQmhHwh/oWxKRjF4Db5OI4ScV5g= github.com/go-acme/tencentclouddnspod v1.0.1208 h1:xAVy1lmg2KcKKeYmFSBQUttwc1o1S++9QTjAotGC+BM= github.com/go-acme/tencentclouddnspod v1.0.1208/go.mod h1:yxG02mkbbVd7lTb97nOn7oj09djhm7hAwxNQw4B9dpQ= +github.com/go-acme/tencentedgdeone v1.0.1212 h1:H0HaYrkyX4htOXgusb6Mf8mKDIr/+CIqq5hbOdQB2EY= +github.com/go-acme/tencentedgdeone v1.0.1212/go.mod h1:IADOuBpaFM1IIKFedlJj7EfMqstThcm1g1g1uVejgxo= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -895,8 +897,8 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1208/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1210 h1:waSk2KyI2VvXtR+XQJm0v1lWfgbJg51iSWJh4hWnyeo= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1210/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1212 h1:61myGZ5h8YgmFEziBGuKPSnPNs75KlqDQwEeGC2dwyk= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1212/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= diff --git a/providers/dns/edgeone/edgeone.go b/providers/dns/edgeone/edgeone.go new file mode 100644 index 000000000..88a795c69 --- /dev/null +++ b/providers/dns/edgeone/edgeone.go @@ -0,0 +1,188 @@ +// Package edgeone implements a DNS provider for solving the DNS-01 challenge using Tencent EdgeOne. +package edgeone + +import ( + "context" + "errors" + "fmt" + "math" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" + teo "github.com/go-acme/tencentedgdeone/v20220901" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + "golang.org/x/net/idna" +) + +// Environment variables names. +const ( + envNamespace = "EDGEONE_" + + EnvSecretID = envNamespace + "SECRET_ID" + EnvSecretKey = envNamespace + "SECRET_KEY" + EnvRegion = envNamespace + "REGION" + EnvSessionToken = envNamespace + "SESSION_TOKEN" + + 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 { + SecretID string + SecretKey string + Region string + SessionToken string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPTimeout time.Duration +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + TTL: env.GetOrDefaultInt(EnvTTL, 60), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 20*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 30*time.Second), + HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *teo.Client + + recordIDs map[string]*string + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for Tencent EdgeOne. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvSecretID, EnvSecretKey) + if err != nil { + return nil, fmt.Errorf("edgeone: %w", err) + } + + config := NewDefaultConfig() + config.SecretID = values[EnvSecretID] + config.SecretKey = values[EnvSecretKey] + config.Region = env.GetOrDefaultString(EnvRegion, "") + config.SessionToken = env.GetOrDefaultString(EnvSessionToken, "") + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Tencent EdgeOne. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("edgeone: the configuration of the DNS provider is nil") + } + + var credential *common.Credential + + switch { + case config.SecretID != "" && config.SecretKey != "" && config.SessionToken != "": + credential = common.NewTokenCredential(config.SecretID, config.SecretKey, config.SessionToken) + case config.SecretID != "" && config.SecretKey != "": + credential = common.NewCredential(config.SecretID, config.SecretKey) + default: + return nil, errors.New("edgeone: credentials missing") + } + + cpf := profile.NewClientProfile() + cpf.HttpProfile.Endpoint = "teo.intl.tencentcloudapi.com" + cpf.HttpProfile.ReqTimeout = int(math.Round(config.HTTPTimeout.Seconds())) + + client, err := teo.NewClient(credential, config.Region, cpf) + if err != nil { + return nil, fmt.Errorf("edgeone: %w", err) + } + + return &DNSProvider{ + config: config, + client: client, + recordIDs: map[string]*string{}, + recordIDsMu: sync.Mutex{}, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + zone, err := d.getHostedZone(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("edgeone: failed to get hosted zone: %w", err) + } + + punnyCoded, err := idna.ToASCII(dns01.UnFqdn(info.EffectiveFQDN)) + if err != nil { + return fmt.Errorf("edgeone: fail to convert punycode: %w", err) + } + + request := teo.NewCreateDnsRecordRequest() + request.Name = ptr.Pointer(punnyCoded) + request.ZoneId = zone.ZoneId + request.Type = ptr.Pointer("TXT") + request.Content = ptr.Pointer(info.Value) + request.TTL = ptr.Pointer(int64(d.config.TTL)) + + nr, err := teo.CreateDnsRecordWithContext(ctx, d.client, request) + if err != nil { + return fmt.Errorf("edgeone: API call failed: %w", err) + } + + d.recordIDsMu.Lock() + d.recordIDs[token] = nr.Response.RecordId + 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) + + ctx := context.Background() + + zone, err := d.getHostedZone(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("edgeone: failed to get hosted zone: %w", err) + } + + // 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("edgeone: unknown record ID for '%s'", info.EffectiveFQDN) + } + + request := teo.NewDeleteDnsRecordsRequest() + request.ZoneId = zone.ZoneId + request.RecordIds = []*string{recordID} + + _, err = teo.DeleteDnsRecordsWithContext(ctx, d.client, request) + if err != nil { + return fmt.Errorf("edgeone: delete record failed: %w", err) + } + + 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/edgeone/edgeone.toml b/providers/dns/edgeone/edgeone.toml new file mode 100644 index 000000000..120756da6 --- /dev/null +++ b/providers/dns/edgeone/edgeone.toml @@ -0,0 +1,27 @@ +Name = "Tencent EdgeOne" +Description = '''''' +URL = "https://edgeone.ai" +Code = "edgeone" +Since = "v4.26.0" + +Example = ''' +EDGEONE_SECRET_ID=abcdefghijklmnopqrstuvwx \ +EDGEONE_SECRET_KEY=your-secret-key \ +lego --email you@example.com --dns edgeone -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + EDGEONE_SECRET_ID = "Access key ID" + EDGEONE_SECRET_KEY = "Access Key secret" + [Configuration.Additional] + EDGEONE_SESSION_TOKEN = "Access Key token" + EDGEONE_REGION = "Region" + EDGEONE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 30)" + EDGEONE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 1200)" + EDGEONE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + EDGEONE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://edgeone.ai/document/50454#dns-record-apis" + GoClient = "https://github.com/tencentcloud/tencentcloud-sdk-go" diff --git a/providers/dns/edgeone/edgeone_test.go b/providers/dns/edgeone/edgeone_test.go new file mode 100644 index 000000000..e1c004d67 --- /dev/null +++ b/providers/dns/edgeone/edgeone_test.go @@ -0,0 +1,147 @@ +package edgeone + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvSecretID, EnvSecretKey). + WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvSecretID: "123", + EnvSecretKey: "456", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{ + EnvSecretID: "", + EnvSecretKey: "", + }, + expected: "edgeone: some credentials information are missing: EDGEONE_SECRET_ID,EDGEONE_SECRET_KEY", + }, + { + desc: "missing access id", + envVars: map[string]string{ + EnvSecretID: "", + EnvSecretKey: "456", + }, + expected: "edgeone: some credentials information are missing: EDGEONE_SECRET_ID", + }, + { + desc: "missing secret key", + envVars: map[string]string{ + EnvSecretID: "123", + EnvSecretKey: "", + }, + expected: "edgeone: some credentials information are missing: EDGEONE_SECRET_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 + secretID string + secretKey string + expected string + }{ + { + desc: "success", + secretID: "123", + secretKey: "456", + }, + { + desc: "missing credentials", + expected: "edgeone: credentials missing", + }, + { + desc: "missing secret id", + secretKey: "456", + expected: "edgeone: credentials missing", + }, + { + desc: "missing secret key", + secretID: "123", + expected: "edgeone: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.SecretID = test.secretID + config.SecretKey = test.secretKey + + 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/edgeone/wrapper.go b/providers/dns/edgeone/wrapper.go new file mode 100644 index 000000000..0d207f030 --- /dev/null +++ b/providers/dns/edgeone/wrapper.go @@ -0,0 +1,50 @@ +package edgeone + +import ( + "context" + "fmt" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" + teo "github.com/go-acme/tencentedgdeone/v20220901" +) + +func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*teo.Zone, error) { + request := teo.NewDescribeZonesRequest() + + var domains []*teo.Zone + + for { + response, err := teo.DescribeZonesWithContext(ctx, d.client, request) + if err != nil { + return nil, fmt.Errorf("API call failed: %w", err) + } + + domains = append(domains, response.Response.Zones...) + + if int64(len(domains)) >= ptr.Deref(response.Response.TotalCount) { + break + } + + request.Offset = ptr.Pointer(int64(len(domains))) + } + + authZone, err := dns01.FindZoneByFqdn(domain) + if err != nil { + return nil, fmt.Errorf("could not find zone: %w", err) + } + + var hostedZone *teo.Zone + for _, zone := range domains { + unfqdn := dns01.UnFqdn(authZone) + if ptr.Deref(zone.ZoneName) == unfqdn { + hostedZone = zone + } + } + + if hostedZone == nil { + return nil, fmt.Errorf("zone %s not found in dnspod for domain %s", authZone, domain) + } + + return hostedZone, nil +} diff --git a/providers/dns/tencentcloud/tencentcloud.toml b/providers/dns/tencentcloud/tencentcloud.toml index 75a950a49..7f06d9386 100644 --- a/providers/dns/tencentcloud/tencentcloud.toml +++ b/providers/dns/tencentcloud/tencentcloud.toml @@ -1,6 +1,6 @@ Name = "Tencent Cloud DNS" Description = '''''' -URL = "https://cloud.tencent.com/product/cns" +URL = "https://cloud.tencent.com/product/dns" Code = "tencentcloud" Since = "v4.6.0" diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 59205f7f6..f3598b7b5 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -54,6 +54,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/dynu" "github.com/go-acme/lego/v4/providers/dns/easydns" "github.com/go-acme/lego/v4/providers/dns/edgedns" + "github.com/go-acme/lego/v4/providers/dns/edgeone" "github.com/go-acme/lego/v4/providers/dns/efficientip" "github.com/go-acme/lego/v4/providers/dns/epik" "github.com/go-acme/lego/v4/providers/dns/exec" @@ -265,6 +266,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return easydns.NewDNSProvider() case "edgedns", "fastdns": return edgedns.NewDNSProvider() + case "edgeone": + return edgeone.NewDNSProvider() case "efficientip": return efficientip.NewDNSProvider() case "epik": From 1904d17e895f2753fda646f8e9315aee56c3d746 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 6 Aug 2025 18:11:54 +0200 Subject: [PATCH 153/298] chore: bump alidns from v4.55.10 to v4.55.11 (#2601) --- go.mod | 7 +++---- go.sum | 15 ++++++--------- providers/dns/alidns/alidns.go | 11 +++++++---- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 47ce27ebe..72109a3d0 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,8 @@ require ( github.com/BurntSushi/toml v1.5.0 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.0.0 - github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.8 + github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.9 + github.com/alibabacloud-go/tea v1.3.10 github.com/aliyun/credentials-go v1.4.6 github.com/aws/aws-sdk-go-v2 v1.36.6 github.com/aws/aws-sdk-go-v2/config v1.29.18 @@ -30,7 +31,7 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 github.com/dnsimple/dnsimple-go/v4 v4.0.0 github.com/exoscale/egoscale/v3 v3.1.24 - github.com/go-acme/alidns-20150109/v4 v4.5.10 + github.com/go-acme/alidns-20150109/v4 v4.5.11-1 github.com/go-acme/tencentclouddnspod v1.0.1208 github.com/go-acme/tencentedgdeone v1.0.1212 github.com/go-jose/go-jose/v4 v4.1.1 @@ -112,9 +113,7 @@ require ( github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect github.com/alibabacloud-go/debug v1.0.1 // indirect - github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect github.com/alibabacloud-go/openapi-util v0.1.1 // indirect - github.com/alibabacloud-go/tea v1.3.9 // indirect github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 // indirect diff --git a/go.sum b/go.sum index f99d60e97..019e2b8dd 100644 --- a/go.sum +++ b/go.sum @@ -123,9 +123,8 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= -github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.11/go.mod h1:wHxkgZT1ClZdcwEVP/pDgYK/9HucsnCfMipmJgCz4xY= -github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.8 h1:AL+nH363NJFS1NXIjCdmj5MOElgKEqgFeoq7vjje350= -github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.8/go.mod h1:d+z3ScRqc7PFzg4h9oqE3h8yunRZvAvU7u+iuPYEhpU= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.9 h1:7P0KWfed/YMtpeuW3E2iwokzoz9L7H9rB+VZzg5DeBs= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.9/go.mod h1:kgnXaV74AVjM3ZWJu1GhyXGuCtxljJ677oUfz6MyJOE= github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= @@ -146,14 +145,13 @@ github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/Ke github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= -github.com/alibabacloud-go/tea v1.3.9 h1:bjgt1bvdY780vz/17iWNNtbXl4A77HWntWMeaUF3So0= -github.com/alibabacloud-go/tea v1.3.9/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= +github.com/alibabacloud-go/tea v1.3.10 h1:J0Ke8iMyoxX2daj90hdPr1QgfxJnhR8SOflB910o/Dk= +github.com/alibabacloud-go/tea v1.3.10/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0= github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= -github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= @@ -247,7 +245,6 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= -github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -317,8 +314,8 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-acme/alidns-20150109/v4 v4.5.10 h1:epLD0VaHR5XUpiM6mjm4MzQFICrk+zpuqDz2aO1/R/k= -github.com/go-acme/alidns-20150109/v4 v4.5.10/go.mod h1:qGRq8kD0xVgn82qRSQmhHwh/oWxKRjF4Db5OI4ScV5g= +github.com/go-acme/alidns-20150109/v4 v4.5.11-1 h1:SAYWMWGLEyaSvKFxdw/O7XNDfSIn4yK5ig5v5O2bX8E= +github.com/go-acme/alidns-20150109/v4 v4.5.11-1/go.mod h1:ZCuTWP0+J6sGCQpMNWhOUVK5vLvNsAF+oT2EmMrJA8U= github.com/go-acme/tencentclouddnspod v1.0.1208 h1:xAVy1lmg2KcKKeYmFSBQUttwc1o1S++9QTjAotGC+BM= github.com/go-acme/tencentclouddnspod v1.0.1208/go.mod h1:yxG02mkbbVd7lTb97nOn7oj09djhm7hAwxNQw4B9dpQ= github.com/go-acme/tencentedgdeone v1.0.1212 h1:H0HaYrkyX4htOXgusb6Mf8mKDIr/+CIqq5hbOdQB2EY= diff --git a/providers/dns/alidns/alidns.go b/providers/dns/alidns/alidns.go index 660098d4a..3664dc88e 100644 --- a/providers/dns/alidns/alidns.go +++ b/providers/dns/alidns/alidns.go @@ -2,11 +2,13 @@ package alidns import ( + "context" "errors" "fmt" "time" openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" + "github.com/alibabacloud-go/tea/dara" "github.com/aliyun/credentials-go/credentials" alidns "github.com/go-acme/alidns-20150109/v4/client" "github.com/go-acme/lego/v4/challenge" @@ -162,7 +164,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return err } - _, err = alidns.AddDomainRecord(d.client, recordRequest) + _, err = alidns.AddDomainRecordWithContext(context.Background(), d.client, recordRequest, &dara.RuntimeOptions{}) if err != nil { return fmt.Errorf("alicloud: API call failed: %w", err) } @@ -188,11 +190,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { RecordId: rec.RecordId, } - _, err = alidns.DeleteDomainRecord(d.client, request) + _, err = alidns.DeleteDomainRecordWithContext(context.Background(), d.client, request, &dara.RuntimeOptions{}) if err != nil { return fmt.Errorf("alicloud: %w", err) } } + return nil } @@ -206,7 +209,7 @@ func (d *DNSProvider) getHostedZone(domain string) (string, error) { for { request.SetPageNumber(startPage) - response, err := alidns.DescribeDomains(d.client, request) + response, err := alidns.DescribeDomainsWithContext(context.Background(), d.client, request, &dara.RuntimeOptions{}) if err != nil { return "", fmt.Errorf("API call failed: %w", err) } @@ -265,7 +268,7 @@ func (d *DNSProvider) findTxtRecords(fqdn string) ([]*alidns.DescribeDomainRecor var records []*alidns.DescribeDomainRecordsResponseBodyDomainRecordsRecord - result, err := alidns.DescribeDomainRecords(d.client, request) + result, err := alidns.DescribeDomainRecordsWithContext(context.Background(), d.client, request, &dara.RuntimeOptions{}) if err != nil { return records, fmt.Errorf("API call has failed: %w", err) } From 0012e20e52ada01f1790990d0e3a8a63e4c19aef Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 8 Aug 2025 18:28:50 +0200 Subject: [PATCH 154/298] tests: new DNS router/server/mock (#2613) --- .golangci.yml | 4 +- challenge/dns01/dns_challenge_manual_test.go | 8 + challenge/dns01/dns_challenge_test.go | 58 ++++ challenge/dns01/fixtures/resolv.conf.1 | 2 +- challenge/dns01/mock_test.go | 78 +++++ challenge/dns01/nameserver_test.go | 265 +++++++++++------ challenge/dns01/precheck.go | 6 +- challenge/dns01/precheck_test.go | 155 ++++++---- platform/tester/dnsmock/dnsmock.go | 191 ++++++++++++ platform/tester/dnsmock/dnsmock_test.go | 240 ++++++++++++++++ platform/tester/dnsmock/handlers.go | 76 +++++ platform/tester/dnsmock/handlers_test.go | 156 ++++++++++ providers/dns/rfc2136/rfc2136.go | 14 +- providers/dns/rfc2136/rfc2136_test.go | 288 +++++++------------ 14 files changed, 1205 insertions(+), 336 deletions(-) create mode 100644 challenge/dns01/mock_test.go create mode 100644 platform/tester/dnsmock/dnsmock.go create mode 100644 platform/tester/dnsmock/dnsmock_test.go create mode 100644 platform/tester/dnsmock/handlers.go create mode 100644 platform/tester/dnsmock/handlers_test.go diff --git a/.golangci.yml b/.golangci.yml index 66f3fd9d0..2fabe806c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -195,8 +195,8 @@ linters: text: dnsTimeout is a global variable linters: - gochecknoglobals - - path: challenge/dns01/nameserver_test.go - text: findXByFqdnTestCases is a global variable + - path: challenge/dns01/precheck.go + text: defaultNameserverPort is a global variable linters: - gochecknoglobals - path: challenge/http01/domain_matcher.go diff --git a/challenge/dns01/dns_challenge_manual_test.go b/challenge/dns01/dns_challenge_manual_test.go index 26a508d1c..e0a2dc93a 100644 --- a/challenge/dns01/dns_challenge_manual_test.go +++ b/challenge/dns01/dns_challenge_manual_test.go @@ -5,10 +5,18 @@ import ( "os" "testing" + "github.com/go-acme/lego/v4/platform/tester/dnsmock" + "github.com/miekg/dns" "github.com/stretchr/testify/require" ) func TestDNSProviderManual(t *testing.T) { + useAsNameserver(t, dnsmock.NewServer(). + Query("_acme-challenge.example.com. CNAME", dnsmock.Noop). + Query("_acme-challenge.example.com. SOA", dnsmock.Error(dns.RcodeNameError)). + Query("example.com. SOA", dnsmock.SOA("")). + Build(t)) + backupStdin := os.Stdin defer func() { os.Stdin = backupStdin }() diff --git a/challenge/dns01/dns_challenge_test.go b/challenge/dns01/dns_challenge_test.go index 48bd9986c..7c723497c 100644 --- a/challenge/dns01/dns_challenge_test.go +++ b/challenge/dns01/dns_challenge_test.go @@ -11,6 +11,8 @@ import ( "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/dnsmock" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -113,6 +115,10 @@ func TestChallenge_PreSolve(t *testing.T) { } func TestChallenge_Solve(t *testing.T) { + useAsNameserver(t, dnsmock.NewServer(). + Query("_acme-challenge.example.com. CNAME", dnsmock.Noop). + Build(t)) + server := tester.MockACMEServer().BuildHTTPS(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) @@ -280,3 +286,55 @@ func TestChallenge_CleanUp(t *testing.T) { }) } } + +func TestGetChallengeInfo(t *testing.T) { + useAsNameserver(t, dnsmock.NewServer(). + Query("_acme-challenge.example.com. CNAME", dnsmock.Noop). + Build(t)) + + info := GetChallengeInfo("example.com", "123") + + expected := ChallengeInfo{ + FQDN: "_acme-challenge.example.com.", + EffectiveFQDN: "_acme-challenge.example.com.", + Value: "pmWkWSBCL51Bfkhn79xPuKBKHz__H6B-mY6G9_eieuM", + } + + assert.Equal(t, expected, info) +} + +func TestGetChallengeInfo_CNAME(t *testing.T) { + useAsNameserver(t, dnsmock.NewServer(). + Query("_acme-challenge.example.com. CNAME", dnsmock.CNAME("example.org.")). + Query("example.org. CNAME", dnsmock.Noop). + Build(t)) + + info := GetChallengeInfo("example.com", "123") + + expected := ChallengeInfo{ + FQDN: "_acme-challenge.example.com.", + EffectiveFQDN: "example.org.", + Value: "pmWkWSBCL51Bfkhn79xPuKBKHz__H6B-mY6G9_eieuM", + } + + assert.Equal(t, expected, info) +} + +func TestGetChallengeInfo_CNAME_disabled(t *testing.T) { + useAsNameserver(t, dnsmock.NewServer(). + // Never called when the env var works. + Query("_acme-challenge.example.com. CNAME", dnsmock.CNAME("example.org.")). + Build(t)) + + t.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "true") + + info := GetChallengeInfo("example.com", "123") + + expected := ChallengeInfo{ + FQDN: "_acme-challenge.example.com.", + EffectiveFQDN: "_acme-challenge.example.com.", + Value: "pmWkWSBCL51Bfkhn79xPuKBKHz__H6B-mY6G9_eieuM", + } + + assert.Equal(t, expected, info) +} diff --git a/challenge/dns01/fixtures/resolv.conf.1 b/challenge/dns01/fixtures/resolv.conf.1 index 3098f99b5..bc2a3c1ac 100644 --- a/challenge/dns01/fixtures/resolv.conf.1 +++ b/challenge/dns01/fixtures/resolv.conf.1 @@ -1,4 +1,4 @@ -domain company.com +domain example.com nameserver 10.200.3.249 nameserver 10.200.3.250:5353 nameserver 2001:4860:4860::8844 diff --git a/challenge/dns01/mock_test.go b/challenge/dns01/mock_test.go new file mode 100644 index 000000000..535d79cda --- /dev/null +++ b/challenge/dns01/mock_test.go @@ -0,0 +1,78 @@ +package dns01 + +import ( + "context" + "net" + "testing" + "time" + + "github.com/miekg/dns" + "github.com/stretchr/testify/require" +) + +func fakeNS(name, ns string) *dns.NS { + return &dns.NS{ + Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 172800}, + Ns: ns, + } +} + +func fakeA(name, ip string) *dns.A { + return &dns.A{ + Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 10}, + A: net.ParseIP(ip), + } +} + +func fakeTXT(name, value string) *dns.TXT { + return &dns.TXT{ + Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 10}, + Txt: []string{value}, + } +} + +// mockResolver modifies the default DNS resolver to use a custom network address during the test execution. +// IMPORTANT: it modifying global variables. +func mockResolver(t *testing.T, addr net.Addr) { + t.Helper() + + _, port, err := net.SplitHostPort(addr.String()) + require.NoError(t, err) + + originalDefaultNameserverPort := defaultNameserverPort + t.Cleanup(func() { + defaultNameserverPort = originalDefaultNameserverPort + }) + + defaultNameserverPort = port + + originalResolver := net.DefaultResolver + t.Cleanup(func() { + net.DefaultResolver = originalResolver + }) + + net.DefaultResolver = &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + d := net.Dialer{Timeout: 1 * time.Second} + + return d.DialContext(ctx, network, addr.String()) + }, + } +} + +func useAsNameserver(t *testing.T, addr net.Addr) { + t.Helper() + + ClearFqdnCache() + t.Cleanup(func() { + ClearFqdnCache() + }) + + originalRecursiveNameservers := recursiveNameservers + t.Cleanup(func() { + recursiveNameservers = originalRecursiveNameservers + }) + + recursiveNameservers = ParseNameservers([]string{addr.String()}) +} diff --git a/challenge/dns01/nameserver_test.go b/challenge/dns01/nameserver_test.go index 4eb7a5f15..dd4d66dcb 100644 --- a/challenge/dns01/nameserver_test.go +++ b/challenge/dns01/nameserver_test.go @@ -5,138 +5,237 @@ import ( "sort" "testing" + "github.com/go-acme/lego/v4/platform/tester/dnsmock" "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestLookupNameserversOK(t *testing.T) { +func Test_lookupNameserversOK(t *testing.T) { testCases := []struct { - fqdn string - nss []string + desc string + fakeDNSServer *dnsmock.Builder + fqdn string + expected []string }{ { - fqdn: "en.wikipedia.org.", - nss: []string{"ns0.wikimedia.org.", "ns1.wikimedia.org.", "ns2.wikimedia.org."}, + fqdn: "en.wikipedia.org.localhost.", + fakeDNSServer: dnsmock.NewServer(). + Query("en.wikipedia.org.localhost SOA", dnsmock.CNAME("dyna.wikimedia.org.localhost")). + Query("wikipedia.org.localhost SOA", dnsmock.SOA("")). + Query("wikipedia.org.localhost NS", + dnsmock.Answer( + fakeNS("wikipedia.org.localhost.", "ns0.wikimedia.org.localhost."), + fakeNS("wikipedia.org.localhost.", "ns1.wikimedia.org.localhost."), + fakeNS("wikipedia.org.localhost.", "ns2.wikimedia.org.localhost."), + ), + ), + expected: []string{"ns0.wikimedia.org.localhost.", "ns1.wikimedia.org.localhost.", "ns2.wikimedia.org.localhost."}, }, { - fqdn: "www.google.com.", - nss: []string{"ns1.google.com.", "ns2.google.com.", "ns3.google.com.", "ns4.google.com."}, + fqdn: "www.google.com.localhost.", + fakeDNSServer: dnsmock.NewServer(). + Query("www.google.com.localhost. SOA", dnsmock.Noop). + Query("google.com.localhost. SOA", dnsmock.SOA("")). + Query("google.com.localhost. NS", + dnsmock.Answer( + fakeNS("google.com.localhost.", "ns1.google.com.localhost."), + fakeNS("google.com.localhost.", "ns2.google.com.localhost."), + fakeNS("google.com.localhost.", "ns3.google.com.localhost."), + fakeNS("google.com.localhost.", "ns4.google.com.localhost."), + ), + ), + expected: []string{"ns1.google.com.localhost.", "ns2.google.com.localhost.", "ns3.google.com.localhost.", "ns4.google.com.localhost."}, }, { - fqdn: "mail.proton.me.", - nss: []string{"ns1.proton.me.", "ns2.proton.me.", "ns3.proton.me."}, + fqdn: "mail.proton.me.localhost.", + fakeDNSServer: dnsmock.NewServer(). + Query("mail.proton.me.localhost. SOA", dnsmock.Noop). + Query("proton.me.localhost. SOA", dnsmock.SOA("")). + Query("proton.me.localhost. NS", + dnsmock.Answer( + fakeNS("proton.me.localhost.", "ns1.proton.me.localhost."), + fakeNS("proton.me.localhost.", "ns2.proton.me.localhost."), + fakeNS("proton.me.localhost.", "ns3.proton.me.localhost."), + ), + ), + expected: []string{"ns1.proton.me.localhost.", "ns2.proton.me.localhost.", "ns3.proton.me.localhost."}, }, } for _, test := range testCases { t.Run(test.fqdn, func(t *testing.T) { - t.Parallel() + useAsNameserver(t, test.fakeDNSServer.Build(t)) nss, err := lookupNameservers(test.fqdn) require.NoError(t, err) sort.Strings(nss) - sort.Strings(test.nss) + sort.Strings(test.expected) - assert.Equal(t, test.nss, nss) + assert.Equal(t, test.expected, nss) }) } } -func TestLookupNameserversErr(t *testing.T) { +func Test_lookupNameserversErr(t *testing.T) { testCases := []struct { - desc string - fqdn string - error string + desc string + fqdn string + fakeDNSServer *dnsmock.Builder + error string }{ { - desc: "invalid tld", - fqdn: "example.invalid.", - error: "could not find zone", + desc: "NXDOMAIN", + fqdn: "example.invalid.", + fakeDNSServer: dnsmock.NewServer(). + Query(". SOA", dnsmock.Error(dns.RcodeNameError)), + error: "could not find zone: [fqdn=example.invalid.] could not find the start of authority for 'example.invalid.' [question='invalid. IN SOA', code=NXDOMAIN]", + }, + { + desc: "NS error", + fqdn: "example.com.", + fakeDNSServer: dnsmock.NewServer(). + Query("example.com. SOA", dnsmock.SOA("")). + Query("example.com. NS", dnsmock.Error(dns.RcodeServerFailure)), + error: "[zone=example.com.] could not determine authoritative nameservers", + }, + { + desc: "empty NS", + fqdn: "example.com.", + fakeDNSServer: dnsmock.NewServer(). + Query("example.com. SOA", dnsmock.SOA("")). + Query("example.me NS", dnsmock.Noop), + error: "[zone=example.com.] could not determine authoritative nameservers", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - t.Parallel() + useAsNameserver(t, test.fakeDNSServer.Build(t)) _, err := lookupNameservers(test.fqdn) require.Error(t, err) - assert.Contains(t, err.Error(), test.error) + assert.EqualError(t, err, test.error) }) } } -var findXByFqdnTestCases = []struct { +type lookupSoaByFqdnTestCase struct { desc string fqdn string zone string primaryNs string nameservers []string expectedError string -}{ - { - desc: "domain is a CNAME", - fqdn: "mail.google.com.", - zone: "google.com.", - primaryNs: "ns1.google.com.", - nameservers: recursiveNameservers, - }, - { - desc: "domain is a non-existent subdomain", - fqdn: "foo.google.com.", - zone: "google.com.", - primaryNs: "ns1.google.com.", - nameservers: recursiveNameservers, - }, - { - desc: "domain is a eTLD", - fqdn: "example.com.ac.", - zone: "ac.", - primaryNs: "a0.nic.ac.", - nameservers: recursiveNameservers, - }, - { - desc: "domain is a cross-zone CNAME", - fqdn: "cross-zone-example.assets.sh.", - zone: "assets.sh.", - primaryNs: "gina.ns.cloudflare.com.", - nameservers: recursiveNameservers, - }, - { - desc: "NXDOMAIN", - fqdn: "test.lego.invalid.", - zone: "lego.invalid.", - nameservers: []string{"8.8.8.8:53"}, - expectedError: `[fqdn=test.lego.invalid.] could not find the start of authority for 'test.lego.invalid.' [question='invalid. IN SOA', code=NXDOMAIN]`, - }, - { - desc: "several non existent nameservers", - fqdn: "mail.google.com.", - zone: "google.com.", - primaryNs: "ns1.google.com.", - nameservers: []string{":7053", ":8053", "8.8.8.8:53"}, - }, - { - desc: "only non-existent nameservers", - fqdn: "mail.google.com.", - zone: "google.com.", - nameservers: []string{":7053", ":8053", ":9053"}, - // use only the start of the message because the port changes with each call: 127.0.0.1:XXXXX->127.0.0.1:7053. - expectedError: "[fqdn=mail.google.com.] could not find the start of authority for 'mail.google.com.': DNS call error: read udp ", - }, - { - desc: "no nameservers", - fqdn: "test.example.com.", - zone: "example.com.", - nameservers: []string{}, - expectedError: "[fqdn=test.example.com.] could not find the start of authority for 'test.example.com.': empty list of nameservers", - }, +} + +func lookupSoaByFqdnTestCases(t *testing.T) []lookupSoaByFqdnTestCase { + t.Helper() + + return []lookupSoaByFqdnTestCase{ + { + desc: "domain is a CNAME", + fqdn: "mail.example.com.", + zone: "example.com.", + primaryNs: "ns1.example.com.", + nameservers: []string{ + dnsmock.NewServer(). + Query("mail.example.com. SOA", dnsmock.CNAME("example.com.")). + Query("example.com. SOA", dnsmock.SOA("")). + Build(t). + String(), + }, + }, + { + desc: "domain is a non-existent subdomain", + fqdn: "foo.example.com.", + zone: "example.com.", + primaryNs: "ns1.example.com.", + nameservers: []string{ + dnsmock.NewServer(). + Query("foo.example.com. SOA", dnsmock.Error(dns.RcodeNameError)). + Query("example.com. SOA", dnsmock.SOA("")). + Build(t). + String(), + }, + }, + { + desc: "domain is a eTLD", + fqdn: "example.com.ac.", + zone: "ac.", + primaryNs: "ns1.nic.ac.", + nameservers: []string{ + dnsmock.NewServer(). + Query("example.com.ac. SOA", dnsmock.Error(dns.RcodeNameError)). + Query("com.ac. SOA", dnsmock.Error(dns.RcodeNameError)). + Query("ac. SOA", dnsmock.SOA("")). + Build(t). + String(), + }, + }, + { + desc: "domain is a cross-zone CNAME", + fqdn: "cross-zone-example.example.com.", + zone: "example.com.", + primaryNs: "ns1.example.com.", + nameservers: []string{ + dnsmock.NewServer(). + Query("cross-zone-example.example.com. SOA", dnsmock.CNAME("example.org.")). + Query("example.com. SOA", dnsmock.SOA("")). + Build(t). + String(), + }, + }, + { + desc: "NXDOMAIN", + fqdn: "test.lego.invalid.", + zone: "lego.invalid.", + nameservers: []string{ + dnsmock.NewServer(). + Query("test.lego.invalid. SOA", dnsmock.Error(dns.RcodeNameError)). + Query("lego.invalid. SOA", dnsmock.Error(dns.RcodeNameError)). + Query("invalid. SOA", dnsmock.Error(dns.RcodeNameError)). + Build(t). + String(), + }, + expectedError: `[fqdn=test.lego.invalid.] could not find the start of authority for 'test.lego.invalid.' [question='invalid. IN SOA', code=NXDOMAIN]`, + }, + { + desc: "several non existent nameservers", + fqdn: "mail.example.com.", + zone: "example.com.", + primaryNs: "ns1.example.com.", + nameservers: []string{ + ":7053", + ":8053", + dnsmock.NewServer(). + Query("mail.example.com. SOA", dnsmock.CNAME("example.com.")). + Query("example.com. SOA", dnsmock.SOA("")). + Build(t). + String(), + }, + }, + { + desc: "only non-existent nameservers", + fqdn: "mail.example.com.", + zone: "example.com.", + nameservers: []string{":7053", ":8053", ":9053"}, + // use only the start of the message because the port changes with each call: 127.0.0.1:XXXXX->127.0.0.1:7053. + expectedError: "[fqdn=mail.example.com.] could not find the start of authority for 'mail.example.com.': DNS call error: read udp ", + }, + { + desc: "no nameservers", + fqdn: "test.example.com.", + zone: "example.com.", + nameservers: []string{}, + expectedError: "[fqdn=test.example.com.] could not find the start of authority for 'test.example.com.': empty list of nameservers", + }, + } } func TestFindZoneByFqdnCustom(t *testing.T) { - for _, test := range findXByFqdnTestCases { + for _, test := range lookupSoaByFqdnTestCases(t) { t.Run(test.desc, func(t *testing.T) { ClearFqdnCache() @@ -153,7 +252,7 @@ func TestFindZoneByFqdnCustom(t *testing.T) { } func TestFindPrimaryNsByFqdnCustom(t *testing.T) { - for _, test := range findXByFqdnTestCases { + for _, test := range lookupSoaByFqdnTestCases(t) { t.Run(test.desc, func(t *testing.T) { ClearFqdnCache() @@ -169,7 +268,7 @@ func TestFindPrimaryNsByFqdnCustom(t *testing.T) { } } -func TestResolveConfServers(t *testing.T) { +func Test_getNameservers_ResolveConfServers(t *testing.T) { testCases := []struct { fixture string expected []string diff --git a/challenge/dns01/precheck.go b/challenge/dns01/precheck.go index 706e8dbec..e10efa33e 100644 --- a/challenge/dns01/precheck.go +++ b/challenge/dns01/precheck.go @@ -9,6 +9,10 @@ import ( "github.com/miekg/dns" ) +// defaultNameserverPort used by authoritative NS. +// This is for tests only. +var defaultNameserverPort = "53" + // PreCheckFunc checks DNS propagation before notifying ACME that the DNS challenge is ready. type PreCheckFunc func(fqdn, value string) (bool, error) @@ -121,7 +125,7 @@ func (p preCheck) checkDNSPropagation(fqdn, value string) (bool, error) { func checkNameserversPropagation(fqdn, value string, nameservers []string, addPort bool) (bool, error) { for _, ns := range nameservers { if addPort { - ns = net.JoinHostPort(ns, "53") + ns = net.JoinHostPort(ns, defaultNameserverPort) } r, err := dnsQuery(fqdn, dns.TypeTXT, []string{ns}, false) diff --git a/challenge/dns01/precheck_test.go b/challenge/dns01/precheck_test.go index 1f3ecbf7e..bda8c781e 100644 --- a/challenge/dns01/precheck_test.go +++ b/challenge/dns01/precheck_test.go @@ -3,40 +3,73 @@ package dns01 import ( "testing" + "github.com/go-acme/lego/v4/platform/tester/dnsmock" + "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestCheckDNSPropagation(t *testing.T) { +func Test_preCheck_checkDNSPropagation(t *testing.T) { + mockResolver(t, + dnsmock.NewServer(). + Query("ns0.lego.localhost. A", + dnsmock.Answer(fakeA("ns0.lego.localhost.", "127.0.0.1"))). + Query("ns1.lego.localhost. A", + dnsmock.Answer(fakeA("ns1.lego.localhost.", "127.0.0.1"))). + Query("example.com. TXT", + dnsmock.Answer( + fakeTXT("example.com.", "one"), + fakeTXT("example.com.", "two"), + fakeTXT("example.com.", "three"), + fakeTXT("example.com.", "four"), + fakeTXT("example.com.", "five"), + ), + ). + Build(t), + ) + + useAsNameserver(t, + dnsmock.NewServer(). + Query("acme-staging.api.example.com. SOA", dnsmock.Error(dns.RcodeNameError)). + Query("api.example.com. SOA", dnsmock.Error(dns.RcodeNameError)). + Query("example.com. SOA", dnsmock.SOA("")). + Query("example.com. NS", + dnsmock.Answer( + fakeNS("example.com.", "ns0.lego.localhost."), + fakeNS("example.com.", "ns1.lego.localhost."), + ), + ). + Build(t), + ) + testCases := []struct { - desc string - fqdn string - value string - expectError bool + desc string + fqdn string + value string + expectedError string }{ { desc: "success", - fqdn: "postman-echo.com.", - value: "postman-domain-verification=c85de626cb79d941310696e06558e2e790223802f3697dfbdcaf65510152d52c", + fqdn: "example.com.", + value: "four", }, { - desc: "no TXT record", - fqdn: "acme-staging.api.letsencrypt.org.", - value: "fe01=", - expectError: true, + desc: "no matching TXT record", + fqdn: "acme-staging.api.example.com.", + value: "fe01=", + expectedError: "did not return the expected TXT record [fqdn: acme-staging.api.example.com., value: fe01=]: one ,two ,three ,four ,five", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - t.Parallel() ClearFqdnCache() check := newPreCheck() ok, err := check.checkDNSPropagation(test.fqdn, test.value) - if test.expectError { - assert.Errorf(t, err, "PreCheckDNS must fail for %s", test.fqdn) + if test.expectedError != "" { + assert.ErrorContainsf(t, err, test.expectedError, "PreCheckDNS must fail for %s", test.fqdn) assert.False(t, ok, "PreCheckDNS must fail for %s", test.fqdn) } else { assert.NoErrorf(t, err, "PreCheckDNS failed for %s", test.fqdn) @@ -46,69 +79,67 @@ func TestCheckDNSPropagation(t *testing.T) { } } -func TestCheckAuthoritativeNss(t *testing.T) { +func Test_checkNameserversPropagation_authoritativeNss(t *testing.T) { testCases := []struct { - desc string - fqdn, value string - ns []string - expected bool + desc string + fqdn, value string + fakeDNSServer *dnsmock.Builder + expectedError string }{ { - desc: "TXT RR w/ expected value", - fqdn: "8.8.8.8.asn.routeviews.org.", - value: "151698.8.8.024", - ns: []string{"asnums.routeviews.org."}, - expected: true, + desc: "TXT RR w/ expected value", + // NS: asnums.routeviews.org. + fqdn: "8.8.8.8.asn.routeviews.org.", + value: "151698.8.8.024", + fakeDNSServer: dnsmock.NewServer(). + Query("8.8.8.8.asn.routeviews.org. TXT", + dnsmock.Answer( + fakeTXT("8.8.8.8.asn.routeviews.org.", "151698.8.8.024"), + ), + ), + }, + { + desc: "TXT RR w/ unexpected value", + // NS: asnums.routeviews.org. + fqdn: "8.8.8.8.asn.routeviews.org.", + value: "fe01=", + fakeDNSServer: dnsmock.NewServer(). + Query("8.8.8.8.asn.routeviews.org. TXT", + dnsmock.Answer( + fakeTXT("8.8.8.8.asn.routeviews.org.", "15169"), + fakeTXT("8.8.8.8.asn.routeviews.org.", "8.8.8.0"), + fakeTXT("8.8.8.8.asn.routeviews.org.", "24"), + ), + ), + expectedError: "did not return the expected TXT record [fqdn: 8.8.8.8.asn.routeviews.org., value: fe01=]: 15169 ,8.8.8.0 ,24", }, { desc: "No TXT RR", - fqdn: "ns1.google.com.", - ns: []string{"ns2.google.com."}, - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - ClearFqdnCache() - - ok, _ := checkNameserversPropagation(test.fqdn, test.value, test.ns, true) - assert.Equal(t, test.expected, ok, test.fqdn) - }) - } -} - -func TestCheckAuthoritativeNssErr(t *testing.T) { - testCases := []struct { - desc string - fqdn, value string - ns []string - error string - }{ - { - desc: "TXT RR /w unexpected value", - fqdn: "8.8.8.8.asn.routeviews.org.", - value: "fe01=", - ns: []string{"asnums.routeviews.org."}, - error: "did not return the expected TXT record", - }, - { - desc: "No TXT RR", + // NS: ns2.google.com. fqdn: "ns1.google.com.", value: "fe01=", - ns: []string{"ns2.google.com."}, - error: "did not return the expected TXT record", + fakeDNSServer: dnsmock.NewServer(). + Query("ns1.google.com.", dnsmock.Noop), + expectedError: "did not return the expected TXT record [fqdn: ns1.google.com., value: fe01=]: ", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - t.Parallel() ClearFqdnCache() - _, err := checkNameserversPropagation(test.fqdn, test.value, test.ns, true) - require.Error(t, err) - assert.Contains(t, err.Error(), test.error) + addr := test.fakeDNSServer.Build(t) + + ok, err := checkNameserversPropagation(test.fqdn, test.value, []string{addr.String()}, false) + + if test.expectedError == "" { + require.NoError(t, err) + assert.True(t, ok) + } else { + require.Error(t, err) + require.ErrorContains(t, err, test.expectedError) + assert.False(t, ok) + } }) } } diff --git a/platform/tester/dnsmock/dnsmock.go b/platform/tester/dnsmock/dnsmock.go new file mode 100644 index 000000000..6cb4f45b8 --- /dev/null +++ b/platform/tester/dnsmock/dnsmock.go @@ -0,0 +1,191 @@ +package dnsmock + +import ( + "fmt" + "math" + "net" + "strings" + "sync" + "testing" + "time" + + "github.com/miekg/dns" + "github.com/stretchr/testify/require" +) + +const noType uint16 = math.MaxUint16 + +type Option func(*dns.Server) error + +type Builder struct { + // domain -> op -> type + routes map[string]map[int]map[uint16]dns.Handler + + stringToType map[string]uint16 +} + +func NewServer() *Builder { + stringToType := make(map[string]uint16) + for typ, str := range dns.TypeToString { + stringToType[str] = typ + } + + return &Builder{ + routes: make(map[string]map[int]map[uint16]dns.Handler), + stringToType: stringToType, + } +} + +func (b *Builder) Query(pattern string, handler dns.HandlerFunc) *Builder { + route, err := b.route(pattern, dns.OpcodeQuery, handler) + if err != nil { + panic(err.Error()) + } + + return route +} + +func (b *Builder) Update(pattern string, handler dns.HandlerFunc) *Builder { + route, err := b.route(pattern, dns.OpcodeUpdate, handler) + if err != nil { + panic(err.Error()) + } + + return route +} + +func (b *Builder) route(pattern string, op int, handler dns.HandlerFunc) (*Builder, error) { + parts := strings.Fields(pattern) + + domain := parts[0] + + _, ok := dns.IsDomainName(domain) + if !ok { + return nil, fmt.Errorf("%s: invalid domain: %s", dns.OpcodeToString[op], domain) + } + + if _, ok := b.routes[domain]; !ok { + b.routes[domain] = make(map[int]map[uint16]dns.Handler) + } + + if _, ok := b.routes[domain][op]; !ok { + b.routes[domain][op] = make(map[uint16]dns.Handler) + } + + if _, ok := b.routes[domain][op][noType]; ok { + return nil, fmt.Errorf("%s: a global route already exists for the domain: %s", dns.OpcodeToString[op], domain) + } + + switch len(parts) { + case 1: + if len(b.routes[domain][op]) > 0 { + return nil, fmt.Errorf("%s: global route and specific routes cannot be mixed for the same domain: %s", dns.OpcodeToString[op], domain) + } + + b.routes[domain][op][noType] = handler + + return b, nil + + case 2: + raw := parts[1] + + qType, ok := b.stringToType[raw] + if !ok { + return nil, fmt.Errorf("%s: unknown type: %s", dns.OpcodeToString[op], raw) + } + + if _, ok := b.routes[domain][op][qType]; ok { + return nil, fmt.Errorf("%s: duplicate route: %s", dns.OpcodeToString[op], pattern) + } + + b.routes[domain][op][qType] = handler + + return b, nil + + default: + return nil, fmt.Errorf("%s: invalid pattern: %s", dns.OpcodeToString[op], pattern) + } +} + +func (b *Builder) Build(t *testing.T, options ...Option) net.Addr { + t.Helper() + + mux := dns.NewServeMux() + + server := &dns.Server{ + Addr: "127.0.0.1:0", + Net: "udp", + ReadTimeout: time.Hour, + WriteTimeout: time.Hour, + Handler: mux, + MsgAcceptFunc: func(dh dns.Header) dns.MsgAcceptAction { + // bypass defaultMsgAcceptFunc to allow dynamic update (https://github.com/miekg/dns/pull/830) + return dns.MsgAccept + }, + } + + for _, option := range options { + require.NoError(t, option(server)) + } + + for pattern, ops := range b.routes { + mux.HandleFunc(pattern, func(w dns.ResponseWriter, req *dns.Msg) { + mTypes, ok := ops[req.Opcode] + if !ok { + _ = w.WriteMsg(new(dns.Msg).SetRcode(req, dns.RcodeNotImplemented)) + + return + } + + if h, found := mTypes[noType]; found { + h.ServeDNS(w, req) + + return + } + + // For safety but it doesn't happen. + if len(req.Question) == 0 { + _ = w.WriteMsg(new(dns.Msg).SetRcode(req, dns.RcodeRefused)) + + return + } + + // For safety but it doesn't happen. + if req.Question[0].Qclass != dns.ClassINET { + _ = w.WriteMsg(new(dns.Msg).SetRcode(req, dns.RcodeRefused)) + + return + } + + // Works only for [Query]. + h, ok := mTypes[req.Question[0].Qtype] + if !ok { + _ = w.WriteMsg(new(dns.Msg).SetRcode(req, dns.RcodeNotImplemented)) + + return + } + + h.ServeDNS(w, req) + }) + } + + t.Cleanup(func() { + _ = server.Shutdown() + }) + + waitLock := sync.Mutex{} + waitLock.Lock() + + server.NotifyStartedFunc = waitLock.Unlock + + go func() { + err := server.ListenAndServe() + if err != nil { + t.Log(err) + } + }() + + waitLock.Lock() + + return server.PacketConn.LocalAddr() +} diff --git a/platform/tester/dnsmock/dnsmock_test.go b/platform/tester/dnsmock/dnsmock_test.go new file mode 100644 index 000000000..77a67a402 --- /dev/null +++ b/platform/tester/dnsmock/dnsmock_test.go @@ -0,0 +1,240 @@ +package dnsmock + +import ( + "testing" + "time" + + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestServer_Query_matchType(t *testing.T) { + addr := NewServer(). + Query("example.com. SOA", Noop). + Build(t) + + client := &dns.Client{Timeout: 1 * time.Second} + + m := new(dns.Msg).SetQuestion("example.com.", dns.TypeSOA) + + r, _, err := client.Exchange(m, addr.String()) + require.NoError(t, err) + + require.Equalf(t, dns.RcodeSuccess, r.Rcode, + "expected %s, got %s", dns.RcodeToString[dns.RcodeSuccess], dns.RcodeToString[r.Rcode]) + assert.Equal(t, m.Question, r.Question) +} + +func TestServer_Query_noType(t *testing.T) { + addr := NewServer(). + Query("example.com.", Noop). + Build(t) + + client := &dns.Client{Timeout: 1 * time.Second} + + m := new(dns.Msg).SetQuestion("example.com.", dns.TypeSOA) + + r, _, err := client.Exchange(m, addr.String()) + require.NoError(t, err) + + require.Equalf(t, dns.RcodeSuccess, r.Rcode, + "expected %s, got %s", dns.RcodeToString[dns.RcodeSuccess], dns.RcodeToString[r.Rcode]) + assert.Equal(t, m.Question, r.Question) +} + +func TestServer_Query_noMatch_domain(t *testing.T) { + addr := NewServer(). + Query("example.com. SOA", Noop). + Build(t) + + client := &dns.Client{Timeout: 1 * time.Second} + + m := new(dns.Msg).SetQuestion("example.org.", dns.TypeSOA) + + r, _, err := client.Exchange(m, addr.String()) + require.NoError(t, err) + + require.Equalf(t, dns.RcodeRefused, r.Rcode, + "expected %s, got %s", dns.RcodeToString[dns.RcodeRefused], dns.RcodeToString[r.Rcode]) + assert.Equal(t, m.Question, r.Question) +} + +func TestServer_Query_noMatch_type(t *testing.T) { + addr := NewServer(). + Query("example.com. SOA", Noop). + Build(t) + + client := &dns.Client{Timeout: 1 * time.Second} + + m := new(dns.Msg).SetQuestion("example.com.", dns.TypeTXT) + + r, _, err := client.Exchange(m, addr.String()) + require.NoError(t, err) + + require.Equalf(t, dns.RcodeNotImplemented, r.Rcode, + "expected %s, got %s", dns.RcodeToString[dns.RcodeNotImplemented], dns.RcodeToString[r.Rcode]) + assert.Equal(t, m.Question, r.Question) +} + +func TestServer_Query_noMatch_opType(t *testing.T) { + addr := NewServer(). + Query("example.com.", Noop). + Build(t) + + client := &dns.Client{Timeout: 1 * time.Second} + + m := new(dns.Msg).SetUpdate("example.com.") + m.Insert([]dns.RR{ + &dns.TXT{ + Hdr: dns.RR_Header{Name: "example.com.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 1}, + Txt: []string{"foo"}, + }, + }) + + r, _, err := client.Exchange(m, addr.String()) + require.NoError(t, err) + + require.Equalf(t, dns.RcodeNotImplemented, r.Rcode, + "expected %s, got %s", dns.RcodeToString[dns.RcodeNotImplemented], dns.RcodeToString[r.Rcode]) + assert.Equal(t, m.Question, r.Question) +} + +func TestServer_Query_unknownType(t *testing.T) { + assert.PanicsWithValue(t, "QUERY: unknown type: ABC", func() { + NewServer(). + Query("example.com. ABC", Noop). + Build(t) + }) +} + +func TestServer_Query_duplicate(t *testing.T) { + assert.PanicsWithValue(t, "QUERY: duplicate route: example.com. SOA", func() { + NewServer(). + Query("example.com. SOA", Noop). + Query("example.com. SOA", Noop). + Build(t) + }) +} + +func TestServer_Query_duplicateGlobal(t *testing.T) { + assert.PanicsWithValue(t, "QUERY: a global route already exists for the domain: example.com.", func() { + NewServer(). + Query("example.com.", Noop). + Query("example.com.", Noop). + Build(t) + }) +} + +func TestServer_Query_mixed(t *testing.T) { + assert.PanicsWithValue(t, "QUERY: global route and specific routes cannot be mixed for the same domain: example.com.", func() { + NewServer(). + Query("example.com. SOA", Noop). + Query("example.com.", Noop). + Build(t) + }) +} + +func TestServer_Query_invalidDomain(t *testing.T) { + assert.PanicsWithValue(t, "QUERY: invalid domain: .example.com.", func() { + NewServer(). + Query(".example.com. SOA", Noop). + Build(t) + }) +} + +func TestServer_Query_invalidPattern(t *testing.T) { + assert.PanicsWithValue(t, "QUERY: invalid pattern: example.com. SOA 13", func() { + NewServer(). + Query("example.com. SOA 13", Noop). + Build(t) + }) +} + +func TestServer_Update(t *testing.T) { + addr := NewServer(). + Update("example.com.", Noop). + Build(t) + + client := &dns.Client{Timeout: 1 * time.Second} + + m := new(dns.Msg).SetUpdate("example.com.") + m.Insert([]dns.RR{ + &dns.TXT{ + Hdr: dns.RR_Header{Name: "example.com.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 1}, + Txt: []string{"foo"}, + }, + }) + + r, _, err := client.Exchange(m, addr.String()) + require.NoError(t, err) + + require.Equalf(t, dns.RcodeSuccess, r.Rcode, + "expected %s, got %s", dns.RcodeToString[dns.RcodeSuccess], dns.RcodeToString[r.Rcode]) + assert.Equal(t, m.Question, r.Question) +} + +func TestServer_Update_noMatch_domain(t *testing.T) { + addr := NewServer(). + Update("example.com.", Noop). + Build(t) + + client := &dns.Client{Timeout: 1 * time.Second} + + m := new(dns.Msg).SetUpdate("example.org.") + m.Insert([]dns.RR{ + &dns.TXT{ + Hdr: dns.RR_Header{Name: "example.org.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 1}, + Txt: []string{"foo"}, + }, + }) + + r, _, err := client.Exchange(m, addr.String()) + require.NoError(t, err) + + require.Equalf(t, dns.RcodeRefused, r.Rcode, + "expected %s, got %s", dns.RcodeToString[dns.RcodeRefused], dns.RcodeToString[r.Rcode]) + assert.Equal(t, m.Question, r.Question) +} + +func TestServer_Update_noMatch_opType(t *testing.T) { + addr := NewServer(). + Update("example.com.", Noop). + Build(t) + + client := &dns.Client{Timeout: 1 * time.Second} + + m := new(dns.Msg).SetQuestion("example.com.", dns.TypeTXT) + + r, _, err := client.Exchange(m, addr.String()) + require.NoError(t, err) + + require.Equalf(t, dns.RcodeNotImplemented, r.Rcode, + "expected %s, got %s", dns.RcodeToString[dns.RcodeNotImplemented], dns.RcodeToString[r.Rcode]) + assert.Equal(t, m.Question, r.Question) +} + +func TestServer_Update_duplicate(t *testing.T) { + assert.PanicsWithValue(t, "UPDATE: a global route already exists for the domain: example.com.", func() { + NewServer(). + Update("example.com.", Noop). + Update("example.com.", Noop). + Build(t) + }) +} + +func TestServer_Update_invalidDomain(t *testing.T) { + assert.PanicsWithValue(t, "UPDATE: invalid domain: .example.com.", func() { + NewServer(). + Update(".example.com.", Noop). + Build(t) + }) +} + +func TestServer_Update_invalidPattern(t *testing.T) { + assert.PanicsWithValue(t, "UPDATE: invalid pattern: example.com. SOA 13", func() { + NewServer(). + Update("example.com. SOA 13", Noop). + Build(t) + }) +} diff --git a/platform/tester/dnsmock/handlers.go b/platform/tester/dnsmock/handlers.go new file mode 100644 index 000000000..e1b047318 --- /dev/null +++ b/platform/tester/dnsmock/handlers.go @@ -0,0 +1,76 @@ +package dnsmock + +import ( + "fmt" + + "github.com/miekg/dns" +) + +func DumpRequest() dns.HandlerFunc { + return func(w dns.ResponseWriter, req *dns.Msg) { + fmt.Println(req) + + Noop(w, req) + } +} + +func SOA(name string) dns.HandlerFunc { + return func(w dns.ResponseWriter, req *dns.Msg) { + if name == "" { + name = req.Question[0].Name + } + + // Handle TLD + base := name + if dns.CountLabel(req.Question[0].Name) == 1 { + base = "nic." + req.Question[0].Name + } + + answer := &dns.SOA{ + Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 120}, + Ns: "ns1." + base, + Mbox: "admin." + base, + Serial: 2016022801, + Refresh: 28800, + Retry: 7200, + Expire: 2419200, + Minttl: 1200, + } + + Answer(answer)(w, req) + } +} + +func CNAME(target string) dns.HandlerFunc { + return func(w dns.ResponseWriter, req *dns.Msg) { + answer := &dns.CNAME{ + Hdr: dns.RR_Header{Name: req.Question[0].Name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 1}, + Target: dns.Fqdn(target), + } + + Answer(answer)(w, req) + } +} + +func Noop(w dns.ResponseWriter, req *dns.Msg) { + _ = w.WriteMsg(new(dns.Msg).SetReply(req)) +} + +func Error(rcode int) dns.HandlerFunc { + return func(w dns.ResponseWriter, req *dns.Msg) { + _ = w.WriteMsg(new(dns.Msg).SetRcode(req, rcode)) + } +} + +func Answer(answer ...dns.RR) func(w dns.ResponseWriter, req *dns.Msg) { + return func(w dns.ResponseWriter, req *dns.Msg) { + m := new(dns.Msg).SetReply(req) + + m.Answer = answer + + err := w.WriteMsg(m) + if err != nil { + panic(err.Error()) + } + } +} diff --git a/platform/tester/dnsmock/handlers_test.go b/platform/tester/dnsmock/handlers_test.go new file mode 100644 index 000000000..13cdc0e2d --- /dev/null +++ b/platform/tester/dnsmock/handlers_test.go @@ -0,0 +1,156 @@ +package dnsmock + +import ( + "testing" + "time" + + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSOA_self(t *testing.T) { + addr := NewServer(). + Query("example.com. SOA", SOA("")). + Build(t) + + client := &dns.Client{Timeout: 1 * time.Second} + + m := new(dns.Msg).SetQuestion("example.com.", dns.TypeSOA) + + r, _, err := client.Exchange(m, addr.String()) + require.NoError(t, err) + + expectedSOA := []dns.RR{&dns.SOA{ + Hdr: dns.RR_Header{Name: "example.com.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 120, Rdlength: 56}, + Ns: "ns1.example.com.", + Mbox: "admin.example.com.", + Serial: 2016022801, + Refresh: 28800, + Retry: 7200, + Expire: 2419200, + Minttl: 1200, + }} + + require.Equal(t, dns.RcodeSuccess, r.Rcode) + assert.Equal(t, expectedSOA, r.Answer) + assert.Equal(t, m.Question, r.Question) +} + +func TestSOA_differentDomain(t *testing.T) { + addr := NewServer(). + Query("example.com. SOA", SOA("example.org.")). + Build(t) + + client := &dns.Client{Timeout: 1 * time.Second} + + m := new(dns.Msg).SetQuestion("example.com.", dns.TypeSOA) + + r, _, err := client.Exchange(m, addr.String()) + require.NoError(t, err) + + require.Equalf(t, dns.RcodeSuccess, r.Rcode, + "expected %s, got %s", dns.RcodeToString[dns.RcodeSuccess], dns.RcodeToString[r.Rcode]) + + expectedSOA := []dns.RR{&dns.SOA{ + Hdr: dns.RR_Header{Name: "example.org.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 120, Rdlength: 56}, + Ns: "ns1.example.org.", + Mbox: "admin.example.org.", + Serial: 2016022801, + Refresh: 28800, + Retry: 7200, + Expire: 2419200, + Minttl: 1200, + }} + + assert.Equal(t, expectedSOA, r.Answer) + assert.Equal(t, m.Question, r.Question) +} + +func TestSOA_tld(t *testing.T) { + addr := NewServer(). + Query("com. SOA", SOA("")). + Build(t) + + client := &dns.Client{Timeout: 1 * time.Second} + + m := new(dns.Msg).SetQuestion("com.", dns.TypeSOA) + + r, _, err := client.Exchange(m, addr.String()) + require.NoError(t, err) + + require.Equalf(t, dns.RcodeSuccess, r.Rcode, + "expected %s, got %s", dns.RcodeToString[dns.RcodeSuccess], dns.RcodeToString[r.Rcode]) + + expectedSOA := []dns.RR{&dns.SOA{ + Hdr: dns.RR_Header{Name: "com.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 120, Rdlength: 48}, + Ns: "ns1.nic.com.", + Mbox: "admin.nic.com.", + Serial: 2016022801, + Refresh: 28800, + Retry: 7200, + Expire: 2419200, + Minttl: 1200, + }} + + assert.Equal(t, expectedSOA, r.Answer) + assert.Equal(t, m.Question, r.Question) +} + +func TestCNAME(t *testing.T) { + addr := NewServer(). + Query("example.com. CNAME", CNAME("example.org.")). + Build(t) + + client := &dns.Client{Timeout: 1 * time.Second} + + m := new(dns.Msg).SetQuestion("example.com.", dns.TypeCNAME) + + r, _, err := client.Exchange(m, addr.String()) + require.NoError(t, err) + + require.Equalf(t, dns.RcodeSuccess, r.Rcode, + "expected %s, got %s", dns.RcodeToString[dns.RcodeSuccess], dns.RcodeToString[r.Rcode]) + + expectedCNAME := []dns.RR{&dns.CNAME{ + Hdr: dns.RR_Header{Name: "example.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 1, Rdlength: 13}, + Target: "example.org.", + }} + + assert.Equal(t, expectedCNAME, r.Answer) + assert.Equal(t, m.Question, r.Question) +} + +func TestNoop(t *testing.T) { + addr := NewServer(). + Query("example.com. CNAME", Noop). + Build(t) + + client := &dns.Client{Timeout: 1 * time.Second} + + m := new(dns.Msg).SetQuestion("example.com.", dns.TypeCNAME) + + r, _, err := client.Exchange(m, addr.String()) + require.NoError(t, err) + + require.Equalf(t, dns.RcodeSuccess, r.Rcode, + "expected %s, got %s", dns.RcodeToString[dns.RcodeSuccess], dns.RcodeToString[r.Rcode]) + assert.Equal(t, m.Question, r.Question) +} + +func TestError(t *testing.T) { + addr := NewServer(). + Query("example.com. CNAME", Error(dns.RcodeNameError)). + Build(t) + + client := &dns.Client{Timeout: 1 * time.Second} + + m := new(dns.Msg).SetQuestion("example.com.", dns.TypeCNAME) + + r, _, err := client.Exchange(m, addr.String()) + require.NoError(t, err) + + require.Equalf(t, dns.RcodeNameError, r.Rcode, + "expected %s, got %s", dns.RcodeToString[dns.RcodeNameError], dns.RcodeToString[r.Rcode]) + assert.Equal(t, m.Question, r.Question) +} diff --git a/providers/dns/rfc2136/rfc2136.go b/providers/dns/rfc2136/rfc2136.go index 6b5c47072..84655b450 100644 --- a/providers/dns/rfc2136/rfc2136.go +++ b/providers/dns/rfc2136/rfc2136.go @@ -131,7 +131,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { config.TSIGSecret = "" } else { // zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2) - config.TSIGKey = strings.ToLower(dns.Fqdn(config.TSIGKey)) + config.TSIGKey = dns.CanonicalName(config.TSIGKey) } if config.TSIGAlgorithm == "" { @@ -193,14 +193,14 @@ func (d *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { } // Create RR - rr := new(dns.TXT) - rr.Hdr = dns.RR_Header{Name: fqdn, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: uint32(ttl)} - rr.Txt = []string{value} - rrs := []dns.RR{rr} + rrs := []dns.RR{&dns.TXT{ + Hdr: dns.RR_Header{Name: fqdn, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: uint32(ttl)}, + Txt: []string{value}, + }} // Create dynamic update packet - m := new(dns.Msg) - m.SetUpdate(zone) + m := new(dns.Msg).SetUpdate(zone) + switch action { case "INSERT": // Always remove old challenge left over from who knows what. diff --git a/providers/dns/rfc2136/rfc2136_test.go b/providers/dns/rfc2136/rfc2136_test.go index 31414a4d4..1dc7270d2 100644 --- a/providers/dns/rfc2136/rfc2136_test.go +++ b/providers/dns/rfc2136/rfc2136_test.go @@ -2,23 +2,21 @@ package rfc2136 import ( "bytes" - "fmt" "strings" - "sync" "testing" "time" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/dnsmock" "github.com/miekg/dns" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) const ( fakeDomain = "123456789.www.example.com" fakeKeyAuth = "123d==" - fakeValue = "Now36o-3BmlB623-0c1qCIUmgWVVmDJb88KGl24pqpo" + fakeValue = "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" fakeFqdn = "_acme-challenge.123456789.www.example.com." fakeZone = "example.com." fakeTTL = 120 @@ -162,33 +160,16 @@ func TestNewDNSProviderConfig(t *testing.T) { } } -func TestCanaryLocalTestServer(t *testing.T) { +func TestDNSProvider_Present_success(t *testing.T) { dns01.ClearFqdnCache() - mux, addr := runLocalDNSTestServer(t, false) - mux.HandleFunc("example.com.", serverHandlerHello) - - c := new(dns.Client) - m := new(dns.Msg) - - m.SetQuestion("example.com.", dns.TypeTXT) - - r, _, err := c.Exchange(m, addr) - require.NoError(t, err, "Failed to communicate with test server") - assert.Len(t, r.Extra, 1, "Failed to communicate with test server") - - txt := r.Extra[0].(*dns.TXT).Txt[0] - assert.Equal(t, "Hello world", txt) -} - -func TestServerSuccess(t *testing.T) { - dns01.ClearFqdnCache() - - mux, addr := runLocalDNSTestServer(t, false) - mux.HandleFunc(fakeZone, serverHandlerReturnSuccess) + addr := dnsmock.NewServer(). + Query(fakeZone+" SOA", dnsmock.SOA("")). + Update(fakeZone+" SOA", dnsmock.Noop). + Build(t) config := NewDefaultConfig() - config.Nameserver = addr + config.Nameserver = addr.String() provider, err := NewDNSProviderConfig(config) require.NoError(t, err) @@ -197,14 +178,72 @@ func TestServerSuccess(t *testing.T) { require.NoError(t, err) } -func TestServerError(t *testing.T) { +func TestDNSProvider_Present_success_updatePacket(t *testing.T) { dns01.ClearFqdnCache() - mux, addr := runLocalDNSTestServer(t, false) - mux.HandleFunc(fakeZone, serverHandlerReturnErr) + reqChan := make(chan *dns.Msg, 1) + + addr := dnsmock.NewServer(). + Query("_acme-challenge.123456789.www.example.com. SOA", dnsmock.SOA(fakeZone)). + Update(fakeZone+" SOA", func(w dns.ResponseWriter, req *dns.Msg) { + dnsmock.Noop(w, req) + + // Only talk back when it is not the SOA RR. + reqChan <- req + }). + Build(t) config := NewDefaultConfig() - config.Nameserver = addr + config.Nameserver = addr.String() + + provider, err := NewDNSProviderConfig(config) + require.NoError(t, err) + + err = provider.Present(fakeDomain, "", fakeKeyAuth) + require.NoError(t, err) + + select { + case <-time.After(time.Second): + t.Fatal("timeout waiting for request") + + case rcvMsg := <-reqChan: + txtRR := &dns.TXT{ + Hdr: dns.RR_Header{Name: fakeFqdn, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: fakeTTL}, + Txt: []string{fakeValue}, + } + + m := new(dns.Msg).SetUpdate(fakeZone) + + m.RemoveRRset([]dns.RR{txtRR}) + m.Insert([]dns.RR{txtRR}) + + expected, err := m.Pack() + require.NoError(t, err, "error packing") + + rcvMsg.Id = m.Id + + actual, err := rcvMsg.Pack() + require.NoError(t, err, "error packing") + + if !bytes.Equal(actual, expected) { + tmp := new(dns.Msg) + require.NoError(t, tmp.Unpack(actual)) + + t.Errorf("Expected msg:\n%s", m) + t.Errorf("Actual msg:\n%s", tmp) + } + } +} + +func TestDNSProvider_Present_error(t *testing.T) { + dns01.ClearFqdnCache() + + addr := dnsmock.NewServer(). + Query(fakeZone+" SOA", dnsmock.Error(dns.RcodeNotZone)). + Build(t) + + config := NewDefaultConfig() + config.Nameserver = addr.String() provider, err := NewDNSProviderConfig(config) require.NoError(t, err) @@ -216,14 +255,20 @@ func TestServerError(t *testing.T) { } } -func TestTsigClient(t *testing.T) { +func TestDNSProvider_Present_tsig_success(t *testing.T) { dns01.ClearFqdnCache() - mux, addr := runLocalDNSTestServer(t, true) - mux.HandleFunc(fakeZone, serverHandlerReturnSuccess) + addr := dnsmock.NewServer(). + Query(fakeZone+" SOA", dnsmock.SOA("")). + Update(fakeZone+" SOA", handleTSIG). + Build(t, func(server *dns.Server) error { + server.TsigSecret = map[string]string{fakeTsigKey: fakeTsigSecret} + + return nil + }) config := NewDefaultConfig() - config.Nameserver = addr + config.Nameserver = addr.String() config.TSIGKey = fakeTsigKey config.TSIGSecret = fakeTsigSecret @@ -234,167 +279,50 @@ func TestTsigClient(t *testing.T) { require.NoError(t, err) } -func TestValidUpdatePacket(t *testing.T) { - reqChan := make(chan *dns.Msg, 10) - +func TestDNSProvider_Present_tsig_error(t *testing.T) { dns01.ClearFqdnCache() - mux, addr := runLocalDNSTestServer(t, false) - mux.HandleFunc(fakeZone, serverHandlerPassBackRequest(reqChan)) + addr := dnsmock.NewServer(). + Query(fakeZone+" SOA", dnsmock.SOA("")). + Update(fakeZone+" SOA", handleTSIG). + Build(t, func(server *dns.Server) error { + server.TsigSecret = map[string]string{"example.org": fakeTsigSecret} - txtRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN TXT %s", fakeFqdn, fakeTTL, fakeValue)) - - m := new(dns.Msg) - m.SetUpdate(fakeZone) - m.RemoveRRset([]dns.RR{txtRR}) - m.Insert([]dns.RR{txtRR}) - - expectStr := m.String() - - expect, err := m.Pack() - require.NoError(t, err, "error packing") + return nil + }) config := NewDefaultConfig() - config.Nameserver = addr + config.Nameserver = addr.String() + config.TSIGKey = fakeTsigKey + config.TSIGSecret = fakeTsigSecret provider, err := NewDNSProviderConfig(config) require.NoError(t, err) - err = provider.Present(fakeDomain, "", "1234d==") - require.NoError(t, err) - - rcvMsg := <-reqChan - rcvMsg.Id = m.Id - - actual, err := rcvMsg.Pack() - require.NoError(t, err, "error packing") - - if !bytes.Equal(actual, expect) { - tmp := new(dns.Msg) - if err := tmp.Unpack(actual); err != nil { - t.Fatalf("Error unpacking actual msg: %v", err) - } - t.Errorf("Expected msg:\n%s", expectStr) - t.Errorf("Actual msg:\n%v", tmp) - } + err = provider.Present(fakeDomain, "", fakeKeyAuth) + require.Error(t, err) + require.EqualError(t, err, "rfc2136: failed to insert: DNS update failed: server replied: NOTZONE") } -func runLocalDNSTestServer(t *testing.T, tsig bool) (*dns.ServeMux, string) { - t.Helper() - - mux := dns.NewServeMux() - - server := &dns.Server{ - Addr: "127.0.0.1:0", - Net: "udp", - ReadTimeout: time.Hour, - WriteTimeout: time.Hour, - MsgAcceptFunc: func(dh dns.Header) dns.MsgAcceptAction { - // bypass defaultMsgAcceptFunc to allow dynamic update (https://github.com/miekg/dns/pull/830) - return dns.MsgAccept - }, - Handler: mux, - } - - t.Cleanup(func() { - _ = server.Shutdown() - }) - - if tsig { - server.TsigSecret = map[string]string{fakeTsigKey: fakeTsigSecret} - } - - waitLock := sync.Mutex{} - waitLock.Lock() - - server.NotifyStartedFunc = waitLock.Unlock - - go func() { - err := server.ListenAndServe() - if err != nil { - t.Log(err) - } - }() - - waitLock.Lock() - - return mux, server.PacketConn.LocalAddr().String() -} - -func serverHandlerHello(w dns.ResponseWriter, req *dns.Msg) { +func handleTSIG(w dns.ResponseWriter, req *dns.Msg) { m := new(dns.Msg) - m.SetReply(req) - m.Extra = make([]dns.RR, 1) - m.Extra[0] = &dns.TXT{ - Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0}, - Txt: []string{"Hello world"}, + tsig := req.IsTsig() + if tsig == nil { + _ = w.WriteMsg(m.SetRcode(req, dns.RcodeRefused)) + return } - _ = w.WriteMsg(m) -} + err := w.TsigStatus() + if err != nil { + _ = w.WriteMsg(m.SetRcode(req, dns.RcodeNotZone)) -func serverHandlerReturnSuccess(w dns.ResponseWriter, req *dns.Msg) { - m := new(dns.Msg) - m.SetReply(req) - - if req.Opcode == dns.OpcodeQuery && req.Question[0].Qtype == dns.TypeSOA && req.Question[0].Qclass == dns.ClassINET { - // Return SOA to appease findZoneByFqdn() - m.Answer = []dns.RR{fakeSOAAnswer()} + return } - if t := req.IsTsig(); t != nil { - if w.TsigStatus() == nil { - // Validated - m.SetTsig(fakeZone, dns.HmacSHA1, 300, time.Now().Unix()) - } - } - - _ = w.WriteMsg(m) -} - -func serverHandlerReturnErr(w dns.ResponseWriter, req *dns.Msg) { - m := new(dns.Msg) - m.SetRcode(req, dns.RcodeNotZone) - - _ = w.WriteMsg(m) -} - -func serverHandlerPassBackRequest(reqChan chan *dns.Msg) func(w dns.ResponseWriter, req *dns.Msg) { - return func(w dns.ResponseWriter, req *dns.Msg) { - m := new(dns.Msg) - m.SetReply(req) - - if req.Opcode == dns.OpcodeQuery && req.Question[0].Qtype == dns.TypeSOA && req.Question[0].Qclass == dns.ClassINET { - // Return SOA to appease findZoneByFqdn() - m.Answer = []dns.RR{fakeSOAAnswer()} - } - - if t := req.IsTsig(); t != nil { - if w.TsigStatus() == nil { - // Validated - m.SetTsig(fakeZone, dns.HmacSHA1, 300, time.Now().Unix()) - } - } - - _ = w.WriteMsg(m) - - if req.Opcode != dns.OpcodeQuery || req.Question[0].Qtype != dns.TypeSOA || req.Question[0].Qclass != dns.ClassINET { - // Only talk back when it is not the SOA RR. - reqChan <- req - } - } -} - -func fakeSOAAnswer() *dns.SOA { - return &dns.SOA{ - Hdr: dns.RR_Header{Name: fakeZone, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: fakeTTL}, - Ns: "ns1." + fakeZone, - Mbox: "admin." + fakeZone, - Serial: 2016022801, - Refresh: 28800, - Retry: 7200, - Expire: 2419200, - Minttl: 1200, - } + // Validated + _ = w.WriteMsg(m. + SetReply(req). + SetTsig(tsig.Hdr.Name, tsig.Algorithm, tsig.Fudge, time.Now().Unix()), + ) } From 8a11af149fc7b64f178c76a6879811bb868d8c7e Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 19 Aug 2025 17:44:47 +0200 Subject: [PATCH 155/298] azuredns: pipeline credential support (#2621) --- docs/content/dns/zz_gen_azuredns.md | 4 + providers/dns/azuredns/azuredns.go | 129 ++++++------------------ providers/dns/azuredns/azuredns.toml | 4 + providers/dns/azuredns/credentials.go | 135 ++++++++++++++++++++++++++ 4 files changed, 172 insertions(+), 100 deletions(-) create mode 100644 providers/dns/azuredns/credentials.go diff --git a/docs/content/dns/zz_gen_azuredns.md b/docs/content/dns/zz_gen_azuredns.md index f9e7d3844..85feaae88 100644 --- a/docs/content/dns/zz_gen_azuredns.md +++ b/docs/content/dns/zz_gen_azuredns.md @@ -229,6 +229,10 @@ This authentication method can be specifically used by setting the `AZURE_AUTH_M Open ID Connect is a mechanism that establish a trust relationship between a running environment and the Azure AD identity provider. It can be enabled by setting the `AZURE_AUTH_METHOD` environment variable to `oidc`. +### Azure DevOps Pipelines + +It can be enabled by setting the `AZURE_AUTH_METHOD` environment variable to `pipeline`. + diff --git a/providers/dns/azuredns/azuredns.go b/providers/dns/azuredns/azuredns.go index 860d19691..dcd4543b0 100644 --- a/providers/dns/azuredns/azuredns.go +++ b/providers/dns/azuredns/azuredns.go @@ -3,19 +3,13 @@ package azuredns import ( - "context" "errors" "fmt" "net/http" - "strings" "time" - "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" - "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" ) @@ -33,10 +27,21 @@ const ( EnvClientID = envNamespace + "CLIENT_ID" EnvClientSecret = envNamespace + "CLIENT_SECRET" - EnvOIDCToken = envNamespace + "OIDC_TOKEN" - EnvOIDCTokenFilePath = envNamespace + "OIDC_TOKEN_FILE_PATH" - EnvOIDCRequestURL = envNamespace + "OIDC_REQUEST_URL" - EnvOIDCRequestToken = envNamespace + "OIDC_REQUEST_TOKEN" + EnvOIDCToken = envNamespace + "OIDC_TOKEN" + EnvOIDCTokenFilePath = envNamespace + "OIDC_TOKEN_FILE_PATH" + EnvOIDCRequestURL = envNamespace + "OIDC_REQUEST_URL" + EnvGitHubOIDCRequestURL = "ACTIONS_ID_TOKEN_REQUEST_URL" + altEnvArmOIDCRequestURL = "ARM_OIDC_REQUEST_URL" + EnvOIDCRequestToken = envNamespace + "OIDC_REQUEST_TOKEN" + EnvGitHubOIDCRequestToken = "ACTIONS_ID_TOKEN_REQUEST_TOKEN" + altEnvArmOIDCRequestToken = "ARM_OIDC_REQUEST_TOKEN" + + EnvServiceConnectionID = envNamespace + "SERVICE_CONNECTION_ID" + altEnvServiceConnectionID = "SERVICE_CONNECTION_ID" + altEnvArmAdoPipelineServiceConnectionID = "ARM_ADO_PIPELINE_SERVICE_CONNECTION_ID" + altEnvArmOIDCAzureServiceConnectionID = "ARM_OIDC_AZURE_SERVICE_CONNECTION_ID" + EnvSystemAccessToken = envNamespace + "SYSTEM_ACCESS_TOKEN" + altEnvSystemAccessToken = "SYSTEM_ACCESSTOKEN" EnvAuthMethod = envNamespace + "AUTH_METHOD" EnvAuthMSITimeout = envNamespace + "AUTH_MSI_TIMEOUT" @@ -46,9 +51,6 @@ const ( EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - - EnvGitHubOIDCRequestURL = "ACTIONS_ID_TOKEN_REQUEST_URL" - EnvGitHubOIDCRequestToken = "ACTIONS_ID_TOKEN_REQUEST_TOKEN" ) var _ challenge.ProviderTimeout = (*DNSProvider)(nil) @@ -73,6 +75,9 @@ type Config struct { OIDCRequestURL string OIDCRequestToken string + ServiceConnectionID string + SystemAccessToken string + AuthMethod string AuthMSITimeout time.Duration @@ -134,13 +139,22 @@ func NewDNSProvider() (*DNSProvider, error) { config.ServiceDiscoveryFilter = env.GetOrFile(EnvServiceDiscoveryFilter) oidcValues, _ := env.GetWithFallback( - []string{EnvOIDCRequestURL, EnvGitHubOIDCRequestURL}, - []string{EnvOIDCRequestToken, EnvGitHubOIDCRequestToken}, + []string{EnvOIDCRequestURL, EnvGitHubOIDCRequestURL, altEnvArmOIDCRequestURL}, + []string{EnvOIDCRequestToken, EnvGitHubOIDCRequestToken, altEnvArmOIDCRequestToken}, ) config.OIDCRequestURL = oidcValues[EnvOIDCRequestURL] config.OIDCRequestToken = oidcValues[EnvOIDCRequestToken] + // https://registry.terraform.io/providers/hashicorp/Azurerm/latest/docs/guides/service_principal_oidc + pipelineValues, _ := env.GetWithFallback( + []string{EnvServiceConnectionID, altEnvServiceConnectionID, altEnvArmAdoPipelineServiceConnectionID, altEnvArmOIDCAzureServiceConnectionID}, + []string{EnvSystemAccessToken, altEnvArmOIDCRequestToken, altEnvSystemAccessToken}, + ) + + config.ServiceConnectionID = pipelineValues[EnvServiceConnectionID] + config.SystemAccessToken = pipelineValues[EnvSystemAccessToken] + config.AuthMethod = env.GetOrFile(EnvAuthMethod) config.AuthMSITimeout = env.GetOrDefaultSecond(EnvAuthMSITimeout, 2*time.Second) @@ -193,88 +207,3 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return d.provider.CleanUp(domain, token, keyAuth) } - -func getCredentials(config *Config) (azcore.TokenCredential, error) { - clientOptions := azcore.ClientOptions{Cloud: config.Environment} - - switch strings.ToLower(config.AuthMethod) { - case "env": - if config.ClientID != "" && config.ClientSecret != "" && config.TenantID != "" { - return azidentity.NewClientSecretCredential(config.TenantID, config.ClientID, config.ClientSecret, - &azidentity.ClientSecretCredentialOptions{ClientOptions: clientOptions}) - } - - return azidentity.NewEnvironmentCredential(&azidentity.EnvironmentCredentialOptions{ClientOptions: clientOptions}) - - case "wli": - return azidentity.NewWorkloadIdentityCredential(&azidentity.WorkloadIdentityCredentialOptions{ClientOptions: clientOptions}) - - case "msi": - cred, err := azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{ClientOptions: clientOptions}) - if err != nil { - return nil, err - } - - return &timeoutTokenCredential{cred: cred, timeout: config.AuthMSITimeout}, nil - - case "cli": - var credOptions *azidentity.AzureCLICredentialOptions - if config.TenantID != "" { - credOptions = &azidentity.AzureCLICredentialOptions{TenantID: config.TenantID} - } - return azidentity.NewAzureCLICredential(credOptions) - - case "oidc": - err := checkOIDCConfig(config) - if err != nil { - return nil, err - } - - return azidentity.NewClientAssertionCredential(config.TenantID, config.ClientID, getOIDCAssertion(config), &azidentity.ClientAssertionCredentialOptions{ClientOptions: clientOptions}) - - default: - return azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ClientOptions: clientOptions}) - } -} - -// timeoutTokenCredential wraps a TokenCredential to add a timeout. -type timeoutTokenCredential struct { - cred azcore.TokenCredential - timeout time.Duration -} - -// GetToken implements the azcore.TokenCredential interface. -func (w *timeoutTokenCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) { - if w.timeout <= 0 { - return w.cred.GetToken(ctx, opts) - } - - ctxTimeout, cancel := context.WithTimeout(ctx, w.timeout) - defer cancel() - - tk, err := w.cred.GetToken(ctxTimeout, opts) - if ce := ctxTimeout.Err(); errors.Is(ce, context.DeadlineExceeded) { - return tk, azidentity.NewCredentialUnavailableError("managed identity timed out") - } - - w.timeout = 0 - - return tk, err -} - -func getZoneName(config *Config, fqdn string) (string, error) { - if config.ZoneName != "" { - return config.ZoneName, nil - } - - authZone, err := dns01.FindZoneByFqdn(fqdn) - if err != nil { - return "", fmt.Errorf("could not find zone for %s: %w", fqdn, err) - } - - if authZone == "" { - return "", errors.New("empty zone name") - } - - return authZone, nil -} diff --git a/providers/dns/azuredns/azuredns.toml b/providers/dns/azuredns/azuredns.toml index 8d14105cb..6c1e1ccff 100644 --- a/providers/dns/azuredns/azuredns.toml +++ b/providers/dns/azuredns/azuredns.toml @@ -174,6 +174,10 @@ This authentication method can be specifically used by setting the `AZURE_AUTH_M Open ID Connect is a mechanism that establish a trust relationship between a running environment and the Azure AD identity provider. It can be enabled by setting the `AZURE_AUTH_METHOD` environment variable to `oidc`. +### Azure DevOps Pipelines + +It can be enabled by setting the `AZURE_AUTH_METHOD` environment variable to `pipeline`. + ''' [Configuration] diff --git a/providers/dns/azuredns/credentials.go b/providers/dns/azuredns/credentials.go new file mode 100644 index 000000000..efca10e59 --- /dev/null +++ b/providers/dns/azuredns/credentials.go @@ -0,0 +1,135 @@ +package azuredns + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/go-acme/lego/v4/challenge/dns01" +) + +const ( + authMethodEnv = "env" + authMethodWLI = "wli" + authMethodMSI = "msi" + authMethodCLI = "cli" + authMethodOIDC = "oidc" + authMethodPipeline = "pipeline" +) + +//nolint:gocyclo // The complexity is related to the number of possible configurations. +func getCredentials(config *Config) (azcore.TokenCredential, error) { + clientOptions := azcore.ClientOptions{Cloud: config.Environment} + + switch strings.ToLower(config.AuthMethod) { + case authMethodEnv: + if config.ClientID != "" && config.ClientSecret != "" && config.TenantID != "" { + return azidentity.NewClientSecretCredential(config.TenantID, config.ClientID, config.ClientSecret, + &azidentity.ClientSecretCredentialOptions{ClientOptions: clientOptions}) + } + + return azidentity.NewEnvironmentCredential(&azidentity.EnvironmentCredentialOptions{ClientOptions: clientOptions}) + + case authMethodWLI: + return azidentity.NewWorkloadIdentityCredential(&azidentity.WorkloadIdentityCredentialOptions{ClientOptions: clientOptions}) + + case authMethodMSI: + cred, err := azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{ClientOptions: clientOptions}) + if err != nil { + return nil, err + } + + return &timeoutTokenCredential{cred: cred, timeout: config.AuthMSITimeout}, nil + + case authMethodCLI: + var credOptions *azidentity.AzureCLICredentialOptions + if config.TenantID != "" { + credOptions = &azidentity.AzureCLICredentialOptions{TenantID: config.TenantID} + } + return azidentity.NewAzureCLICredential(credOptions) + + case authMethodOIDC: + err := checkOIDCConfig(config) + if err != nil { + return nil, err + } + + return azidentity.NewClientAssertionCredential(config.TenantID, config.ClientID, getOIDCAssertion(config), &azidentity.ClientAssertionCredentialOptions{ClientOptions: clientOptions}) + + case authMethodPipeline: + err := checkPipelineConfig(config) + if err != nil { + return nil, err + } + + // Uses the env var `SYSTEM_OIDCREQUESTURI`, + // but the constant is not exported, + // and there is no way to set it programmatically. + // https://github.com/Azure/azure-sdk-for-go/blob/aae2fb75ffccafc669db72bebc3c1a66332f48d7/sdk/azidentity/azure_pipelines_credential.go#L22 + // https://github.com/Azure/azure-sdk-for-go/blob/aae2fb75ffccafc669db72bebc3c1a66332f48d7/sdk/azidentity/azure_pipelines_credential.go#L79 + + return azidentity.NewAzurePipelinesCredential(config.TenantID, config.ClientID, config.ServiceConnectionID, config.SystemAccessToken, &azidentity.AzurePipelinesCredentialOptions{ClientOptions: clientOptions}) + + default: + return azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ClientOptions: clientOptions}) + } +} + +// timeoutTokenCredential wraps a TokenCredential to add a timeout. +type timeoutTokenCredential struct { + cred azcore.TokenCredential + timeout time.Duration +} + +// GetToken implements the azcore.TokenCredential interface. +func (w *timeoutTokenCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) { + if w.timeout <= 0 { + return w.cred.GetToken(ctx, opts) + } + + ctxTimeout, cancel := context.WithTimeout(ctx, w.timeout) + defer cancel() + + tk, err := w.cred.GetToken(ctxTimeout, opts) + if ce := ctxTimeout.Err(); errors.Is(ce, context.DeadlineExceeded) { + return tk, azidentity.NewCredentialUnavailableError("managed identity timed out") + } + + w.timeout = 0 + + return tk, err +} + +func getZoneName(config *Config, fqdn string) (string, error) { + if config.ZoneName != "" { + return config.ZoneName, nil + } + + authZone, err := dns01.FindZoneByFqdn(fqdn) + if err != nil { + return "", fmt.Errorf("could not find zone for %s: %w", fqdn, err) + } + + if authZone == "" { + return "", errors.New("empty zone name") + } + + return authZone, nil +} + +func checkPipelineConfig(config *Config) error { + if config.ServiceConnectionID == "" { + return errors.New("azuredns: ServiceConnectionID is missing") + } + + if config.SystemAccessToken == "" { + return errors.New("azuredns: SystemAccessToken is missing") + } + + return nil +} From 50a24ced373569e5209e75c75e75ba3a0477412a Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 21 Aug 2025 13:19:26 +0200 Subject: [PATCH 156/298] Add DNS provider for Binary Lane (#2624) --- README.md | 74 ++++----- cmd/zz_gen_cmd_dnshelp.go | 21 +++ docs/content/dns/zz_gen_binarylane.md | 67 ++++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/binarylane/binarylane.go | 157 ++++++++++++++++++ providers/dns/binarylane/binarylane.toml | 22 +++ providers/dns/binarylane/binarylane_test.go | 115 +++++++++++++ providers/dns/binarylane/internal/client.go | 146 ++++++++++++++++ .../dns/binarylane/internal/client_test.go | 97 +++++++++++ .../fixtures/create_record-request.json | 6 + .../internal/fixtures/create_record.json | 9 + .../binarylane/internal/fixtures/error.json | 14 ++ providers/dns/binarylane/internal/types.go | 42 +++++ providers/dns/zz_gen_dns_providers.go | 3 + 14 files changed, 737 insertions(+), 38 deletions(-) create mode 100644 docs/content/dns/zz_gen_binarylane.md create mode 100644 providers/dns/binarylane/binarylane.go create mode 100644 providers/dns/binarylane/binarylane.toml create mode 100644 providers/dns/binarylane/binarylane_test.go create mode 100644 providers/dns/binarylane/internal/client.go create mode 100644 providers/dns/binarylane/internal/client_test.go create mode 100644 providers/dns/binarylane/internal/fixtures/create_record-request.json create mode 100644 providers/dns/binarylane/internal/fixtures/create_record.json create mode 100644 providers/dns/binarylane/internal/fixtures/error.json create mode 100644 providers/dns/binarylane/internal/types.go diff --git a/README.md b/README.md index 66ed2e4d6..50da4d5d3 100644 --- a/README.md +++ b/README.md @@ -70,188 +70,188 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Azure DNS Baidu Cloud + Binary Lane Bindman - Bluecat + Bluecat BookMyName Brandit (deprecated) Bunny - Checkdomain + Checkdomain Civo Cloud.ru CloudDNS - Cloudflare + Cloudflare ClouDNS CloudXNS (Deprecated) ConoHa v2 - ConoHa v3 + ConoHa v3 Constellix Core-Networks CPanel/WHM - Derak Cloud + Derak Cloud deSEC.io Designate DNSaaS for Openstack Digital Ocean - DirectAdmin + DirectAdmin DNS Made Easy dnsHome.de DNSimple - DNSPod (deprecated) + DNSPod (deprecated) Domain Offensive (do.de) Domeneshop DreamHost - Duck DNS + Duck DNS Dyn DynDnsFree.de Dynu - EasyDNS + EasyDNS Efficient IP Epik Exoscale - External program + External program F5 XC freemyip.com G-Core - Gandi + Gandi Gandi Live DNS (v5) Glesys Go Daddy - Google Cloud + Google Cloud Google Domains Hetzner Hosting.de - Hosttech + Hosttech HTTP request http.net Huawei Cloud - Hurricane Electric DNS + Hurricane Electric DNS HyperOne IBM Cloud (SoftLayer) IIJ DNS Platform Service - Infoblox + Infoblox Infomaniak Internet Initiative Japan Internet.bs - INWX + INWX Ionos IPv64 iwantmyname - Joker + Joker Joohoi's ACME-DNS Liara Lima-City - Linode (v4) + Linode (v4) Liquid Web Loopia LuaDNS - Mail-in-a-Box + Mail-in-a-Box ManageEngine CloudDNS Manual Metaname - Metaregistrar + Metaregistrar mijn.host Mittwald myaddr.{tools,dev,io} - MyDNS.jp + MyDNS.jp MythicBeasts Name.com Namecheap - Namesilo + Namesilo NearlyFreeSpeech.NET Netcup Netlify - Nicmanager + Nicmanager NIFCloud Njalla Nodion - NS1 + NS1 Open Telekom Cloud Oracle Cloud OVH - plesk.com + plesk.com Porkbun PowerDNS Rackspace - Rain Yun/雨云 + Rain Yun/雨云 RcodeZero reg.ru Regfish - RFC2136 + RFC2136 RimuHosting RU CENTER Sakura Cloud - Scaleway + Scaleway Selectel Selectel v2 SelfHost.(de|eu) - Servercow + Servercow Shellrent Simply.com Sonic - Spaceship + Spaceship Stackpath Technitium Tencent Cloud DNS - Tencent EdgeOne + Tencent EdgeOne Timeweb Cloud TransIP UKFast SafeDNS - Ultradns + Ultradns Variomedia VegaDNS Vercel - Versio.[nl|eu|uk] + Versio.[nl|eu|uk] VinylDNS VK Cloud Volcano Engine/火山引擎 - Vscale + Vscale Vultr Webnames Websupport - WEDOS + WEDOS West.cn/西部数码 Yandex 360 Yandex Cloud - Yandex PDD + Yandex PDD Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 1aacdc7af..d53274139 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -25,6 +25,7 @@ func allDNSCodes() string { "azure", "azuredns", "baiducloud", + "binarylane", "bindman", "bluecat", "bookmyname", @@ -448,6 +449,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/baiducloud`) + case "binarylane": + // generated from: providers/dns/binarylane/binarylane.toml + ew.writeln(`Configuration for Binary Lane.`) + ew.writeln(`Code: 'binarylane'`) + ew.writeln(`Since: 'v4.26.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "BINARYLANE_API_TOKEN": API token`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "BINARYLANE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "BINARYLANE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "BINARYLANE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "BINARYLANE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/binarylane`) + case "bindman": // generated from: providers/dns/bindman/bindman.toml ew.writeln(`Configuration for Bindman.`) diff --git a/docs/content/dns/zz_gen_binarylane.md b/docs/content/dns/zz_gen_binarylane.md new file mode 100644 index 000000000..4d65bb0bc --- /dev/null +++ b/docs/content/dns/zz_gen_binarylane.md @@ -0,0 +1,67 @@ +--- +title: "Binary Lane" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: binarylane +dnsprovider: + since: "v4.26.0" + code: "binarylane" + url: "https://www.binarylane.com.au/" +--- + + + + + + +Configuration for [Binary Lane](https://www.binarylane.com.au/). + + + + +- Code: `binarylane` +- Since: v4.26.0 + + +Here is an example bash command using the Binary Lane provider: + +```bash +BINARYLANE_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns binarylane -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `BINARYLANE_API_TOKEN` | API token | + +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 | +|--------------------------------|-------------| +| `BINARYLANE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `BINARYLANE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `BINARYLANE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `BINARYLANE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://api.binarylane.com.au/reference/#tag/Domains) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 62e422217..ea5b037fe 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/binarylane/binarylane.go b/providers/dns/binarylane/binarylane.go new file mode 100644 index 000000000..d8f459e2f --- /dev/null +++ b/providers/dns/binarylane/binarylane.go @@ -0,0 +1,157 @@ +// Package binarylane implements a DNS provider for solving the DNS-01 challenge using Binary Lane. +package binarylane + +import ( + "context" + "errors" + "fmt" + "net/http" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/binarylane/internal" +) + +// Environment variables names. +const ( + envNamespace = "BINARYLANE_" + + EnvAPIToken = envNamespace + "API_TOKEN" + + 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 { + APIToken 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, 3600), + 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 *internal.Client + + recordIDs map[string]int64 + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for Binary Lane. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIToken) + if err != nil { + return nil, fmt.Errorf("binarylane: %w", err) + } + + config := NewDefaultConfig() + config.APIToken = values[EnvAPIToken] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Binary Lane. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("binarylane: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.APIToken) + if err != nil { + return nil, fmt.Errorf("binarylane: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + recordIDs: make(map[string]int64), + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("binarylane: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("binarylane: %w", err) + } + + record := internal.Record{ + Type: "TXT", + Name: subDomain, + Data: info.Value, + TTL: d.config.TTL, + } + + response, err := d.client.CreateRecord(context.Background(), dns01.UnFqdn(authZone), record) + if err != nil { + return fmt.Errorf("binarylane: create record: %w", err) + } + + d.recordIDsMu.Lock() + d.recordIDs[token] = response.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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("binarylane: could not find zone for domain %q: %w", domain, err) + } + + // 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("binarylane: unknown record ID for '%s'", info.EffectiveFQDN) + } + + err = d.client.DeleteRecord(context.Background(), dns01.UnFqdn(authZone), recordID) + if err != nil { + return fmt.Errorf("binarylane: delete record: %w", err) + } + + 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/binarylane/binarylane.toml b/providers/dns/binarylane/binarylane.toml new file mode 100644 index 000000000..5038fc3e6 --- /dev/null +++ b/providers/dns/binarylane/binarylane.toml @@ -0,0 +1,22 @@ +Name = "Binary Lane" +Description = '''''' +URL = "https://www.binarylane.com.au/" +Code = "binarylane" +Since = "v4.26.0" + +Example = ''' +BINARYLANE_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns binarylane -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + BINARYLANE_API_TOKEN = "API token" + [Configuration.Additional] + BINARYLANE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + BINARYLANE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + BINARYLANE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + BINARYLANE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://api.binarylane.com.au/reference/#tag/Domains" diff --git a/providers/dns/binarylane/binarylane_test.go b/providers/dns/binarylane/binarylane_test.go new file mode 100644 index 000000000..9519fe0f2 --- /dev/null +++ b/providers/dns/binarylane/binarylane_test.go @@ -0,0 +1,115 @@ +package binarylane + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvAPIToken).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvAPIToken: "secret", + }, + }, + { + desc: "missing API token", + envVars: map[string]string{ + EnvAPIToken: "", + }, + expected: "binarylane: some credentials information are missing: BINARYLANE_API_TOKEN", + }, + } + + 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 + apiToken string + expected string + }{ + { + desc: "success", + apiToken: "secret", + }, + { + desc: "missing API token", + expected: "binarylane: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIToken = test.apiToken + + 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/binarylane/internal/client.go b/providers/dns/binarylane/internal/client.go new file mode 100644 index 000000000..45518cb41 --- /dev/null +++ b/providers/dns/binarylane/internal/client.go @@ -0,0 +1,146 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const defaultBaseURL = "https://api.binarylane.com.au/v2/" + +const authorizationHeader = "Authorization" + +// Client the Binary Lane API client. +type Client struct { + apiToken string + + baseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(apiToken string) (*Client, error) { + if apiToken == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + apiToken: apiToken, + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +// CreateRecord Creates a new domain record. +// https://api.binarylane.com.au/reference/#tag/Domains/paths/~1v2~1domains~1%7Bdomain_name%7D~1records/post +func (c *Client) CreateRecord(ctx context.Context, domain string, record Record) (*Record, error) { + endpoint := c.baseURL.JoinPath("domains", domain, "records") + + if record.Name == "" { + record.Name = "@" + } + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) + if err != nil { + return nil, err + } + + var result APIResponse + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result.DomainRecord, nil +} + +// DeleteRecord Deletes an existing domain record. +// https://api.binarylane.com.au/reference/#tag/Domains/paths/~1v2~1domains~1%7Bdomain_name%7D~1records~1%7Brecord_id%7D/delete +func (c *Client) DeleteRecord(ctx context.Context, domainName string, recordID int64) error { + endpoint := c.baseURL.JoinPath("domains", domainName, "records", strconv.FormatInt(recordID, 10)) + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) do(req *http.Request, result any) error { + req.Header.Set(authorizationHeader, "Bearer "+c.apiToken) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/binarylane/internal/client_test.go b/providers/dns/binarylane/internal/client_test.go new file mode 100644 index 000000000..0398d5adf --- /dev/null +++ b/providers/dns/binarylane/internal/client_test.go @@ -0,0 +1,97 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("secret") + if err != nil { + return nil, err + } + + client.baseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + WithAuthorization("Bearer secret"), + ) +} + +func TestClient_CreateRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /domains/example.com/records", + servermock.ResponseFromFixture("create_record.json"), + servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). + Build(t) + + record := Record{ + Type: "TXT", + Name: "foo", + Data: "txtTXTtxt", + TTL: 300, + } + + rec, err := client.CreateRecord(t.Context(), "example.com", record) + require.NoError(t, err) + + expected := &Record{ + ID: 123, + Type: "TXT", + Name: "foo", + Data: "txtTXTtxt", + TTL: 300, + } + + require.Equal(t, expected, rec) +} + +func TestClient_CreateRecord_error(t *testing.T) { + client := mockBuilder(). + Route("POST /domains/example.com/records", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) + + record := Record{ + Type: "TXT", + Name: "foo", + Data: "txtTXTtxt", + TTL: 300, + } + + _, err := client.CreateRecord(t.Context(), "example.com", record) + require.EqualError(t, err, "400: type: title: detail: instance: property1: a") +} + +func TestClient_DeleteRecord(t *testing.T) { + client := mockBuilder(). + Route("DELETE /domains/example.com/records/123", + servermock.Noop(). + WithStatusCode(http.StatusNoContent)). + Build(t) + + err := client.DeleteRecord(t.Context(), "example.com", 123) + require.NoError(t, err) +} + +func TestClient_DeleteRecord_error(t *testing.T) { + client := mockBuilder(). + Route("DELETE /domains/example.com/records/123", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) + + err := client.DeleteRecord(t.Context(), "example.com", 123) + require.EqualError(t, err, "400: type: title: detail: instance: property1: a") +} diff --git a/providers/dns/binarylane/internal/fixtures/create_record-request.json b/providers/dns/binarylane/internal/fixtures/create_record-request.json new file mode 100644 index 000000000..98a349650 --- /dev/null +++ b/providers/dns/binarylane/internal/fixtures/create_record-request.json @@ -0,0 +1,6 @@ +{ + "type": "TXT", + "name": "foo", + "data": "txtTXTtxt", + "ttl": 300 +} diff --git a/providers/dns/binarylane/internal/fixtures/create_record.json b/providers/dns/binarylane/internal/fixtures/create_record.json new file mode 100644 index 000000000..709bef23e --- /dev/null +++ b/providers/dns/binarylane/internal/fixtures/create_record.json @@ -0,0 +1,9 @@ +{ + "domain_record": { + "id": 123, + "type": "TXT", + "name": "foo", + "data": "txtTXTtxt", + "ttl": 300 + } +} diff --git a/providers/dns/binarylane/internal/fixtures/error.json b/providers/dns/binarylane/internal/fixtures/error.json new file mode 100644 index 000000000..79d115f74 --- /dev/null +++ b/providers/dns/binarylane/internal/fixtures/error.json @@ -0,0 +1,14 @@ +{ + "type": "type", + "title": "title", + "status": 400, + "detail": "detail", + "instance": "instance", + "errors": { + "property1": [ + "a" + ] + }, + "property1": null, + "property2": null +} diff --git a/providers/dns/binarylane/internal/types.go b/providers/dns/binarylane/internal/types.go new file mode 100644 index 000000000..e6c27410a --- /dev/null +++ b/providers/dns/binarylane/internal/types.go @@ -0,0 +1,42 @@ +package internal + +import ( + "fmt" + "strings" +) + +type APIError struct { + Type string `json:"type"` + Title string `json:"title"` + Status int `json:"status"` + Detail string `json:"detail"` + Instance string `json:"instance"` + Errors map[string][]string `json:"errors"` +} + +func (a *APIError) Error() string { + msg := fmt.Sprintf("%d: %s: %s: %s: %s", a.Status, a.Type, a.Title, a.Detail, a.Instance) + + for s, values := range a.Errors { + msg += fmt.Sprintf(": %s: %s", s, strings.Join(values, ", ")) + } + + return msg +} + +type Record struct { + ID int64 `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Data string `json:"data,omitempty"` + Priority int `json:"priority,omitempty"` + Port int `json:"port,omitempty"` + TTL int `json:"ttl,omitempty"` + Weight int `json:"weight,omitempty"` + Flags int `json:"flags,omitempty"` + Tag string `json:"tag,omitempty"` +} + +type APIResponse struct { + DomainRecord *Record `json:"domain_record"` +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index f3598b7b5..44ab9d493 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -19,6 +19,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/azure" "github.com/go-acme/lego/v4/providers/dns/azuredns" "github.com/go-acme/lego/v4/providers/dns/baiducloud" + "github.com/go-acme/lego/v4/providers/dns/binarylane" "github.com/go-acme/lego/v4/providers/dns/bindman" "github.com/go-acme/lego/v4/providers/dns/bluecat" "github.com/go-acme/lego/v4/providers/dns/bookmyname" @@ -196,6 +197,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return azuredns.NewDNSProvider() case "baiducloud": return baiducloud.NewDNSProvider() + case "binarylane": + return binarylane.NewDNSProvider() case "bindman": return bindman.NewDNSProvider() case "bluecat": From 784ce2be95e9d655e0b3dd7eb509eef79bf090a0 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 24 Aug 2025 22:18:26 +0200 Subject: [PATCH 157/298] oraclecloud: add aliases (#2627) --- cmd/zz_gen_cmd_dnshelp.go | 17 ++-- docs/content/dns/zz_gen_oraclecloud.md | 23 +++-- .../dns/oraclecloud/configurationprovider.go | 99 ++++++++++++++----- providers/dns/oraclecloud/oraclecloud.go | 31 +++++- providers/dns/oraclecloud/oraclecloud.toml | 23 +++-- providers/dns/oraclecloud/oraclecloud_test.go | 7 +- 6 files changed, 143 insertions(+), 57 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index d53274139..9a4e24807 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -2518,12 +2518,12 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Credentials:`) ew.writeln(` - "OCI_COMPARTMENT_OCID": Compartment OCID`) - ew.writeln(` - "OCI_PRIVKEY_FILE": Private key file (ignored if OCI_AUTH_TYPE=instance_principal)`) - ew.writeln(` - "OCI_PRIVKEY_PASS": Private key password (ignored if OCI_AUTH_TYPE=instance_principal)`) - ew.writeln(` - "OCI_PUBKEY_FINGERPRINT": Public key fingerprint (ignored if OCI_AUTH_TYPE=instance_principal)`) - ew.writeln(` - "OCI_REGION": Region (can be empty if OCI_AUTH_TYPE=instance_principal)`) - ew.writeln(` - "OCI_TENANCY_OCID": Tenancy OCID (ignored if OCI_AUTH_TYPE=instance_principal)`) - ew.writeln(` - "OCI_USER_OCID": User OCID (ignored if OCI_AUTH_TYPE=instance_principal)`) + ew.writeln(` - "OCI_FINGERPRINT": Public key fingerprint (ignored if 'OCI_AUTH_TYPE=instance_principal')`) + ew.writeln(` - "OCI_PRIVATE_KEY_PASSWORD": Private key password (ignored if 'OCI_AUTH_TYPE=instance_principal')`) + ew.writeln(` - "OCI_PRIVATE_KEY_PATH": Private key file (ignored if 'OCI_AUTH_TYPE=instance_principal')`) + ew.writeln(` - "OCI_REGION": Region (it can be empty if 'OCI_AUTH_TYPE=instance_principal').`) + ew.writeln(` - "OCI_TENANCY_OCID": Tenancy OCID (ignored if 'OCI_AUTH_TYPE=instance_principal')`) + ew.writeln(` - "OCI_USER_OCID": User OCID (ignored if 'OCI_AUTH_TYPE=instance_principal')`) ew.writeln() ew.writeln(`Additional Configuration:`) @@ -2532,6 +2532,11 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(` - "OCI_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "OCI_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln(` - "OCI_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + ew.writeln(` - "TF_VAR_fingerprint": Alias on 'OCI_FINGERPRINT'`) + ew.writeln(` - "TF_VAR_private_key_path": Alias on 'OCI_PRIVATE_KEY_PATH'`) + ew.writeln(` - "TF_VAR_region": Alias on 'OCI_REGION'`) + ew.writeln(` - "TF_VAR_tenancy_ocid": Alias on 'OCI_TENANCY_OCID'`) + ew.writeln(` - "TF_VAR_user_ocid": Alias on 'OCI_USER_OCID'`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/oraclecloud`) diff --git a/docs/content/dns/zz_gen_oraclecloud.md b/docs/content/dns/zz_gen_oraclecloud.md index 974466601..c43c24b21 100644 --- a/docs/content/dns/zz_gen_oraclecloud.md +++ b/docs/content/dns/zz_gen_oraclecloud.md @@ -27,11 +27,11 @@ Here is an example bash command using the Oracle Cloud provider: ```bash # Using API Key authentication: -OCI_PRIVKEY_FILE="~/.oci/oci_api_key.pem" \ -OCI_PRIVKEY_PASS="secret" \ +OCI_PRIVATE_KEY_PATH="~/.oci/oci_api_key.pem" \ +OCI_PRIVATE_KEY_PASSWORD="secret" \ OCI_TENANCY_OCID="ocid1.tenancy.oc1..secret" \ OCI_USER_OCID="ocid1.user.oc1..secret" \ -OCI_PUBKEY_FINGERPRINT="00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00" \ +OCI_FINGERPRINT="00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00" \ OCI_REGION="us-phoenix-1" \ OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com run @@ -51,12 +51,12 @@ lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com | Environment Variable Name | Description | |-----------------------|-------------| | `OCI_COMPARTMENT_OCID` | Compartment OCID | -| `OCI_PRIVKEY_FILE` | Private key file (ignored if OCI_AUTH_TYPE=instance_principal) | -| `OCI_PRIVKEY_PASS` | Private key password (ignored if OCI_AUTH_TYPE=instance_principal) | -| `OCI_PUBKEY_FINGERPRINT` | Public key fingerprint (ignored if OCI_AUTH_TYPE=instance_principal) | -| `OCI_REGION` | Region (can be empty if OCI_AUTH_TYPE=instance_principal) | -| `OCI_TENANCY_OCID` | Tenancy OCID (ignored if OCI_AUTH_TYPE=instance_principal) | -| `OCI_USER_OCID` | User OCID (ignored if OCI_AUTH_TYPE=instance_principal) | +| `OCI_FINGERPRINT` | Public key fingerprint (ignored if `OCI_AUTH_TYPE=instance_principal`) | +| `OCI_PRIVATE_KEY_PASSWORD` | Private key password (ignored if `OCI_AUTH_TYPE=instance_principal`) | +| `OCI_PRIVATE_KEY_PATH` | Private key file (ignored if `OCI_AUTH_TYPE=instance_principal`) | +| `OCI_REGION` | Region (it can be empty if `OCI_AUTH_TYPE=instance_principal`). | +| `OCI_TENANCY_OCID` | Tenancy OCID (ignored if `OCI_AUTH_TYPE=instance_principal`) | +| `OCI_USER_OCID` | User OCID (ignored if `OCI_AUTH_TYPE=instance_principal`) | 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" %}}). @@ -71,6 +71,11 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | `OCI_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `OCI_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | | `OCI_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | +| `TF_VAR_fingerprint` | Alias on `OCI_FINGERPRINT` | +| `TF_VAR_private_key_path` | Alias on `OCI_PRIVATE_KEY_PATH` | +| `TF_VAR_region` | Alias on `OCI_REGION` | +| `TF_VAR_tenancy_ocid` | Alias on `OCI_TENANCY_OCID` | +| `TF_VAR_user_ocid` | Alias on `OCI_USER_OCID` | 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" %}}). diff --git a/providers/dns/oraclecloud/configurationprovider.go b/providers/dns/oraclecloud/configurationprovider.go index b18af50e4..97710108c 100644 --- a/providers/dns/oraclecloud/configurationprovider.go +++ b/providers/dns/oraclecloud/configurationprovider.go @@ -6,30 +6,41 @@ import ( "errors" "fmt" "os" + "slices" + "strings" + "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" "github.com/nrdcg/oci-go-sdk/common/v1065" ) type environmentConfigurationProvider struct { - values map[string]string - privateKeyPassphrase string + values map[string]string } -func newEnvironmentConfigurationProvider(values map[string]string) *environmentConfigurationProvider { - return &environmentConfigurationProvider{ - values: values, - privateKeyPassphrase: env.GetOrFile(EnvPrivKeyPass), - } -} - -func (p *environmentConfigurationProvider) PrivateRSAKey() (*rsa.PrivateKey, error) { - privateKey, err := getPrivateKey(envPrivKey) +func newEnvironmentConfigurationProvider() (*environmentConfigurationProvider, error) { + values, err := env.GetWithFallback( + []string{EnvRegion, altEnvTFVarRegion}, + []string{EnvUserOCID, altEnvTFVarUserOCID}, + []string{EnvTenancyOCID, altEnvTFVarTenancyOCID}, + []string{EnvPubKeyFingerprint, altEnvFingerprint, altEnvTFVarFingerprint}, + ) if err != nil { return nil, err } - return common.PrivateKeyFromBytesWithPassword(privateKey, []byte(p.privateKeyPassphrase)) + return &environmentConfigurationProvider{ + values: values, + }, nil +} + +func (p *environmentConfigurationProvider) PrivateRSAKey() (*rsa.PrivateKey, error) { + privateKey, err := getPrivateKey() + if err != nil { + return nil, err + } + + return common.PrivateKeyFromBytesWithPassword(privateKey, []byte(p.privateKeyPassword())) } func (p *environmentConfigurationProvider) KeyID() (string, error) { @@ -51,7 +62,7 @@ func (p *environmentConfigurationProvider) KeyID() (string, error) { return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil } -func (p *environmentConfigurationProvider) TenancyOCID() (value string, err error) { +func (p *environmentConfigurationProvider) TenancyOCID() (string, error) { return p.values[EnvTenancyOCID], nil } @@ -72,26 +83,62 @@ func (p *environmentConfigurationProvider) AuthType() (common.AuthConfig, error) return common.AuthConfig{AuthType: common.UnknownAuthenticationType}, errors.New("unsupported, keep the interface") } -func getPrivateKey(envVar string) ([]byte, error) { - envVarValue := os.Getenv(envVar) +func (p *environmentConfigurationProvider) privateKeyPassword() string { + return env.GetOneWithFallback(EnvPrivKeyPass, "", env.ParseString, altEnvPrivateKeyPassword, altEnvTFVarPrivateKeyPassword) +} + +func getPrivateKey() ([]byte, error) { + base64EnvKeys := []string{envPrivKey, altEnvPrivateKey} + + envVarValue := getEnvWithStrictFallback(base64EnvKeys...) if envVarValue != "" { bytes, err := base64.StdEncoding.DecodeString(envVarValue) if err != nil { - return nil, fmt.Errorf("failed to read base64 value %s (defined by env var %s): %w", envVarValue, envVar, err) + return nil, fmt.Errorf("failed to read base64 value %s (defined by env vars %s): %w", envVarValue, + strings.Join(base64EnvKeys, " or "), err) } + return bytes, nil } - fileVar := envVar + "_FILE" - fileVarValue := os.Getenv(fileVar) - if fileVarValue == "" { - return nil, fmt.Errorf("no value provided for: %s or %s", envVar, fileVar) + fileEnvKeys := []string{EnvPrivKeyFile, altEnvPrivateKeyPath, altEnvTFVarPrivateKeyPath} + + fileVarValue := getEnvFileWithStrictFallback(fileEnvKeys...) + if len(fileVarValue) == 0 { + return nil, fmt.Errorf("no value provided for: %s", + strings.Join(slices.Concat(base64EnvKeys, fileEnvKeys), " or "), + ) } - fileContents, err := os.ReadFile(fileVarValue) - if err != nil { - return nil, fmt.Errorf("failed to read the file %s (defined by env var %s): %w", fileVarValue, fileVar, err) - } - - return fileContents, nil + return fileVarValue, nil +} + +func getEnvWithStrictFallback(keys ...string) string { + for _, key := range keys { + envVarValue := os.Getenv(key) + if envVarValue != "" { + return envVarValue + } + } + + return "" +} + +func getEnvFileWithStrictFallback(keys ...string) []byte { + for _, key := range keys { + fileVarValue := os.Getenv(key) + if fileVarValue == "" { + continue + } + + fileContents, err := os.ReadFile(fileVarValue) + if err != nil { + log.Printf("Failed to read the file %s (defined by env var %s): %s", fileVarValue, key, err) + return nil + } + + return fileContents + } + + return nil } diff --git a/providers/dns/oraclecloud/oraclecloud.go b/providers/dns/oraclecloud/oraclecloud.go index a5e97baf8..47902568c 100644 --- a/providers/dns/oraclecloud/oraclecloud.go +++ b/providers/dns/oraclecloud/oraclecloud.go @@ -32,12 +32,29 @@ const ( EnvUserOCID = envNamespace + "USER_OCID" EnvPubKeyFingerprint = envNamespace + "PUBKEY_FINGERPRINT" + altEnvPrivateKey = envNamespace + "PRIVATE_KEY" // alias on OCI_PRIVKEY + altEnvPrivateKeyPath = altEnvPrivateKey + "_PATH" // alias on OCI_PRIVKEY_FILE + altEnvPrivateKeyPassword = altEnvPrivateKey + "_PASSWORD" // alias on OCI_PRIVKEY_PASS + altEnvFingerprint = envNamespace + "FINGERPRINT" // alias on OCI_PUBKEY_FINGERPRINT + EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) +// https://github.com/oracle/oci-go-sdk/blob/7f425f74c74fd0c6a5acb74466c85eb5346e0092/common/client.go#L350 +// https://github.com/oracle/oci-go-sdk/blob/7f425f74c74fd0c6a5acb74466c85eb5346e0092/common/configuration.go#L174-L175 +const ( + altEnvTFVarNamespace = "TF_VAR_" + altEnvTFVarRegion = altEnvTFVarNamespace + "region" // alias on OCI_REGION + altEnvTFVarFingerprint = altEnvTFVarNamespace + "fingerprint" // alias on OCI_PUBKEY_FINGERPRINT + altEnvTFVarUserOCID = altEnvTFVarNamespace + "user_ocid" // alias on OCI_USER_OCID + altEnvTFVarTenancyOCID = altEnvTFVarNamespace + "tenancy_ocid" // alias on OCI_TENANCY_OCID + altEnvTFVarPrivateKeyPath = altEnvTFVarNamespace + "private_key_path" // alias on OCI_PRIVKEY_FILE + altEnvTFVarPrivateKeyPassword = altEnvTFVarNamespace + "private_key_password" // alias on OCI_PRIVKEY_PASS +) + var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. @@ -82,7 +99,9 @@ func NewDNSProvider() (*DNSProvider, error) { config.CompartmentID = values[EnvCompartmentOCID] - configurationProvider, err := auth.InstancePrincipalConfigurationProviderForRegion(common.Region(env.GetOrFile(EnvRegion))) + region := env.GetOneWithFallback(EnvRegion, "", env.ParseString, altEnvTFVarRegion) + + configurationProvider, err := auth.InstancePrincipalConfigurationProviderForRegion(common.Region(region)) if err != nil { return nil, fmt.Errorf("oraclecloud: %w", err) } @@ -90,13 +109,19 @@ func NewDNSProvider() (*DNSProvider, error) { config.OCIConfigProvider = configurationProvider default: - values, err := env.Get(envPrivKey, EnvTenancyOCID, EnvUserOCID, EnvPubKeyFingerprint, EnvRegion, EnvCompartmentOCID) + values, err := env.Get(EnvCompartmentOCID) if err != nil { return nil, fmt.Errorf("oraclecloud: %w", err) } config.CompartmentID = values[EnvCompartmentOCID] - config.OCIConfigProvider = newEnvironmentConfigurationProvider(values) + + ecp, err := newEnvironmentConfigurationProvider() + if err != nil { + return nil, fmt.Errorf("oraclecloud: %w", err) + } + + config.OCIConfigProvider = ecp } return NewDNSProviderConfig(config) diff --git a/providers/dns/oraclecloud/oraclecloud.toml b/providers/dns/oraclecloud/oraclecloud.toml index b02333219..f13cb1e1e 100644 --- a/providers/dns/oraclecloud/oraclecloud.toml +++ b/providers/dns/oraclecloud/oraclecloud.toml @@ -6,11 +6,11 @@ Since = "v2.3.0" Example = ''' # Using API Key authentication: -OCI_PRIVKEY_FILE="~/.oci/oci_api_key.pem" \ -OCI_PRIVKEY_PASS="secret" \ +OCI_PRIVATE_KEY_PATH="~/.oci/oci_api_key.pem" \ +OCI_PRIVATE_KEY_PASSWORD="secret" \ OCI_TENANCY_OCID="ocid1.tenancy.oc1..secret" \ OCI_USER_OCID="ocid1.user.oc1..secret" \ -OCI_PUBKEY_FINGERPRINT="00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00" \ +OCI_FINGERPRINT="00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00" \ OCI_REGION="us-phoenix-1" \ OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com run @@ -25,14 +25,19 @@ lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com [Configuration] [Configuration.Credentials] OCI_COMPARTMENT_OCID = "Compartment OCID" - OCI_REGION = "Region (can be empty if OCI_AUTH_TYPE=instance_principal)" - OCI_PRIVKEY_FILE = "Private key file (ignored if OCI_AUTH_TYPE=instance_principal)" - OCI_PRIVKEY_PASS = "Private key password (ignored if OCI_AUTH_TYPE=instance_principal)" - OCI_TENANCY_OCID = "Tenancy OCID (ignored if OCI_AUTH_TYPE=instance_principal)" - OCI_USER_OCID = "User OCID (ignored if OCI_AUTH_TYPE=instance_principal)" - OCI_PUBKEY_FINGERPRINT = "Public key fingerprint (ignored if OCI_AUTH_TYPE=instance_principal)" + OCI_REGION = "Region (it can be empty if `OCI_AUTH_TYPE=instance_principal`)." + OCI_PRIVATE_KEY_PATH = "Private key file (ignored if `OCI_AUTH_TYPE=instance_principal`)" + OCI_PRIVATE_KEY_PASSWORD = "Private key password (ignored if `OCI_AUTH_TYPE=instance_principal`)" + OCI_TENANCY_OCID = "Tenancy OCID (ignored if `OCI_AUTH_TYPE=instance_principal`)" + OCI_USER_OCID = "User OCID (ignored if `OCI_AUTH_TYPE=instance_principal`)" + OCI_FINGERPRINT = "Public key fingerprint (ignored if `OCI_AUTH_TYPE=instance_principal`)" [Configuration.Additional] OCI_AUTH_TYPE = "Authorization type. Possible values: 'instance_principal', '' (Default: '')" + TF_VAR_region = "Alias on `OCI_REGION`" + TF_VAR_fingerprint = "Alias on `OCI_FINGERPRINT`" + TF_VAR_user_ocid = "Alias on `OCI_USER_OCID`" + TF_VAR_tenancy_ocid = "Alias on `OCI_TENANCY_OCID`" + TF_VAR_private_key_path = "Alias on `OCI_PRIVATE_KEY_PATH`" OCI_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" OCI_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" OCI_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" diff --git a/providers/dns/oraclecloud/oraclecloud_test.go b/providers/dns/oraclecloud/oraclecloud_test.go index 473fcd7ed..058566504 100644 --- a/providers/dns/oraclecloud/oraclecloud_test.go +++ b/providers/dns/oraclecloud/oraclecloud_test.go @@ -72,7 +72,7 @@ func TestNewDNSProvider(t *testing.T) { { desc: "missing credentials", envVars: map[string]string{}, - expected: "oraclecloud: some credentials information are missing: OCI_PRIVKEY,OCI_TENANCY_OCID,OCI_USER_OCID,OCI_PUBKEY_FINGERPRINT,OCI_REGION,OCI_COMPARTMENT_OCID", + expected: "oraclecloud: some credentials information are missing: OCI_COMPARTMENT_OCID", }, { desc: "missing CompartmentID", @@ -98,7 +98,7 @@ func TestNewDNSProvider(t *testing.T) { EnvRegion: "us-phoenix-1", EnvCompartmentOCID: "123", }, - expected: "oraclecloud: some credentials information are missing: OCI_PRIVKEY", + expected: "oraclecloud: can not create client, bad configuration: no value provided for: OCI_PRIVKEY or OCI_PRIVATE_KEY or OCI_PRIVKEY_FILE or OCI_PRIVATE_KEY_PATH or TF_VAR_private_key_path", }, { desc: "missing OCI_PRIVKEY_PASS", @@ -362,13 +362,12 @@ func mockConfigurationProvider(keyPassphrase string) *environmentConfigurationPr return &environmentConfigurationProvider{ values: map[string]string{ EnvCompartmentOCID: "test", - EnvPrivKeyPass: "test", + EnvPrivKeyPass: keyPassphrase, EnvTenancyOCID: "test", EnvUserOCID: "test", EnvPubKeyFingerprint: "test", EnvRegion: "test", }, - privateKeyPassphrase: keyPassphrase, } } From cb445240706a880857ea6373c9149f200bf17f75 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 28 Aug 2025 17:04:43 +0200 Subject: [PATCH 158/298] simply: update to API v2 (#2631) --- providers/dns/simply/internal/client.go | 6 ++++-- providers/dns/simply/internal/client_test.go | 19 ++++++++++--------- providers/dns/simply/simply.toml | 1 + 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/providers/dns/simply/internal/client.go b/providers/dns/simply/internal/client.go index 74f5fe671..47e602d92 100644 --- a/providers/dns/simply/internal/client.go +++ b/providers/dns/simply/internal/client.go @@ -16,7 +16,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) -const defaultBaseURL = "https://api.simply.com/1/" +const defaultBaseURL = "https://api.simply.com/2/" // Client is a Simply.com API client. type Client struct { @@ -111,10 +111,12 @@ func (c *Client) DeleteRecord(ctx context.Context, zoneName string, id int64) er } func (c *Client) createEndpoint(zoneName, uri string) *url.URL { - return c.baseURL.JoinPath(c.accountName, c.apiKey, "my", "products", zoneName, "dns", "records", strings.TrimSuffix(uri, "/")) + return c.baseURL.JoinPath("my", "products", zoneName, "dns", "records", strings.TrimSuffix(uri, "/")) } func (c *Client) do(req *http.Request, result Response) error { + req.SetBasicAuth(c.accountName, c.apiKey) + resp, err := c.HTTPClient.Do(req) if err != nil { return errutils.NewHTTPDoError(req, err) diff --git a/providers/dns/simply/internal/client_test.go b/providers/dns/simply/internal/client_test.go index 83aa714bf..b0bdac6b3 100644 --- a/providers/dns/simply/internal/client_test.go +++ b/providers/dns/simply/internal/client_test.go @@ -24,12 +24,13 @@ func mockBuilder() *servermock.Builder[*Client] { return client, nil }, - servermock.CheckHeader().WithJSONHeaders()) + servermock.CheckHeader().WithJSONHeaders(). + WithBasicAuth("accountname", "apikey")) } func TestClient_GetRecords(t *testing.T) { client := mockBuilder(). - Route("GET /accountname/apikey/my/products/azone01/dns/records", + Route("GET /my/products/azone01/dns/records", servermock.ResponseFromFixture("get_records.json")). Build(t) @@ -76,7 +77,7 @@ func TestClient_GetRecords(t *testing.T) { func TestClient_GetRecords_error(t *testing.T) { client := mockBuilder(). - Route("GET /accountname/apikey/my/products/azone01/dns/records", + Route("GET /my/products/azone01/dns/records", servermock.ResponseFromFixture("bad_auth_error.json"). WithStatusCode(http.StatusBadRequest)). Build(t) @@ -89,7 +90,7 @@ func TestClient_GetRecords_error(t *testing.T) { func TestClient_AddRecord(t *testing.T) { client := mockBuilder(). - Route("POST /accountname/apikey/my/products/azone01/dns/records", + Route("POST /my/products/azone01/dns/records", servermock.ResponseFromFixture("add_record.json")). Build(t) @@ -109,7 +110,7 @@ func TestClient_AddRecord(t *testing.T) { func TestClient_AddRecord_error(t *testing.T) { client := mockBuilder(). - Route("POST /accountname/apikey/my/products/azone01/dns/records", + Route("POST /my/products/azone01/dns/records", servermock.ResponseFromFixture("bad_zone_error.json"). WithStatusCode(http.StatusNotFound)). Build(t) @@ -130,7 +131,7 @@ func TestClient_AddRecord_error(t *testing.T) { func TestClient_EditRecord(t *testing.T) { client := mockBuilder(). - Route("PUT /accountname/apikey/my/products/azone01/dns/records/123456789", + Route("PUT /my/products/azone01/dns/records/123456789", servermock.ResponseFromFixture("success.json")). Build(t) @@ -148,7 +149,7 @@ func TestClient_EditRecord(t *testing.T) { func TestClient_EditRecord_error(t *testing.T) { client := mockBuilder(). - Route("PUT /accountname/apikey/my/products/azone01/dns/records/123456789", + Route("PUT /my/products/azone01/dns/records/123456789", servermock.ResponseFromFixture("invalid_record_id.json"). WithStatusCode(http.StatusNotFound)). Build(t) @@ -167,7 +168,7 @@ func TestClient_EditRecord_error(t *testing.T) { func TestClient_DeleteRecord(t *testing.T) { client := mockBuilder(). - Route("DELETE /accountname/apikey/my/products/azone01/dns/records/123456789", + Route("DELETE /my/products/azone01/dns/records/123456789", servermock.ResponseFromFixture("success.json")). Build(t) @@ -177,7 +178,7 @@ func TestClient_DeleteRecord(t *testing.T) { func TestClient_DeleteRecord_error(t *testing.T) { client := mockBuilder(). - Route("DELETE /accountname/apikey/my/products/azone01/dns/records/123456789", + Route("DELETE /my/products/azone01/dns/records/123456789", servermock.ResponseFromFixture("invalid_record_id.json"). WithStatusCode(http.StatusNotFound)). Build(t) diff --git a/providers/dns/simply/simply.toml b/providers/dns/simply/simply.toml index 2814fd955..c586e0db5 100644 --- a/providers/dns/simply/simply.toml +++ b/providers/dns/simply/simply.toml @@ -22,3 +22,4 @@ lego --email you@example.com --dns simply -d '*.example.com' -d example.com run [Links] API = "https://www.simply.com/en/docs/api/" + Spec = "https://generator.swagger.io/?url=https://api.simply.com/2/openapi.json#/" From 5c1e21308c61c29f474319afec9c52e62dd24519 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 28 Aug 2025 23:44:26 +0200 Subject: [PATCH 159/298] chore: update documentation theme (#2632) --- .github/workflows/documentation.yml | 2 +- .github/workflows/pr.yml | 2 +- Makefile | 4 ++-- cmd/zz_gen_cmd_dnshelp.go | 2 +- docs/Makefile | 8 +++---- docs/content/dns/zz_gen_ibmcloud.md | 2 +- docs/go.mod | 2 +- docs/go.sum | 4 ++-- docs/hugo.toml | 32 ++-------------------------- providers/dns/ibmcloud/ibmcloud.toml | 2 +- 10 files changed, 16 insertions(+), 44 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 46f7f6730..c0bbbfbdc 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: stable - HUGO_VERSION: 0.131.0 + HUGO_VERSION: 0.148.2 CGO_ENABLED: 0 steps: diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index cb5966f3d..b68a7810b 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -14,7 +14,7 @@ jobs: env: GO_VERSION: stable GOLANGCI_LINT_VERSION: v2.2.1 - HUGO_VERSION: 0.131.0 + HUGO_VERSION: 0.148.2 CGO_ENABLED: 0 LEGO_E2E_TESTS: CI MEMCACHED_HOSTS: localhost:11211 diff --git a/Makefile b/Makefile index 28cb33908..8536dfc40 100644 --- a/Makefile +++ b/Makefile @@ -54,10 +54,10 @@ detach: .PHONY: docs-build docs-serve docs-themes docs-build: generate-dns - @make -C ./docs hugo-build + @make -C ./docs build docs-serve: generate-dns - @make -C ./docs hugo + @make -C ./docs serve docs-themes: @make -C ./docs hugo-themes diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 9a4e24807..d49670f1b 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -1692,7 +1692,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Credentials:`) ew.writeln(` - "SOFTLAYER_API_KEY": Classic Infrastructure API key`) - ew.writeln(` - "SOFTLAYER_USERNAME": Username (IBM Cloud is _)`) + ew.writeln(` - "SOFTLAYER_USERNAME": Username (IBM Cloud is {accountID}_{emailAddress})`) ew.writeln() ew.writeln(`Additional Configuration:`) diff --git a/docs/Makefile b/docs/Makefile index 8e32681d1..6c84c7d1d 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,14 +1,14 @@ -.PHONY: default clean hugo hugo-build +.PHONY: default clean serve build -default: clean hugo +default: clean serve clean: rm -rf public/ -hugo-build: clean +build: clean hugo --enableGitInfo --source . -hugo: +serve: hugo server --disableFastRender --enableGitInfo --watch --source . # hugo server -D diff --git a/docs/content/dns/zz_gen_ibmcloud.md b/docs/content/dns/zz_gen_ibmcloud.md index c2f9f4fe1..94997b703 100644 --- a/docs/content/dns/zz_gen_ibmcloud.md +++ b/docs/content/dns/zz_gen_ibmcloud.md @@ -39,7 +39,7 @@ lego --email you@example.com --dns ibmcloud -d '*.example.com' -d example.com ru | Environment Variable Name | Description | |-----------------------|-------------| | `SOFTLAYER_API_KEY` | Classic Infrastructure API key | -| `SOFTLAYER_USERNAME` | Username (IBM Cloud is _) | +| `SOFTLAYER_USERNAME` | Username (IBM Cloud is {accountID}_{emailAddress}) | 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" %}}). diff --git a/docs/go.mod b/docs/go.mod index 5cb2add45..2240eb1e6 100644 --- a/docs/go.mod +++ b/docs/go.mod @@ -2,4 +2,4 @@ module github.com/go-acme/lego/docs go 1.20 -require github.com/McShelby/hugo-theme-relearn v0.0.0-20240802145348-259f21f89851 +require github.com/McShelby/hugo-theme-relearn v0.0.0-20250707094454-9803d5122ebb diff --git a/docs/go.sum b/docs/go.sum index 1ed963e87..b62d5c809 100644 --- a/docs/go.sum +++ b/docs/go.sum @@ -1,2 +1,2 @@ -github.com/McShelby/hugo-theme-relearn v0.0.0-20240802145348-259f21f89851 h1:JpmKIb1bRzuAcgnphwSb35Xz9rk/Alq19uRWVGSwScA= -github.com/McShelby/hugo-theme-relearn v0.0.0-20240802145348-259f21f89851/go.mod h1:mKQQdxZNIlLvAj8X3tMq+RzntIJSr9z7XdzuMomt0IM= +github.com/McShelby/hugo-theme-relearn v0.0.0-20250707094454-9803d5122ebb h1:iTGWOs8uKUaYmd7+wHRyPGXxt+SS5Bhvx2RRboYRXlI= +github.com/McShelby/hugo-theme-relearn v0.0.0-20250707094454-9803d5122ebb/go.mod h1:mKQQdxZNIlLvAj8X3tMq+RzntIJSr9z7XdzuMomt0IM= diff --git a/docs/hugo.toml b/docs/hugo.toml index a974cea73..b17206d43 100644 --- a/docs/hugo.toml +++ b/docs/hugo.toml @@ -2,47 +2,19 @@ baseURL = "https://go-acme.github.io/lego/" languageCode = "en-us" title = "Lego" -# Code highlighting settings -pygmentsCodefences = true -pygmentsCodeFencesGuesSsyntax = false -pygmentsOptions = "" -pygmentsStyle = "monokai" -# The monokai stylesheet is included in the base template. -pygmentsUseClasses = true - [permalinks] dns = "/dns/:slug/" [params] - # Prefix URL to edit current page. Will display an "Edit this page" button on top right hand corner of every page. - # Useful to give opportunity to people to create merge request for your doc. - # See the config.toml file from this documentation site to have an example. -# editURL = "" # Description of the site, will be used in meta information # description = "" # Shows a checkmark for visited pages on the menu showVisitedLinks = true - # Disable search function. It will hide search bar -# disableSearch = false - # Javascript and CSS cache are automatically busted when new version of site is generated. - # Set this to true to disable this behavior (some proxies don't handle well this optimization) -# disableAssetsBusting = false - # Set this to true to disable copy-to-clipboard button for inline code. -# disableInlineCopyToClipBoard = true - # A title for shortcuts in menu is set by default. Set this to true to disable it. -# disableShortcutsTitle = false - # When using mulitlingual website, disable the switch language button. -# disableLanguageSwitchingButton = false - # Hide breadcrumbs in the header and only show the current page title -# disableBreadcrumb = true - # Hide Next and Previous page buttons normally displayed full height beside content -# disableNextPrev = true - # Order sections in menu by "weight" or "title". Default to "weight" -# ordersectionsby = "weight" # Change default color scheme with a variant one. Can be "red", "blue", "green". themeVariant = "blue" custom_css = ["css/theme-custom.css"] disableLandingPageButton = true + hideAuthorEmail = true # Author of the site, will be used in meta information [params.author] @@ -71,7 +43,7 @@ pygmentsUseClasses = true weight = 12 [outputs] - home = [ "html", "rss", "search", "searchpage"] + home = ['html', 'rss', 'print'] [module] [[module.imports]] diff --git a/providers/dns/ibmcloud/ibmcloud.toml b/providers/dns/ibmcloud/ibmcloud.toml index 090c010c9..2a6c12f82 100644 --- a/providers/dns/ibmcloud/ibmcloud.toml +++ b/providers/dns/ibmcloud/ibmcloud.toml @@ -12,7 +12,7 @@ lego --email you@example.com --dns ibmcloud -d '*.example.com' -d example.com ru [Configuration] [Configuration.Credentials] - SOFTLAYER_USERNAME = "Username (IBM Cloud is _)" + SOFTLAYER_USERNAME = "Username (IBM Cloud is {accountID}_{emailAddress})" SOFTLAYER_API_KEY = "Classic Infrastructure API key" [Configuration.Additional] SOFTLAYER_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" From 2308cd47787a87f444e587aa759d02291fabd8a2 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 5 Sep 2025 15:35:49 +0200 Subject: [PATCH 160/298] feat(EAB): fallback to base64.URLEncoding (#2635) --- acme/api/account.go | 18 ++++++++++++++++-- acme/api/account_test.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 acme/api/account_test.go diff --git a/acme/api/account.go b/acme/api/account.go index 85de84ef3..cab5d477f 100644 --- a/acme/api/account.go +++ b/acme/api/account.go @@ -29,9 +29,9 @@ func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) { // NewEAB Creates a new account with an External Account Binding. func (a *AccountService) NewEAB(accMsg acme.Account, kid, hmacEncoded string) (acme.ExtendedAccount, error) { - hmac, err := base64.RawURLEncoding.DecodeString(hmacEncoded) + hmac, err := decodeEABHmac(hmacEncoded) if err != nil { - return acme.ExtendedAccount{}, fmt.Errorf("acme: could not decode hmac key: %w", err) + return acme.ExtendedAccount{}, err } eabJWS, err := a.core.signEABContent(a.core.GetDirectory().NewAccountURL, kid, hmac) @@ -83,3 +83,17 @@ func (a *AccountService) Deactivate(accountURL string) error { _, err := a.core.post(accountURL, req, nil) return err } + +func decodeEABHmac(hmacEncoded string) ([]byte, error) { + hmac, errRaw := base64.RawURLEncoding.DecodeString(hmacEncoded) + if errRaw == nil { + return hmac, nil + } + + hmac, err := base64.URLEncoding.DecodeString(hmacEncoded) + if err == nil { + return hmac, nil + } + + return nil, fmt.Errorf("acme: could not decode hmac key: %w", errors.Join(errRaw, err)) +} diff --git a/acme/api/account_test.go b/acme/api/account_test.go new file mode 100644 index 000000000..16bd80741 --- /dev/null +++ b/acme/api/account_test.go @@ -0,0 +1,35 @@ +package api + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_decodeEABHmac(t *testing.T) { + testCases := []struct { + desc string + hmac string + }{ + { + desc: "RawURLEncoding", + hmac: "BAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGhsUIjJBVSwWIzNHKC0UMHJZJT8OHx", + }, + { + desc: "URLEncoding", + hmac: "nKTo9Hu8fpCqWPXx-25LVbZrJWxcHISsr4qHrRR0j5U=", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + v, err := decodeEABHmac(test.hmac) + require.NoError(t, err) + + assert.NotEmpty(t, v) + }) + } +} From 6bfc09068080db054c1840d96bb0f136cc62e45c Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 7 Sep 2025 23:30:00 +0200 Subject: [PATCH 161/298] selectelv2: add missing options (#2639) --- cmd/zz_gen_cmd_dnshelp.go | 3 + docs/content/dns/zz_gen_selectelv2.md | 3 + providers/dns/selectelv2/selectelv2.go | 64 ++++++++++++++------- providers/dns/selectelv2/selectelv2.toml | 3 + providers/dns/selectelv2/selectelv2_test.go | 22 ++++--- 5 files changed, 66 insertions(+), 29 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index d49670f1b..7c43bb2a7 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -2935,11 +2935,14 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) + ew.writeln(` - "SELECTELV2_AUTH_REGION": Location for auth endpoint like ResellAPI or Keystone (default: 'ru-1')`) + ew.writeln(` - "SELECTELV2_AUTH_URL": Identity endpoint (defaul: 'https://cloud.api.selcloud.ru/identity/v3/')`) ew.writeln(` - "SELECTELV2_BASE_URL": API endpoint URL`) ew.writeln(` - "SELECTELV2_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "SELECTELV2_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 5)`) ew.writeln(` - "SELECTELV2_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln(` - "SELECTELV2_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) + ew.writeln(` - "SELECTELV2_USER_DOMAIN_NAME": To specify the domain name (account ID) where the user is located. (default: SELECTELV2_ACCOUNT_ID)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/selectelv2`) diff --git a/docs/content/dns/zz_gen_selectelv2.md b/docs/content/dns/zz_gen_selectelv2.md index 24dd67d1e..933ca201f 100644 --- a/docs/content/dns/zz_gen_selectelv2.md +++ b/docs/content/dns/zz_gen_selectelv2.md @@ -53,11 +53,14 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| +| `SELECTELV2_AUTH_REGION` | Location for auth endpoint like ResellAPI or Keystone (default: 'ru-1') | +| `SELECTELV2_AUTH_URL` | Identity endpoint (defaul: 'https://cloud.api.selcloud.ru/identity/v3/') | | `SELECTELV2_BASE_URL` | API endpoint URL | | `SELECTELV2_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `SELECTELV2_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 5) | | `SELECTELV2_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | | `SELECTELV2_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | +| `SELECTELV2_USER_DOMAIN_NAME` | To specify the domain name (account ID) where the user is located. (default: SELECTELV2_ACCOUNT_ID) | 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" %}}). diff --git a/providers/dns/selectelv2/selectelv2.go b/providers/dns/selectelv2/selectelv2.go index 1b5d55394..2654cd742 100644 --- a/providers/dns/selectelv2/selectelv2.go +++ b/providers/dns/selectelv2/selectelv2.go @@ -21,11 +21,14 @@ import ( const ( envNamespace = "SELECTELV2_" - EnvBaseURL = envNamespace + "BASE_URL" - EnvUsernameOS = envNamespace + "USERNAME" - EnvPasswordOS = envNamespace + "PASSWORD" - EnvAccount = envNamespace + "ACCOUNT_ID" - EnvProjectID = envNamespace + "PROJECT_ID" + EnvBaseURL = envNamespace + "BASE_URL" + EnvUsernameOS = envNamespace + "USERNAME" + EnvPasswordOS = envNamespace + "PASSWORD" + EnvDomainName = envNamespace + "ACCOUNT_ID" + EnvProjectID = envNamespace + "PROJECT_ID" + EnvAuthRegion = envNamespace + "AUTH_REGION" + EnvAuthURL = envNamespace + "AUTH_URL" + EnvUserDomainName = envNamespace + "USER_DOMAIN_NAME" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -34,7 +37,12 @@ const ( ) const ( - defaultBaseURL = "https://api.selectel.ru/domains/v2" + defaultBaseURL = "https://api.selectel.ru/domains/v2" + defaultAuthRegion = "ru-1" + defaultAuthURL = "https://cloud.api.selcloud.ru/identity/v3/" +) + +const ( defaultTTL = 60 defaultPropagationTimeout = 120 * time.Second defaultPollingInterval = 5 * time.Second @@ -47,11 +55,15 @@ var errNotFound = errors.New("rrset not found") // Config is used to configure the creation of the DNSProvider. type Config struct { - BaseURL string - Username string - Password string - Account string - ProjectID string + BaseURL string + Username string + Password string + DomainName string + ProjectID string + AuthURL string + AuthRegion string + UserDomainName string + TTL int PropagationTimeout time.Duration PollingInterval time.Duration @@ -61,7 +73,10 @@ type Config struct { // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - BaseURL: env.GetOrDefaultString(EnvBaseURL, defaultBaseURL), + BaseURL: env.GetOrDefaultString(EnvBaseURL, defaultBaseURL), + AuthRegion: env.GetOrDefaultString(EnvAuthRegion, defaultAuthRegion), + AuthURL: env.GetOrDefaultString(EnvAuthURL, defaultAuthURL), + TTL: env.GetOrDefaultInt(EnvTTL, defaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, defaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, defaultPollingInterval), @@ -78,7 +93,7 @@ type DNSProvider struct { // NewDNSProvider returns a DNSProvider instance configured for Selectel Domains APIv2. func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvUsernameOS, EnvPasswordOS, EnvAccount, EnvProjectID) + values, err := env.Get(EnvUsernameOS, EnvPasswordOS, EnvDomainName, EnvProjectID) if err != nil { return nil, fmt.Errorf("selectelv2: %w", err) } @@ -86,8 +101,9 @@ func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() config.Username = values[EnvUsernameOS] config.Password = values[EnvPasswordOS] - config.Account = values[EnvAccount] + config.DomainName = values[EnvDomainName] config.ProjectID = values[EnvProjectID] + config.UserDomainName = env.GetOrDefaultString(EnvUserDomainName, "") return NewDNSProviderConfig(config) } @@ -106,8 +122,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("selectelv2: missing password") } - if config.Account == "" { - return nil, errors.New("selectelv2: missing account") + if config.DomainName == "" { + return nil, errors.New("selectelv2: missing account ID") } if config.ProjectID == "" { @@ -133,7 +149,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { func (d *DNSProvider) Present(domain, _, keyAuth string) error { ctx := context.Background() - client, err := d.authorize() + client, err := d.authorize(ctx) if err != nil { return fmt.Errorf("selectelv2: authorize: %w", err) } @@ -180,7 +196,7 @@ func (d *DNSProvider) Present(domain, _, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { ctx := context.Background() - client, err := d.authorize() + client, err := d.authorize(ctx) if err != nil { return fmt.Errorf("selectelv2: authorize: %w", err) } @@ -221,8 +237,8 @@ func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { return nil } -func (d *DNSProvider) authorize() (*clientWrapper, error) { - token, err := obtainOpenstackToken(d.config) +func (d *DNSProvider) authorize(ctx context.Context) (*clientWrapper, error) { + token, err := obtainOpenstackToken(ctx, d.config) if err != nil { return nil, err } @@ -235,12 +251,16 @@ func (d *DNSProvider) authorize() (*clientWrapper, error) { }, nil } -func obtainOpenstackToken(config *Config) (string, error) { +func obtainOpenstackToken(ctx context.Context, config *Config) (string, error) { vpcClient, err := selvpcclient.NewClient(&selvpcclient.ClientOptions{ + Context: ctx, + DomainName: config.DomainName, + AuthURL: config.AuthURL, + AuthRegion: config.AuthRegion, Username: config.Username, Password: config.Password, - UserDomainName: config.Account, ProjectID: config.ProjectID, + UserDomainName: config.UserDomainName, }) if err != nil { return "", fmt.Errorf("new VPC client: %w", err) diff --git a/providers/dns/selectelv2/selectelv2.toml b/providers/dns/selectelv2/selectelv2.toml index 4993cb0f8..fd8dbda9f 100644 --- a/providers/dns/selectelv2/selectelv2.toml +++ b/providers/dns/selectelv2/selectelv2.toml @@ -20,6 +20,9 @@ lego --email you@example.com --dns selectelv2 -d '*.example.com' -d example.com SELECTELV2_PROJECT_ID = "Cloud project ID (UUID)" [Configuration.Additional] SELECTELV2_BASE_URL = "API endpoint URL" + SELECTELV2_AUTH_REGION = "Location for auth endpoint like ResellAPI or Keystone (default: 'ru-1')" + SELECTELV2_AUTH_URL = "Identity endpoint (defaul: 'https://cloud.api.selcloud.ru/identity/v3/')" + SELECTELV2_USER_DOMAIN_NAME = "To specify the domain name (account ID) where the user is located. (default: SELECTELV2_ACCOUNT_ID)" SELECTELV2_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 5)" SELECTELV2_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" SELECTELV2_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" diff --git a/providers/dns/selectelv2/selectelv2_test.go b/providers/dns/selectelv2/selectelv2_test.go index 4859b9932..265c17e90 100644 --- a/providers/dns/selectelv2/selectelv2_test.go +++ b/providers/dns/selectelv2/selectelv2_test.go @@ -11,7 +11,15 @@ import ( const envDomain = envNamespace + "DOMAIN" -var envTest = tester.NewEnvTest(EnvUsernameOS, EnvPasswordOS, EnvAccount, EnvProjectID). +var envTest = tester.NewEnvTest( + EnvUsernameOS, + EnvPasswordOS, + EnvDomainName, + EnvUserDomainName, + EnvProjectID, + EnvAuthRegion, + EnvAuthURL, +). WithDomain(envDomain) func TestNewDNSProvider(t *testing.T) { @@ -25,7 +33,7 @@ func TestNewDNSProvider(t *testing.T) { envVars: map[string]string{ EnvUsernameOS: "someName", EnvPasswordOS: "qwerty", - EnvAccount: "1", + EnvDomainName: "1", EnvProjectID: "111a11111aaa11aa1a11aaa11111aa1a", }, }, @@ -33,7 +41,7 @@ func TestNewDNSProvider(t *testing.T) { desc: "missing username", envVars: map[string]string{ EnvPasswordOS: "qwerty", - EnvAccount: "1", + EnvDomainName: "1", EnvProjectID: "111a11111aaa11aa1a11aaa11111aa1a", }, expected: "selectelv2: some credentials information are missing: SELECTELV2_USERNAME", @@ -42,7 +50,7 @@ func TestNewDNSProvider(t *testing.T) { desc: "missing password", envVars: map[string]string{ EnvUsernameOS: "someName", - EnvAccount: "1", + EnvDomainName: "1", EnvProjectID: "111a11111aaa11aa1a11aaa11111aa1a", }, expected: "selectelv2: some credentials information are missing: SELECTELV2_PASSWORD", @@ -61,7 +69,7 @@ func TestNewDNSProvider(t *testing.T) { envVars: map[string]string{ EnvUsernameOS: "someName", EnvPasswordOS: "qwerty", - EnvAccount: "1", + EnvDomainName: "1", }, expected: "selectelv2: some credentials information are missing: SELECTELV2_PROJECT_ID", }, @@ -123,7 +131,7 @@ func TestNewDNSProviderConfig(t *testing.T) { username: "user", password: "secret", projectID: "111a11111aaa11aa1a11aaa11111aa1a", - expected: "selectelv2: missing account", + expected: "selectelv2: missing account ID", }, { desc: "missing projectID", @@ -139,7 +147,7 @@ func TestNewDNSProviderConfig(t *testing.T) { config := NewDefaultConfig() config.Username = test.username config.Password = test.password - config.Account = test.account + config.DomainName = test.account config.ProjectID = test.projectID p, err := NewDNSProviderConfig(config) From de8959229d5e13f9d73df47444a992f2350ae9b8 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 11 Sep 2025 17:51:36 +0200 Subject: [PATCH 162/298] chore: update dependencies (#2640) --- go.mod | 152 ++++++----- go.sum | 343 ++++++++++++------------ providers/dns/namesilo/namesilo.go | 16 +- providers/dns/namesilo/namesilo_test.go | 2 +- 4 files changed, 265 insertions(+), 248 deletions(-) diff --git a/go.mod b/go.mod index 72109a3d0..dc847166a 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/go-acme/lego/v4 go 1.24.0 require ( - cloud.google.com/go/compute/metadata v0.7.0 + cloud.google.com/go/compute/metadata v0.8.0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 @@ -15,26 +15,26 @@ require ( github.com/Azure/go-autorest/autorest/to v0.4.1 github.com/BurntSushi/toml v1.5.0 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 - github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.0.0 - github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.9 - github.com/alibabacloud-go/tea v1.3.10 - github.com/aliyun/credentials-go v1.4.6 - github.com/aws/aws-sdk-go-v2 v1.36.6 - github.com/aws/aws-sdk-go-v2/config v1.29.18 - github.com/aws/aws-sdk-go-v2/credentials v1.17.71 - github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.5 - github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1 - github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1 - github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 + github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 + github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.11 + github.com/alibabacloud-go/tea v1.3.12 + github.com/aliyun/credentials-go v1.4.7 + github.com/aws/aws-sdk-go-v2 v1.39.0 + github.com/aws/aws-sdk-go-v2/config v1.31.8 + github.com/aws/aws-sdk-go-v2/credentials v1.18.12 + github.com/aws/aws-sdk-go-v2/service/lightsail v1.48.4 + github.com/aws/aws-sdk-go-v2/service/route53 v1.58.2 + github.com/aws/aws-sdk-go-v2/service/s3 v1.88.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.38.4 github.com/aziontech/azionapi-go-sdk v0.142.0 - github.com/baidubce/bce-sdk-go v0.9.235 + github.com/baidubce/bce-sdk-go v0.9.243 github.com/cenkalti/backoff/v4 v4.3.0 github.com/dnsimple/dnsimple-go/v4 v4.0.0 - github.com/exoscale/egoscale/v3 v3.1.24 - github.com/go-acme/alidns-20150109/v4 v4.5.11-1 - github.com/go-acme/tencentclouddnspod v1.0.1208 - github.com/go-acme/tencentedgdeone v1.0.1212 - github.com/go-jose/go-jose/v4 v4.1.1 + github.com/exoscale/egoscale/v3 v3.1.26 + github.com/go-acme/alidns-20150109/v4 v4.6.0 + github.com/go-acme/tencentclouddnspod v1.1.10 + github.com/go-acme/tencentedgdeone v1.1.19 + github.com/go-jose/go-jose/v4 v4.1.2 github.com/go-viper/mapstructure/v2 v2.4.0 github.com/google/go-cmp v0.7.0 github.com/google/go-querystring v1.1.0 @@ -42,15 +42,15 @@ require ( github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/go-version v1.7.0 - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.159 + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.168 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 github.com/labbsr0x/bindman-dns-webhook v1.0.2 - github.com/ldez/grignotin v0.9.0 - github.com/linode/linodego v1.53.0 + github.com/ldez/grignotin v0.10.0 + github.com/linode/linodego v1.57.0 github.com/liquidweb/liquidweb-go v1.6.4 github.com/mattn/go-isatty v0.0.20 - github.com/miekg/dns v1.1.67 + github.com/miekg/dns v1.1.68 github.com/mimuret/golang-iij-dpf v0.9.1 github.com/namedotcom/go/v4 v4.0.2 github.com/nrdcg/auroradns v1.1.0 @@ -61,49 +61,49 @@ require ( github.com/nrdcg/goacmedns v0.2.0 github.com/nrdcg/goinwx v0.11.0 github.com/nrdcg/mailinabox v0.2.0 - github.com/nrdcg/namesilo v0.2.1 + github.com/nrdcg/namesilo v0.5.0 github.com/nrdcg/nodion v0.1.0 - github.com/nrdcg/oci-go-sdk/common/v1065 v1065.95.2 - github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.95.2 + github.com/nrdcg/oci-go-sdk/common/v1065 v1065.100.0 + github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.100.0 github.com/nrdcg/porkbun v0.4.0 github.com/nzdjb/go-metaname v1.0.0 github.com/ovh/go-ovh v1.9.0 github.com/pquerna/otp v1.5.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.3.2 - github.com/sacloud/iaas-api-go v1.16.1 + github.com/sacloud/api-client-go v0.3.3 + github.com/sacloud/iaas-api-go v1.17.1 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34 github.com/selectel/domains-go v1.1.0 github.com/selectel/go-selvpcclient/v4 v4.1.0 github.com/softlayer/softlayer-go v1.1.7 - github.com/stretchr/testify v1.10.0 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1212 + github.com/stretchr/testify v1.11.1 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.26 github.com/transip/gotransip/v6 v6.26.0 - github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec + github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 github.com/urfave/cli/v2 v2.27.7 github.com/vinyldns/go-vinyldns v0.9.16 - github.com/volcengine/volc-sdk-golang v1.0.216 - github.com/vultr/govultr/v3 v3.21.1 - github.com/yandex-cloud/go-genproto v0.14.0 - github.com/yandex-cloud/go-sdk/services/dns v0.0.3 - github.com/yandex-cloud/go-sdk/v2 v2.0.8 - golang.org/x/crypto v0.40.0 - golang.org/x/net v0.42.0 - golang.org/x/oauth2 v0.30.0 - golang.org/x/text v0.27.0 - golang.org/x/time v0.12.0 - google.golang.org/api v0.242.0 - gopkg.in/ns1/ns1-go.v2 v2.14.4 + github.com/volcengine/volc-sdk-golang v1.0.219 + github.com/vultr/govultr/v3 v3.23.0 + github.com/yandex-cloud/go-genproto v0.23.0 + github.com/yandex-cloud/go-sdk/services/dns v0.0.12 + github.com/yandex-cloud/go-sdk/v2 v2.11.0 + golang.org/x/crypto v0.42.0 + golang.org/x/net v0.44.0 + golang.org/x/oauth2 v0.31.0 + golang.org/x/text v0.29.0 + golang.org/x/time v0.13.0 + google.golang.org/api v0.249.0 + gopkg.in/ns1/ns1-go.v2 v2.15.0 gopkg.in/yaml.v2 v2.4.0 - software.sslmate.com/src/go-pkcs12 v0.5.0 + software.sslmate.com/src/go-pkcs12 v0.6.0 ) require ( - cloud.google.com/go/auth v0.16.2 // indirect + cloud.google.com/go/auth v0.16.5 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect @@ -115,50 +115,52 @@ require ( github.com/alibabacloud-go/debug v1.0.1 // indirect github.com/alibabacloud-go/openapi-util v0.1.1 // indirect github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.37 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.18 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.25.6 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4 // indirect - github.com/aws/smithy-go v1.22.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.29.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.4 // indirect + github.com/aws/smithy-go v1.23.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dimchansky/utfbom v1.1.1 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-errors/errors v1.0.1 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.23.0 // indirect github.com/go-resty/resty/v2 v2.16.5 // indirect + github.com/goccy/go-yaml v1.9.8 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect - github.com/googleapis/gax-go/v2 v2.14.2 // indirect + github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect + github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect github.com/kylelemons/godebug v1.1.0 // indirect @@ -166,6 +168,7 @@ require ( github.com/leodido/go-urn v1.4.0 // indirect github.com/liquidweb/liquidweb-cli v0.6.9 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -198,21 +201,22 @@ require ( go.mongodb.org/mongo-driver v1.13.1 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.3.1 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect - golang.org/x/mod v0.25.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/tools v0.34.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect - google.golang.org/grpc v1.73.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/tools v0.36.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/grpc v1.75.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 019e2b8dd..11371d2db 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,8 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4= -cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA= +cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI= +cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -23,8 +23,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= -cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA= +cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -42,14 +42,14 @@ github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYs github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 h1:ci6Yd6nysBRLEodoziB6ah1+YOzZbZk+NYneoA6q+6E= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 h1:MhRfI58HblXzCtWEZCO0feHs8LweePB3s90r7WaR1KU= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0/go.mod h1:okZ+ZURbArNdlJ+ptXoyHNuOETzOl1Oww19rm8I2WLA= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw= @@ -105,8 +105,8 @@ github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.0.0 h1:ot8yMzEm0Kx2LCTOzlM7zh4ASLFZ6H0iYhtIKolSujs= -github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.0.0/go.mod h1:rvh3imDA6EaQi+oM/GQHkQAOHbXPKJ7EWJvfjuw141Q= +github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 h1:h/33OxYLqBk0BYmEbSUy7MlvgQR/m1w1/7OJFKoPL1I= +github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0/go.mod h1:rvh3imDA6EaQi+oM/GQHkQAOHbXPKJ7EWJvfjuw141Q= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -123,8 +123,8 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= -github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.9 h1:7P0KWfed/YMtpeuW3E2iwokzoz9L7H9rB+VZzg5DeBs= -github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.9/go.mod h1:kgnXaV74AVjM3ZWJu1GhyXGuCtxljJ677oUfz6MyJOE= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.11 h1:5JIs4BPVpzbgcBrgmUxBdDsMjmsHZvPbTPD1m4aB+ZQ= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.11/go.mod h1:ue0+WkdPxpCB2JP3iaG4Iawayxp72kyT5uDbozQKaW8= github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= @@ -145,8 +145,9 @@ github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/Ke github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= -github.com/alibabacloud-go/tea v1.3.10 h1:J0Ke8iMyoxX2daj90hdPr1QgfxJnhR8SOflB910o/Dk= -github.com/alibabacloud-go/tea v1.3.10/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= +github.com/alibabacloud-go/tea v1.3.11/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= +github.com/alibabacloud-go/tea v1.3.12 h1:ir2Io80UlBy1JHf7t+uCTxmaGQtiEta1WpV29NGJTkE= +github.com/alibabacloud-go/tea v1.3.12/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= @@ -156,8 +157,8 @@ github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6q github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= -github.com/aliyun/credentials-go v1.4.6 h1:CG8rc/nxCNKfXbZWpWDzI9GjF4Tuu3Es14qT8Y0ClOk= -github.com/aliyun/credentials-go v1.4.6/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= +github.com/aliyun/credentials-go v1.4.7 h1:T17dLqEtPUFvjDRRb5giVvLh6dFT8IcNFJJb7MeyCxw= +github.com/aliyun/credentials-go v1.4.7/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -170,52 +171,52 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.36.6 h1:zJqGjVbRdTPojeCGWn5IR5pbJwSQSBh5RWFTQcEQGdU= -github.com/aws/aws-sdk-go-v2 v1.36.6/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY= -github.com/aws/aws-sdk-go-v2/config v1.29.18 h1:x4T1GRPnqKV8HMJOMtNktbpQMl3bIsfx8KbqmveUO2I= -github.com/aws/aws-sdk-go-v2/config v1.29.18/go.mod h1:bvz8oXugIsH8K7HLhBv06vDqnFv3NsGDt2Znpk7zmOU= -github.com/aws/aws-sdk-go-v2/credentials v1.17.71 h1:r2w4mQWnrTMJjOyIsZtGp3R3XGY3nqHn8C26C2lQWgA= -github.com/aws/aws-sdk-go-v2/credentials v1.17.71/go.mod h1:E7VF3acIup4GB5ckzbKFrCK0vTvEQxOxgdq4U3vcMCY= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33 h1:D9ixiWSG4lyUBL2DDNK924Px9V/NBVpML90MHqyTADY= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.33/go.mod h1:caS/m4DI+cij2paz3rtProRBI4s/+TCiWoaWZuQ9010= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37 h1:osMWfm/sC/L4tvEdQ65Gri5ZZDCUpuYJZbTTDrsn4I0= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.37/go.mod h1:ZV2/1fbjOPr4G4v38G3Ww5TBT4+hmsK45s/rxu1fGy0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37 h1:v+X21AvTb2wZ+ycg1gx+orkB/9U6L7AOp93R7qYxsxM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.37/go.mod h1:G0uM1kyssELxmJ2VZEfG0q2npObR3BAkF3c1VsfVnfs= +github.com/aws/aws-sdk-go-v2 v1.39.0 h1:xm5WV/2L4emMRmMjHFykqiA4M/ra0DJVSWUkDyBjbg4= +github.com/aws/aws-sdk-go-v2 v1.39.0/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00= +github.com/aws/aws-sdk-go-v2/config v1.31.8 h1:kQjtOLlTU4m4A64TsRcqwNChhGCwaPBt+zCQt/oWsHU= +github.com/aws/aws-sdk-go-v2/config v1.31.8/go.mod h1:QPpc7IgljrKwH0+E6/KolCgr4WPLerURiU592AYzfSY= +github.com/aws/aws-sdk-go-v2/credentials v1.18.12 h1:zmc9e1q90wMn8wQbjryy8IwA6Q4XlaL9Bx2zIqdNNbk= +github.com/aws/aws-sdk-go-v2/credentials v1.18.12/go.mod h1:3VzdRDR5u3sSJRI4kYcOSIBbeYsgtVk7dG5R/U6qLWY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 h1:Is2tPmieqGS2edBnmOJIbdvOA6Op+rRpaYR60iBAwXM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7/go.mod h1:F1i5V5421EGci570yABvpIXgRIBPb5JM+lSkHF6Dq5w= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 h1:UCxq0X9O3xrlENdKf1r9eRJoKz/b0AfGkpp3a7FPlhg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7/go.mod h1:rHRoJUNUASj5Z/0eqI4w32vKvC7atoWR0jC+IkmVH8k= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 h1:Y6DTZUn7ZUC4th9FMBbo8LVE+1fyq3ofw+tRwkUd3PY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7/go.mod h1:x3XE6vMnU9QvHN/Wrx2s44kwzV2o2g5x/siw4ZUJ9g8= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.37 h1:XTZZ0I3SZUHAtBLBU6395ad+VOblE0DwQP6MuaNeics= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.37/go.mod h1:Pi6ksbniAWVwu2S8pEzcYPyhUkAcLaufxN7PfAUQjBk= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7 h1:BszAktdUo2xlzmYHjWMq70DqJ7cROM8iBd3f6hrpuMQ= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7/go.mod h1:XJ1yHki/P7ZPuG4fd3f0Pg/dSGA2cTQBCLw82MH2H48= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.5 h1:M5/B8JUaCI8+9QD+u3S/f4YHpvqE9RpSkV3rf0Iks2w= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.5/go.mod h1:Bktzci1bwdbpuLiu3AOksiNPMl/LLKmX1TWmqp2xbvs= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18 h1:vvbXsA2TVO80/KT7ZqCbx934dt6PY+vQ8hZpUZ/cpYg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.18/go.mod h1:m2JJHledjBGNMsLOF1g9gbAxprzq3KjC8e4lxtn+eWg= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.18 h1:OS2e0SKqsU2LiJPqL8u9x41tKc6MMEHrWjLVLn3oysg= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.18/go.mod h1:+Yrk+MDGzlNGxCXieljNeWpoZTCQUQVL+Jk9hGGJ8qM= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.5 h1:DYQbfSAWcMwRM0LbCDyQkPB1AcaZcLzLoaFrYcpyMag= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.5/go.mod h1:Lav4KLgncVjjrwLWutOccjEgJ4T/RAdY+Ic0hmNIgI0= -github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1 h1:R3nSX1hguRy6MnknHiepSvqnnL8ansFwK2hidPesAYU= -github.com/aws/aws-sdk-go-v2/service/route53 v1.53.1/go.mod h1:fmSiB4OAghn85lQgk7XN9l9bpFg5Bm1v3HuaXKytPEw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1 h1:RkHXU9jP0DptGy7qKI8CBGsUJruWz0v5IgwBa2DwWcU= -github.com/aws/aws-sdk-go-v2/service/s3 v1.84.1/go.mod h1:3xAOf7tdKF+qbb+XpU+EPhNXAdun3Lu1RcDrj8KC24I= -github.com/aws/aws-sdk-go-v2/service/sso v1.25.6 h1:rGtWqkQbPk7Bkwuv3NzpE/scwwL9sC1Ul3tn9x83DUI= -github.com/aws/aws-sdk-go-v2/service/sso v1.25.6/go.mod h1:u4ku9OLv4TO4bCPdxf4fA1upaMaJmP9ZijGk3AAOC6Q= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4 h1:OV/pxyXh+eMA0TExHEC4jyWdumLxNbzz1P0zJoezkJc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.4/go.mod h1:8Mm5VGYwtm+r305FfPSuc+aFkrypeylGYhFim6XEPoc= -github.com/aws/aws-sdk-go-v2/service/sts v1.34.1 h1:aUrLQwJfZtwv3/ZNG2xRtEen+NqI3iesuacjP51Mv1s= -github.com/aws/aws-sdk-go-v2/service/sts v1.34.1/go.mod h1:3wFBZKoWnX3r+Sm7in79i54fBmNfwhdNdQuscCw7QIk= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7 h1:zmZ8qvtE9chfhBPuKB2aQFxW5F/rpwXUgmcVCgQzqRw= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7/go.mod h1:vVYfbpd2l+pKqlSIDIOgouxNsGu5il9uDp0ooWb0jys= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 h1:mLgc5QIgOy26qyh5bvW+nDoAppxgn3J2WV3m9ewq7+8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7/go.mod h1:wXb/eQnqt8mDQIQTTmcw58B5mYGxzLGZGK8PWNFZ0BA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7 h1:u3VbDKUCWarWiU+aIUK4gjTr/wQFXV17y3hgNno9fcA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7/go.mod h1:/OuMQwhSyRapYxq6ZNpPer8juGNrB4P5Oz8bZ2cgjQE= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.48.4 h1:rJjEP5CSJw3Xsoe5Lvhbvr5P8q+rdt8/5IL2MDCc5n0= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.48.4/go.mod h1:O5Ew7rQ2iERj/HtA0AxBWymP0UVcG4iuMoIQzbRhcZU= +github.com/aws/aws-sdk-go-v2/service/route53 v1.58.2 h1:uqxTxY0i8b1ZFHxIf6pZYpUCOuYV/xxcgTv0vDz8Iig= +github.com/aws/aws-sdk-go-v2/service/route53 v1.58.2/go.mod h1:py/7C8W37SHqyHk6tkvZKiFDvMA/WkfPv5Qd8dUXYQw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.88.1 h1:+RpGuaQ72qnU83qBKVwxkznewEdAGhIWo/PQCmkhhog= +github.com/aws/aws-sdk-go-v2/service/s3 v1.88.1/go.mod h1:xajPTguLoeQMAOE44AAP2RQoUhF8ey1g5IFHARv71po= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.3 h1:7PKX3VYsZ8LUWceVRuv0+PU+E7OtQb1lgmi5vmUE9CM= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.3/go.mod h1:Ql6jE9kyyWI5JHn+61UT/Y5Z0oyVJGmgmJbZD5g4unY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.4 h1:e0XBRn3AptQotkyBFrHAxFB8mDhAIOfsG+7KyJ0dg98= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.4/go.mod h1:XclEty74bsGBCr1s0VSaA11hQ4ZidK4viWK7rRfO88I= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.4 h1:PR00NXRYgY4FWHqOGx3fC3lhVKjsp1GdloDv2ynMSd8= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.4/go.mod h1:Z+Gd23v97pX9zK97+tX4ppAgqCt3Z2dIXB02CtBncK8= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw= -github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= +github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/aziontech/azionapi-go-sdk v0.142.0 h1:1NOHXlC0/7VgbfbTIGVpsYn1THCugM4/SCOXVdUHQ+A= github.com/aziontech/azionapi-go-sdk v0.142.0/go.mod h1:cA5DY/VP4X5Eu11LpQNzNn83ziKjja7QVMIl4J45feA= -github.com/baidubce/bce-sdk-go v0.9.235 h1:iAi+seH9w1Go2szFNzyGumahLGDsuYZ3i8hduX3qiM8= -github.com/baidubce/bce-sdk-go v0.9.235/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/baidubce/bce-sdk-go v0.9.243 h1:6/yb519gFiABE6U1qbVZzBmEonfEGj5qcXrmIkbkFyQ= +github.com/baidubce/bce-sdk-go v0.9.243/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -233,12 +234,9 @@ github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -267,8 +265,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= @@ -287,10 +283,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/exoscale/egoscale/v3 v3.1.24 h1:EUWmjw/JgMj1faX5ojosjrJE5eY0QEWP0KBmLyFU6aE= -github.com/exoscale/egoscale/v3 v3.1.24/go.mod h1:A53enXfm8nhVMpIYw0QxiwQ2P6AdCF4F/nVYChNEzdE= +github.com/exoscale/egoscale/v3 v3.1.26 h1:bXXT0zVLbE4QFm6tmt0bg6ZPk9pQgUA3Z8SJrctQ7b0= +github.com/exoscale/egoscale/v3 v3.1.26/go.mod h1:0iY8OxgHJCS5TKqDNhwOW95JBKCnBZl3YGU4Yt+NqkU= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= @@ -314,20 +311,20 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-acme/alidns-20150109/v4 v4.5.11-1 h1:SAYWMWGLEyaSvKFxdw/O7XNDfSIn4yK5ig5v5O2bX8E= -github.com/go-acme/alidns-20150109/v4 v4.5.11-1/go.mod h1:ZCuTWP0+J6sGCQpMNWhOUVK5vLvNsAF+oT2EmMrJA8U= -github.com/go-acme/tencentclouddnspod v1.0.1208 h1:xAVy1lmg2KcKKeYmFSBQUttwc1o1S++9QTjAotGC+BM= -github.com/go-acme/tencentclouddnspod v1.0.1208/go.mod h1:yxG02mkbbVd7lTb97nOn7oj09djhm7hAwxNQw4B9dpQ= -github.com/go-acme/tencentedgdeone v1.0.1212 h1:H0HaYrkyX4htOXgusb6Mf8mKDIr/+CIqq5hbOdQB2EY= -github.com/go-acme/tencentedgdeone v1.0.1212/go.mod h1:IADOuBpaFM1IIKFedlJj7EfMqstThcm1g1g1uVejgxo= +github.com/go-acme/alidns-20150109/v4 v4.6.0 h1:1w0AZXlroykumr83vfxdYcM8L8bFWkV6SbqhxElzT4w= +github.com/go-acme/alidns-20150109/v4 v4.6.0/go.mod h1:qCK/gDRcJZrr5D8OIWuUugECsrdrJZnLXjWF7Mqi91g= +github.com/go-acme/tencentclouddnspod v1.1.10 h1:ERVJ4mc3cT4Nb3+n6H/c1AwZnChGBqLoymE0NVYscKI= +github.com/go-acme/tencentclouddnspod v1.1.10/go.mod h1:Bo/0YQJ/99FM+44HmCQkByuptX1tJsJ9V14MGV/2Qco= +github.com/go-acme/tencentedgdeone v1.1.19 h1:1jdEpMITrDuXHnu7QLOy2hpLW0BlDof70/KuRT+EiTo= +github.com/go-acme/tencentedgdeone v1.1.19/go.mod h1:gpu7HvXfcKWBrq5HAEBowZNkdk7JFPkagRzC/infUY0= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= -github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= +github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= +github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= @@ -338,18 +335,22 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= @@ -364,6 +365,8 @@ github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= +github.com/goccy/go-yaml v1.9.8 h1:5gMyLUeU1/6zl+WFfR1hN7D2kf+1/eRGa7DFtToiBvQ= +github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= @@ -375,8 +378,8 @@ github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -459,8 +462,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= -github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= +github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw= github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= @@ -525,8 +528,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.159 h1:6LZysc4iyO4cHB1aJsRklWfSEJr8CEhW7BmcM0SkYcU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.159/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.168 h1:CQcnff1kIag7rq12IcdTsF0xIW6WJoUx4MqNnDib420= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.168/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -537,8 +540,8 @@ github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 h1:AKsihjFT/t6Y0keEv3p59DACcOuh0inWXdUB0ZOzYH0= github.com/infobloxopen/infoblox-go-client/v2 v2.10.0/go.mod h1:NeNJpz09efw/edzqkVivGv1bWqBXTomqYBRFbP+XBqg= github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= -github.com/jarcoal/httpmock v1.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k= -github.com/jarcoal/httpmock v1.4.0/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= +github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A= +github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= @@ -556,8 +559,9 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU= +github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -594,12 +598,13 @@ github.com/labbsr0x/bindman-dns-webhook v1.0.2 h1:I7ITbmQPAVwrDdhd6dHKi+MYJTJqPC github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA= github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk= github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= -github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= -github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= +github.com/ldez/grignotin v0.10.0 h1:NQPeh1E/Eza4F0exCeC1WkpnLvgUcQDT8MQ1vOLML0E= +github.com/ldez/grignotin v0.10.0/go.mod h1:oR4iCKUP9fwoeO6vCQeD7M5SMxCT6xdVas4vg0h1LaI= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/linode/linodego v1.53.0 h1:UWr7bUUVMtcfsuapC+6blm6+jJLPd7Tf9MZUpdOERnI= -github.com/linode/linodego v1.53.0/go.mod h1:bI949fZaVchjWyKIA08hNyvAcV6BAS+PM2op3p7PAWA= +github.com/linode/linodego v1.57.0 h1:B5cl2gRNtaY1TIQ7B4uAhDa8NjtiWdEnWUO8nkHaU0A= +github.com/linode/linodego v1.57.0/go.mod h1:7zol1dqpLRdAT9s04mIaUhA+rXaRERFnKBERcQZiEeg= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= @@ -622,6 +627,7 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -634,8 +640,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0= -github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= +github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= +github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34= github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= @@ -692,14 +698,14 @@ github.com/nrdcg/goinwx v0.11.0 h1:GER0SE3POub7rxARt3Y3jRy1OON1hwF1LRxHz5xsFBw= github.com/nrdcg/goinwx v0.11.0/go.mod h1:0BXSC0FxVtU4aTjX0Zw3x0DK32tjugLzeNIAGtwXvPQ= github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk= github.com/nrdcg/mailinabox v0.2.0/go.mod h1:0yxqeYOiGyxAu7Sb94eMxHPIOsPYXAjTeA9ZhePhGnc= -github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg= -github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= +github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE= +github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.95.2 h1:a7QUZD5c+NkrFrdkdyJUO9cOUo8VQJyRkcIzk9Wh+DI= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.95.2/go.mod h1:O6osg9dPzXq7H2ib/1qzimzG5oXSJFgccR7iawg7SwA= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.95.2 h1:yflYnbQu4ciWH/GEztqlAccLPw4k5mp11uhW++al5ow= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.95.2/go.mod h1:atPDu37gu8HT7TtPpovrkgNmDAgOGM6TVEJ7ANTblMs= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.100.0 h1:QvgOjQ7QW8M4f+FeRr43looh/hiQP2y6RRVHsuPu5YI= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.100.0/go.mod h1:sOWH1Rqtipy3kyrIER0JLge8O7n8pIr7G8UVEs6xaDY= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.100.0 h1:TvZNfl2qbBNxtLWbdNMLM02Ygukj7VW3IVsay0FxwRo= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.100.0/go.mod h1:wVoSAX7BxmHUJX4FZ1E9hyxtoupsCUfE4nQeoZs+GSQ= github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -790,8 +796,6 @@ 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/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= -github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= 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= @@ -803,12 +807,12 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sacloud/api-client-go v0.3.2 h1:INbdSpQbyGN9Ai4hQ+Gbv3UQcgtRPG2tJrOmqT7HGl0= -github.com/sacloud/api-client-go v0.3.2/go.mod h1:0p3ukcWYXRCc2AUWTl1aA+3sXLvurvvDqhRaLZRLBwo= +github.com/sacloud/api-client-go v0.3.3 h1:ZpSAyGpITA8UFO3Hq4qMHZLGuNI1FgxAxo4sqBnCKDs= +github.com/sacloud/api-client-go v0.3.3/go.mod h1:0p3ukcWYXRCc2AUWTl1aA+3sXLvurvvDqhRaLZRLBwo= github.com/sacloud/go-http v0.1.9 h1:Xa5PY8/pb7XWhwG9nAeXSrYXPbtfBWqawgzxD5co3VE= github.com/sacloud/go-http v0.1.9/go.mod h1:DpDG+MSyxYaBwPJ7l3aKLMzwYdTVtC5Bo63HActcgoE= -github.com/sacloud/iaas-api-go v1.16.1 h1:B5Lec9WyZkrOCjtGkVuPn5RxDm/zCzazVsHh7BQIjYQ= -github.com/sacloud/iaas-api-go v1.16.1/go.mod h1:QVPHLwYzpECMsuml55I3FWAggsb4XSuzYGE9re/SkrQ= +github.com/sacloud/iaas-api-go v1.17.1 h1:DeNbmyjNITHZUo2ZLWhxALUKjoZ7zih7M0BvwYbwHWo= +github.com/sacloud/iaas-api-go v1.17.1/go.mod h1:T3SG9JXPH7XPfxhNA+G81WLeujmCdlh5dgTD8OsAj1g= github.com/sacloud/packages-go v0.0.11 h1:hrRWLmfPM9w7GBs6xb5/ue6pEMl8t1UuDKyR/KfteHo= github.com/sacloud/packages-go v0.0.11/go.mod h1:XNF5MCTWcHo9NiqWnYctVbASSSZR3ZOmmQORIzcurJ8= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= @@ -885,17 +889,18 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1208/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1212 h1:61myGZ5h8YgmFEziBGuKPSnPNs75KlqDQwEeGC2dwyk= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1212/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.10/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.19/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.26 h1:h8/jVLNbsLFQczGX6NDp1GcJahA81ytDj9R/wrq0i8k= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.26/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= @@ -903,17 +908,17 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/transip/gotransip/v6 v6.26.0 h1:Aejfvh8rSp8Mj2GX/RpdBjMCv+Iy/DmgfNgczPDP550= github.com/transip/gotransip/v6 v6.26.0/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec h1:2s/ghQ8wKE+UzD/hf3P4Gd1j0JI9ncbxv+nsypPoUYI= -github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7Qs3ku1ckpqed8tCRSqTlp8NAeZfAVpfx4OzXMss= +github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 h1:/VaznPrb/b68e3iMvkr27fU7JqPKU4j7tIITZnjQX1k= +github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419/go.mod h1:QN0/PdenvYWB0GRMz6JJbPeZz2Lph2iys1p8AFVHm2c= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= -github.com/volcengine/volc-sdk-golang v1.0.216 h1:+wAq8RvxpGECveRJaAXZFpzrZoQ33WjMuRyd9iY2Oc0= -github.com/volcengine/volc-sdk-golang v1.0.216/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= -github.com/vultr/govultr/v3 v3.21.1 h1:0cnA8fXiqayPGbAlNHaW+5oCQjpDNkkAm3Nt3LOHplM= -github.com/vultr/govultr/v3 v3.21.1/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= +github.com/volcengine/volc-sdk-golang v1.0.219 h1:IqMCdpJ6uuqS2ZZQYUVHKVd+2H1au0NDsSt0wx6hv9k= +github.com/volcengine/volc-sdk-golang v1.0.219/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= +github.com/vultr/govultr/v3 v3.23.0 h1:0jZo4FI+oMkPXFez1bvhsb5Ql0EZUFbe3SNLq3d8IIY= +github.com/vultr/govultr/v3 v3.23.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= @@ -922,12 +927,12 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yandex-cloud/go-genproto v0.14.0 h1:yDqD260mICkjodXyAaDhESfrLr6gIGwwRc9MYE0jvW0= -github.com/yandex-cloud/go-genproto v0.14.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= -github.com/yandex-cloud/go-sdk/services/dns v0.0.3 h1:erphTBXKSpm/tETa6FXrw4niSHjhySzAKHUc2/BZKx0= -github.com/yandex-cloud/go-sdk/services/dns v0.0.3/go.mod h1:lbBaFJVouETfVnd3YzNF5vW6vgYR2FVfGLUzLexyGlI= -github.com/yandex-cloud/go-sdk/v2 v2.0.8 h1:wQNIzEZYnClSQyo2fjEgnGEErWjJNBpSAinaKcP+VSg= -github.com/yandex-cloud/go-sdk/v2 v2.0.8/go.mod h1:9Gqpq7d0EUAS+H2OunILtMi3hmMPav+fYoy9rmydM4s= +github.com/yandex-cloud/go-genproto v0.23.0 h1:7nnb/o//eK4ZDd3wCuhWn2DoY1U2+dcUTJosoY49h0M= +github.com/yandex-cloud/go-genproto v0.23.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-sdk/services/dns v0.0.12 h1:c5TNaX7r3DqY37YJFbr7HyQFRcSe1WzCbR81LVwxXyk= +github.com/yandex-cloud/go-sdk/services/dns v0.0.12/go.mod h1:6CRtIkxq6iTSZIOT42EFns54CEr35ncECy4ix9lXUd4= +github.com/yandex-cloud/go-sdk/v2 v2.11.0 h1:K3Z0IyKkhEnvcDa0NUKm9ArB0yDL7Nr1rgxTz8W2VH0= +github.com/yandex-cloud/go-sdk/v2 v2.11.0/go.mod h1:aiib58mIEY7YiKbpViYZJtBvMjcskAvZ5w/TFshPRCg= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= @@ -957,16 +962,16 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -1019,8 +1024,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1064,8 +1069,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= -golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1123,8 +1128,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1132,8 +1137,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= +golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1150,8 +1155,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1218,9 +1223,11 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1235,8 +1242,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1251,8 +1258,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1271,16 +1278,16 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= +golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1338,14 +1345,18 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= -golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -1364,8 +1375,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.242.0 h1:7Lnb1nfnpvbkCiZek6IXKdJ0MFuAZNAJKQfA1ws62xg= -google.golang.org/api v0.242.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= +google.golang.org/api v0.249.0 h1:0VrsWAKzIZi058aeq+I86uIXbNhm9GxSHpbmZ92a38w= +google.golang.org/api v0.249.0/go.mod h1:dGk9qyI0UYPwO/cjt2q06LG/EhUpwZGdAbYF14wHHrQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1404,12 +1415,12 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= -google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1427,8 +1438,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1443,8 +1454,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1461,8 +1472,8 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/ns1/ns1-go.v2 v2.14.4 h1:77eP71rZ24I+9k1gITgjJXRyJzzmflA9oPUkYPB/wyc= -gopkg.in/ns1/ns1-go.v2 v2.14.4/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= +gopkg.in/ns1/ns1-go.v2 v2.15.0 h1:cE3xSMdSCV8kf9SQldzqgW/Ueh7sv3yO2JwKtYxxz3E= +gopkg.in/ns1/ns1-go.v2 v2.15.0/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -1493,5 +1504,5 @@ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -software.sslmate.com/src/go-pkcs12 v0.5.0 h1:EC6R394xgENTpZ4RltKydeDUjtlM5drOYIG9c6TVj2M= -software.sslmate.com/src/go-pkcs12 v0.5.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= +software.sslmate.com/src/go-pkcs12 v0.6.0 h1:f3sQittAeF+pao32Vb+mkli+ZyT+VwKaD014qFGq6oU= +software.sslmate.com/src/go-pkcs12 v0.6.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/providers/dns/namesilo/namesilo.go b/providers/dns/namesilo/namesilo.go index f76c8549e..8b12821e6 100644 --- a/providers/dns/namesilo/namesilo.go +++ b/providers/dns/namesilo/namesilo.go @@ -2,6 +2,7 @@ package namesilo import ( + "context" "errors" "fmt" "time" @@ -79,12 +80,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("namesilo: TTL should be in [%d, %d]", defaultTTL, maxTTL) } - transport, err := namesilo.NewTokenTransport(config.APIKey) - if err != nil { - return nil, fmt.Errorf("namesilo: %w", err) + if config.APIKey == "" { + return nil, errors.New("namesilo: credentials missing") } - return &DNSProvider{client: namesilo.NewClient(transport.Client()), config: config}, nil + return &DNSProvider{client: namesilo.NewClient(config.APIKey), config: config}, nil } // Present creates a TXT record to fulfill the dns-01 challenge. @@ -103,7 +103,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("namesilo: %w", err) } - _, err = d.client.DnsAddRecord(&namesilo.DnsAddRecordParams{ + _, err = d.client.DnsAddRecord(context.Background(), &namesilo.DnsAddRecordParams{ Domain: zoneName, Type: "TXT", Host: subdomain, @@ -118,6 +118,8 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) @@ -127,7 +129,7 @@ func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { zoneName := dns01.UnFqdn(zone) - resp, err := d.client.DnsListRecords(&namesilo.DnsListRecordsParams{Domain: zoneName}) + resp, err := d.client.DnsListRecords(ctx, &namesilo.DnsListRecordsParams{Domain: zoneName}) if err != nil { return fmt.Errorf("namesilo: %w", err) } @@ -139,7 +141,7 @@ func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { for _, r := range resp.Reply.ResourceRecord { if r.Type == "TXT" && r.Value == info.Value && (r.Host == subdomain || r.Host == dns01.UnFqdn(info.EffectiveFQDN)) { - _, err := d.client.DnsDeleteRecord(&namesilo.DnsDeleteRecordParams{Domain: zoneName, ID: r.RecordID}) + _, err := d.client.DnsDeleteRecord(ctx, &namesilo.DnsDeleteRecordParams{Domain: zoneName, ID: r.RecordID}) if err != nil { return fmt.Errorf("namesilo: %w", err) } diff --git a/providers/dns/namesilo/namesilo_test.go b/providers/dns/namesilo/namesilo_test.go index 4b01d7388..e3ef956bf 100644 --- a/providers/dns/namesilo/namesilo_test.go +++ b/providers/dns/namesilo/namesilo_test.go @@ -77,7 +77,7 @@ func TestNewDNSProviderConfig(t *testing.T) { { desc: "missing API key", ttl: defaultTTL, - expected: "namesilo: credentials missing: API key", + expected: "namesilo: credentials missing", }, { desc: "unavailable TTL", From f4bd48e672ce8a2366642a905d830a996e190787 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Fri, 12 Sep 2025 12:17:23 +0200 Subject: [PATCH 163/298] servercow: updated API documentation link (#2643) --- docs/content/dns/zz_gen_servercow.md | 2 +- providers/dns/servercow/servercow.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/dns/zz_gen_servercow.md b/docs/content/dns/zz_gen_servercow.md index 3214b0ca5..3851325d1 100644 --- a/docs/content/dns/zz_gen_servercow.md +++ b/docs/content/dns/zz_gen_servercow.md @@ -62,7 +62,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). ## More information -- [API documentation](https://cp.servercow.de/client/plugin/support_manager/knowledgebase/view/34/dns-api-v1/7/) +- [API documentation](https://wiki.servercow.de/en/domains/dns_api/api-syntax/) diff --git a/providers/dns/servercow/servercow.toml b/providers/dns/servercow/servercow.toml index 655257b84..de9727163 100644 --- a/providers/dns/servercow/servercow.toml +++ b/providers/dns/servercow/servercow.toml @@ -21,4 +21,4 @@ lego --email you@example.com --dns servercow -d '*.example.com' -d example.com r SERVERCOW_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] - API = "https://cp.servercow.de/client/plugin/support_manager/knowledgebase/view/34/dns-api-v1/7/" + API = "https://wiki.servercow.de/en/domains/dns_api/api-syntax/" From bfe7df489ba5b0c054bc19af30ed22f3022e6a65 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 13 Sep 2025 13:06:59 +0200 Subject: [PATCH 164/298] chore: update dependencies (#2644) --- go.mod | 8 ++++---- go.sum | 28 +++++++++++++++------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index dc847166a..8b13aa1ec 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.0 require ( cloud.google.com/go/compute/metadata v0.8.0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 @@ -16,7 +16,7 @@ require ( github.com/BurntSushi/toml v1.5.0 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 - github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.11 + github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.12 github.com/alibabacloud-go/tea v1.3.12 github.com/aliyun/credentials-go v1.4.7 github.com/aws/aws-sdk-go-v2 v1.39.0 @@ -76,7 +76,7 @@ require ( github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34 github.com/selectel/domains-go v1.1.0 github.com/selectel/go-selvpcclient/v4 v4.1.0 - github.com/softlayer/softlayer-go v1.1.7 + github.com/softlayer/softlayer-go v1.2.1 github.com/stretchr/testify v1.11.1 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.26 github.com/transip/gotransip/v6 v6.26.0 @@ -191,7 +191,7 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.7.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.7 // indirect github.com/spf13/viper v1.18.2 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect diff --git a/go.sum b/go.sum index 11371d2db..73c319861 100644 --- a/go.sum +++ b/go.sum @@ -42,8 +42,8 @@ github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYs github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 h1:ci6Yd6nysBRLEodoziB6ah1+YOzZbZk+NYneoA6q+6E= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 h1:MhRfI58HblXzCtWEZCO0feHs8LweePB3s90r7WaR1KU= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0/go.mod h1:okZ+ZURbArNdlJ+ptXoyHNuOETzOl1Oww19rm8I2WLA= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= @@ -123,8 +123,9 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= -github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.11 h1:5JIs4BPVpzbgcBrgmUxBdDsMjmsHZvPbTPD1m4aB+ZQ= github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.11/go.mod h1:ue0+WkdPxpCB2JP3iaG4Iawayxp72kyT5uDbozQKaW8= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.12 h1:e2yCrhtWd6Qcsy4he2OL+jIAU+93Lx9OcLlPRoFLT1w= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.12/go.mod h1:f2wDpbM7hK9SvLIH09zSKVU1TsyemUNOqErMscMMl7c= github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= @@ -356,8 +357,8 @@ github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaC github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= @@ -449,8 +450,8 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= @@ -722,16 +723,16 @@ github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo= -github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= +github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= +github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= -github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= +github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= @@ -843,8 +844,8 @@ github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA= github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= -github.com/softlayer/softlayer-go v1.1.7 h1:SgTL+pQZt1h+5QkAhVmHORM/7N9c1X0sljJhuOIHxWE= -github.com/softlayer/softlayer-go v1.1.7/go.mod h1:WeJrBLoTJcaT8nO1azeyHyNpo/fDLtbpbvh+pzts+Qw= +github.com/softlayer/softlayer-go v1.2.1 h1:8ucHxn5laVsVPb0/aMGnr6tOMt1I9BgEtU5mn70OGKw= +github.com/softlayer/softlayer-go v1.2.1/go.mod h1:Gz9/ktcmB7Z8EJlu+QEJJpkv8lAmnhYdB9Tc6gedjmo= github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ= github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -866,8 +867,9 @@ github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJ github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= From e76933536e7872b42afb0c51f8533b68045df25e Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 13 Sep 2025 13:07:40 +0200 Subject: [PATCH 165/298] Add DNS provider for KeyHelp (#2642) --- README.md | 43 ++-- cmd/zz_gen_cmd_dnshelp.go | 22 ++ docs/content/dns/zz_gen_keyhelp.md | 69 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/keyhelp/internal/client.go | 174 ++++++++++++++ providers/dns/keyhelp/internal/client_test.go | 169 ++++++++++++++ .../dns/keyhelp/internal/fixtures/error.json | 4 + .../internal/fixtures/get_domain_records.json | 24 ++ .../fixtures/get_domain_records2.json | 30 +++ .../internal/fixtures/get_domains.json | 41 ++++ .../update_domain_records-request.json | 28 +++ .../update_domain_records-request2.json | 22 ++ .../fixtures/update_domain_records.json | 3 + providers/dns/keyhelp/internal/types.go | 63 +++++ providers/dns/keyhelp/keyhelp.go | 220 ++++++++++++++++++ providers/dns/keyhelp/keyhelp.toml | 24 ++ providers/dns/keyhelp/keyhelp_test.go | 195 ++++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 18 files changed, 1116 insertions(+), 20 deletions(-) create mode 100644 docs/content/dns/zz_gen_keyhelp.md create mode 100644 providers/dns/keyhelp/internal/client.go create mode 100644 providers/dns/keyhelp/internal/client_test.go create mode 100644 providers/dns/keyhelp/internal/fixtures/error.json create mode 100644 providers/dns/keyhelp/internal/fixtures/get_domain_records.json create mode 100644 providers/dns/keyhelp/internal/fixtures/get_domain_records2.json create mode 100644 providers/dns/keyhelp/internal/fixtures/get_domains.json create mode 100644 providers/dns/keyhelp/internal/fixtures/update_domain_records-request.json create mode 100644 providers/dns/keyhelp/internal/fixtures/update_domain_records-request2.json create mode 100644 providers/dns/keyhelp/internal/fixtures/update_domain_records.json create mode 100644 providers/dns/keyhelp/internal/types.go create mode 100644 providers/dns/keyhelp/keyhelp.go create mode 100644 providers/dns/keyhelp/keyhelp.toml create mode 100644 providers/dns/keyhelp/keyhelp_test.go diff --git a/README.md b/README.md index 50da4d5d3..20348d87b 100644 --- a/README.md +++ b/README.md @@ -155,103 +155,108 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Joker Joohoi's ACME-DNS + KeyHelp Liara - Lima-City + Lima-City Linode (v4) Liquid Web Loopia - LuaDNS + LuaDNS Mail-in-a-Box ManageEngine CloudDNS Manual - Metaname + Metaname Metaregistrar mijn.host Mittwald - myaddr.{tools,dev,io} + myaddr.{tools,dev,io} MyDNS.jp MythicBeasts Name.com - Namecheap + Namecheap Namesilo NearlyFreeSpeech.NET Netcup - Netlify + Netlify Nicmanager NIFCloud Njalla - Nodion + Nodion NS1 Open Telekom Cloud Oracle Cloud - OVH + OVH plesk.com Porkbun PowerDNS - Rackspace + Rackspace Rain Yun/雨云 RcodeZero reg.ru - Regfish + Regfish RFC2136 RimuHosting RU CENTER - Sakura Cloud + Sakura Cloud Scaleway Selectel Selectel v2 - SelfHost.(de|eu) + SelfHost.(de|eu) Servercow Shellrent Simply.com - Sonic + Sonic Spaceship Stackpath Technitium - Tencent Cloud DNS + Tencent Cloud DNS Tencent EdgeOne Timeweb Cloud TransIP - UKFast SafeDNS + UKFast SafeDNS Ultradns Variomedia VegaDNS - Vercel + Vercel Versio.[nl|eu|uk] VinylDNS VK Cloud - Volcano Engine/火山引擎 + Volcano Engine/火山引擎 Vscale Vultr Webnames - Websupport + Websupport WEDOS West.cn/西部数码 Yandex 360 - Yandex Cloud + Yandex Cloud Yandex PDD Zone.ee ZoneEdit + Zonomi + + + diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 7c43bb2a7..55199386e 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -94,6 +94,7 @@ func allDNSCodes() string { "ipv64", "iwantmyname", "joker", + "keyhelp", "liara", "lightsail", "limacity", @@ -1921,6 +1922,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/joker`) + case "keyhelp": + // generated from: providers/dns/keyhelp/keyhelp.toml + ew.writeln(`Configuration for KeyHelp.`) + ew.writeln(`Code: 'keyhelp'`) + ew.writeln(`Since: 'v4.26.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "KEYHELP_API_KEY": API key`) + ew.writeln(` - "KEYHELP_BASE_URL": Server URL`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "KEYHELP_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "KEYHELP_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "KEYHELP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "KEYHELP_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/keyhelp`) + case "liara": // generated from: providers/dns/liara/liara.toml ew.writeln(`Configuration for Liara.`) diff --git a/docs/content/dns/zz_gen_keyhelp.md b/docs/content/dns/zz_gen_keyhelp.md new file mode 100644 index 000000000..2886a0a8e --- /dev/null +++ b/docs/content/dns/zz_gen_keyhelp.md @@ -0,0 +1,69 @@ +--- +title: "KeyHelp" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: keyhelp +dnsprovider: + since: "v4.26.0" + code: "keyhelp" + url: "https://www.keyweb.de/en/keyhelp/keyhelp/" +--- + + + + + + +Configuration for [KeyHelp](https://www.keyweb.de/en/keyhelp/keyhelp/). + + + + +- Code: `keyhelp` +- Since: v4.26.0 + + +Here is an example bash command using the KeyHelp provider: + +```bash +KEYHELP_BASE_URL="https://keyhelp.example.com" \ +KEYHELP_API_KEY="xxx" \ +lego --email you@example.com --dns keyhelp -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `KEYHELP_API_KEY` | API key | +| `KEYHELP_BASE_URL` | Server URL | + +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 | +|--------------------------------|-------------| +| `KEYHELP_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `KEYHELP_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `KEYHELP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `KEYHELP_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://app.swaggerhub.com/apis-docs/keyhelp/api/) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index ea5b037fe..6e788392c 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, 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, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, 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, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/keyhelp/internal/client.go b/providers/dns/keyhelp/internal/client.go new file mode 100644 index 000000000..3c731fc49 --- /dev/null +++ b/providers/dns/keyhelp/internal/client.go @@ -0,0 +1,174 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +// APIKeyHeader API key header. +const APIKeyHeader = "X-Api-Key" + +// Client the KeyHelp API client. +type Client struct { + apiKey string + + baseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(baseURL, apiKey string) (*Client, error) { + if baseURL == "" { + return nil, errors.New("missing base URL") + } + + if apiKey == "" { + return nil, errors.New("credentials missing") + } + + base, err := url.Parse(baseURL) + if err != nil { + return nil, fmt.Errorf("parse base URL: %w", err) + } + + return &Client{ + apiKey: apiKey, + baseURL: base.JoinPath("api", "v2"), + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) do(req *http.Request, result any) error { + req.Header.Set(APIKeyHeader, c.apiKey) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) { + endpoint := c.baseURL.JoinPath("domains") + + query := endpoint.Query() + query.Set("sort", "domain_utf8") + endpoint.RawQuery = query.Encode() + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var result []Domain + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result, nil +} + +func (c *Client) ListDomainRecords(ctx context.Context, domainID int) (*DomainRecords, error) { + endpoint := c.baseURL.JoinPath("dns", strconv.Itoa(domainID)) + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var result DomainRecords + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return &result, nil +} + +func (c *Client) UpdateDomainRecords(ctx context.Context, domainID int, records DomainRecords) (*DomainID, error) { + endpoint := c.baseURL.JoinPath("dns", strconv.Itoa(domainID)) + + req, err := newJSONRequest(ctx, http.MethodPut, endpoint, records) + if err != nil { + return nil, err + } + + var result DomainID + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return &result, nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/keyhelp/internal/client_test.go b/providers/dns/keyhelp/internal/client_test.go new file mode 100644 index 000000000..80b21495b --- /dev/null +++ b/providers/dns/keyhelp/internal/client_test.go @@ -0,0 +1,169 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(server.URL, "secret") + if err != nil { + return nil, err + } + + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + With(APIKeyHeader, "secret"). + WithJSONHeaders(), + ) +} + +func TestClient_ListDomains(t *testing.T) { + client := mockBuilder(). + Route("GET /api/v2/domains", + servermock.ResponseFromFixture("get_domains.json"), + servermock.CheckQueryParameter(). + With("sort", "domain_utf8"). + Strict()). + Build(t) + + domains, err := client.ListDomains(t.Context()) + require.NoError(t, err) + + expected := []Domain{{ + ID: 8, + UserID: 4, + ParentDomainID: 0, + Status: 1, + Domain: "example.com", + DomainUTF8: "example.com", + IsEmailDomain: true, + }} + + assert.Equal(t, expected, domains) +} + +func TestClient_ListDomains_error(t *testing.T) { + client := mockBuilder(). + Route("GET /api/v2/domains", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + _, err := client.ListDomains(t.Context()) + + require.EqualError(t, err, "401 Unauthorized: API key is missing or invalid.") +} + +func TestClient_ListDomainRecords(t *testing.T) { + client := mockBuilder(). + Route("GET /api/v2/dns/123", + servermock.ResponseFromFixture("get_domain_records.json")). + Build(t) + + domainRecords, err := client.ListDomainRecords(t.Context(), 123) + require.NoError(t, err) + + expected := &DomainRecords{ + DkimRecord: `default._domainkey IN TXT ( "v=DKIM1; k=rsa; s=email; " "...DKIM KEY..." )`, + Records: &Records{ + Soa: &SOARecord{ + TTL: 86400, + PrimaryNs: "ns.example.com.", + RName: "root.example.com.", + Refresh: 14400, + Retry: 1800, + Expire: 604800, + Minimum: 3600, + }, + Other: []Record{{ + Host: "@", + TTL: 86400, + Type: "A", + Value: "192.168.178.1", + }}, + }, + } + + assert.Equal(t, expected, domainRecords) +} + +func TestClient_ListDomainRecords_error(t *testing.T) { + client := mockBuilder(). + Route("GET /api/v2/dns/8", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + _, err := client.ListDomainRecords(t.Context(), 8) + + require.EqualError(t, err, "401 Unauthorized: API key is missing or invalid.") +} + +func TestClient_UpdateDomainRecords(t *testing.T) { + client := mockBuilder(). + Route("PUT /api/v2/dns/8", + servermock.ResponseFromFixture("update_domain_records.json"), + servermock.CheckRequestJSONBodyFromFixture("update_domain_records-request.json")). + Build(t) + + records := DomainRecords{ + DkimRecord: `default._domainkey IN TXT ( "v=DKIM1; k=rsa; s=email; " "...DKIM KEY..." )`, + Records: &Records{ + Soa: &SOARecord{ + TTL: 86400, + PrimaryNs: "ns.example.com.", + RName: "root.example.com.", + Refresh: 14400, + Retry: 1800, + Expire: 604800, + Minimum: 3600, + }, + Other: []Record{ + { + Host: "@", + TTL: 86400, + Type: "A", + Value: "192.168.178.1", + }, + { + Host: "_acme-challenge", + TTL: 120, + Type: "TXT", + Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + }, + }, + }, + } + + domainID, err := client.UpdateDomainRecords(t.Context(), 8, records) + require.NoError(t, err) + + expected := &DomainID{ID: 8} + + assert.Equal(t, expected, domainID) +} + +func TestClient_UpdateDomainRecords_error(t *testing.T) { + client := mockBuilder(). + Route("PUT /api/v2/dns/123", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + records := DomainRecords{} + + _, err := client.UpdateDomainRecords(t.Context(), 123, records) + + require.EqualError(t, err, "401 Unauthorized: API key is missing or invalid.") +} diff --git a/providers/dns/keyhelp/internal/fixtures/error.json b/providers/dns/keyhelp/internal/fixtures/error.json new file mode 100644 index 000000000..4fdf5e8f5 --- /dev/null +++ b/providers/dns/keyhelp/internal/fixtures/error.json @@ -0,0 +1,4 @@ +{ + "code": "401 Unauthorized", + "message": "API key is missing or invalid." +} diff --git a/providers/dns/keyhelp/internal/fixtures/get_domain_records.json b/providers/dns/keyhelp/internal/fixtures/get_domain_records.json new file mode 100644 index 000000000..50483bb8e --- /dev/null +++ b/providers/dns/keyhelp/internal/fixtures/get_domain_records.json @@ -0,0 +1,24 @@ +{ + "is_custom_dns": false, + "is_dns_disabled": false, + "dkim_record": "default._domainkey IN TXT ( \"v=DKIM1; k=rsa; s=email; \" \"...DKIM KEY...\" )", + "records": { + "soa": { + "ttl": 86400, + "primary_ns": "ns.example.com.", + "rname": "root.example.com.", + "refresh": 14400, + "retry": 1800, + "expire": 604800, + "minimum": 3600 + }, + "other": [ + { + "host": "@", + "ttl": 86400, + "type": "A", + "value": "192.168.178.1" + } + ] + } +} diff --git a/providers/dns/keyhelp/internal/fixtures/get_domain_records2.json b/providers/dns/keyhelp/internal/fixtures/get_domain_records2.json new file mode 100644 index 000000000..cd49fd6d0 --- /dev/null +++ b/providers/dns/keyhelp/internal/fixtures/get_domain_records2.json @@ -0,0 +1,30 @@ +{ + "is_custom_dns": false, + "is_dns_disabled": false, + "dkim_record": "default._domainkey IN TXT ( \"v=DKIM1; k=rsa; s=email; \" \"...DKIM KEY...\" )", + "records": { + "soa": { + "ttl": 86400, + "primary_ns": "ns.example.com.", + "rname": "root.example.com.", + "refresh": 14400, + "retry": 1800, + "expire": 604800, + "minimum": 3600 + }, + "other": [ + { + "host": "@", + "ttl": 86400, + "type": "A", + "value": "192.168.178.1" + }, + { + "host": "_acme-challenge", + "ttl": 120, + "type": "TXT", + "value": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" + } + ] + } +} diff --git a/providers/dns/keyhelp/internal/fixtures/get_domains.json b/providers/dns/keyhelp/internal/fixtures/get_domains.json new file mode 100644 index 000000000..28ae0887d --- /dev/null +++ b/providers/dns/keyhelp/internal/fixtures/get_domains.json @@ -0,0 +1,41 @@ +[ + { + "id": 8, + "id_user": 4, + "id_parent_domain": 0, + "status": 1, + "domain": "example.com", + "domain_utf8": "example.com", + "created_at": "2019-08-15T11:29:13+02:00", + "php_version": "", + "traffic": 32434624, + "is_disabled": false, + "delete_on": "2025-09-02T19:31:14+0000", + "dkim_selector": "default", + "dkim_record": "default._domainkey IN TXT ( \"v=DKIM1; k=rsa; s=email; \" \"...DKIM KEY...\" )", + "is_custom_dns": false, + "is_dns_disabled": false, + "is_subdomain": false, + "is_system_domain": false, + "is_email_domain": true, + "is_email_sending_only": false, + "target": { + "target": "https://www.keyhelp.de", + "is_forwarding": true, + "forwarding_type": 301 + }, + "security": { + "id_certificate": 0, + "lets_encrypt": true, + "is_prefer_https": true, + "is_hsts": true, + "hsts_max_age": 10368000, + "hsts_include": true, + "hsts_preload": true + }, + "apache": { + "http_directives": "# My custom HTTP directives", + "https_directives": "# My custom HTTPS directives" + } + } +] diff --git a/providers/dns/keyhelp/internal/fixtures/update_domain_records-request.json b/providers/dns/keyhelp/internal/fixtures/update_domain_records-request.json new file mode 100644 index 000000000..6f83ead11 --- /dev/null +++ b/providers/dns/keyhelp/internal/fixtures/update_domain_records-request.json @@ -0,0 +1,28 @@ +{ + "dkim_record": "default._domainkey IN TXT ( \"v=DKIM1; k=rsa; s=email; \" \"...DKIM KEY...\" )", + "records": { + "soa": { + "ttl": 86400, + "primary_ns": "ns.example.com.", + "rname": "root.example.com.", + "refresh": 14400, + "retry": 1800, + "expire": 604800, + "minimum": 3600 + }, + "other": [ + { + "host": "@", + "ttl": 86400, + "type": "A", + "value": "192.168.178.1" + }, + { + "host": "_acme-challenge", + "ttl": 120, + "type": "TXT", + "value": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" + } + ] + } +} diff --git a/providers/dns/keyhelp/internal/fixtures/update_domain_records-request2.json b/providers/dns/keyhelp/internal/fixtures/update_domain_records-request2.json new file mode 100644 index 000000000..3ebb2ee7a --- /dev/null +++ b/providers/dns/keyhelp/internal/fixtures/update_domain_records-request2.json @@ -0,0 +1,22 @@ +{ + "dkim_record": "default._domainkey IN TXT ( \"v=DKIM1; k=rsa; s=email; \" \"...DKIM KEY...\" )", + "records": { + "soa": { + "ttl": 86400, + "primary_ns": "ns.example.com.", + "rname": "root.example.com.", + "refresh": 14400, + "retry": 1800, + "expire": 604800, + "minimum": 3600 + }, + "other": [ + { + "host": "@", + "ttl": 86400, + "type": "A", + "value": "192.168.178.1" + } + ] + } +} diff --git a/providers/dns/keyhelp/internal/fixtures/update_domain_records.json b/providers/dns/keyhelp/internal/fixtures/update_domain_records.json new file mode 100644 index 000000000..a335b5ba5 --- /dev/null +++ b/providers/dns/keyhelp/internal/fixtures/update_domain_records.json @@ -0,0 +1,3 @@ +{ + "id": 8 +} diff --git a/providers/dns/keyhelp/internal/types.go b/providers/dns/keyhelp/internal/types.go new file mode 100644 index 000000000..8716fa0c8 --- /dev/null +++ b/providers/dns/keyhelp/internal/types.go @@ -0,0 +1,63 @@ +package internal + +import ( + "fmt" +) + +type APIError struct { + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} + +func (a *APIError) Error() string { + return fmt.Sprintf("%s: %s", a.Code, a.Message) +} + +type Domain struct { + ID int `json:"id,omitempty"` + UserID int `json:"id_user,omitempty"` + ParentDomainID int `json:"id_parent_domain,omitempty"` + Status int `json:"status,omitempty"` + Domain string `json:"domain,omitempty"` + DomainUTF8 string `json:"domain_utf8,omitempty"` + IsDisabled bool `json:"is_disabled,omitempty"` + IsCustomDNS bool `json:"is_custom_dns,omitempty"` + IsDNSDisabled bool `json:"is_dns_disabled,omitempty"` + IsSubdomain bool `json:"is_subdomain,omitempty"` + IsSystemDomain bool `json:"is_system_domain,omitempty"` + IsEmailDomain bool `json:"is_email_domain,omitempty"` + IsEmailSendingOnly bool `json:"is_email_sending_only,omitempty"` +} + +type DomainID struct { + ID int `json:"id,omitempty"` +} + +type DomainRecords struct { + IsCustomDNS bool `json:"is_custom_dns,omitempty"` + IsDNSDisabled bool `json:"is_dns_disabled,omitempty"` + DkimRecord string `json:"dkim_record,omitempty"` + Records *Records `json:"records,omitempty"` +} + +type Records struct { + Soa *SOARecord `json:"soa,omitempty"` + Other []Record `json:"other,omitempty"` +} + +type SOARecord struct { + TTL int `json:"ttl,omitempty"` + PrimaryNs string `json:"primary_ns,omitempty"` + RName string `json:"rname,omitempty"` + Refresh int `json:"refresh,omitempty"` + Retry int `json:"retry,omitempty"` + Expire int `json:"expire,omitempty"` + Minimum int `json:"minimum,omitempty"` +} + +type Record struct { + Host string `json:"host"` + TTL int `json:"ttl"` + Type string `json:"type"` + Value string `json:"value"` +} diff --git a/providers/dns/keyhelp/keyhelp.go b/providers/dns/keyhelp/keyhelp.go new file mode 100644 index 000000000..dfe3af556 --- /dev/null +++ b/providers/dns/keyhelp/keyhelp.go @@ -0,0 +1,220 @@ +// Package keyhelp implements a DNS provider for solving the DNS-01 challenge using KeyHelp. +package keyhelp + +import ( + "context" + "errors" + "fmt" + "net/http" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/keyhelp/internal" +) + +// Environment variables names. +const ( + envNamespace = "KEYHELP_" + + EnvBaseURL = envNamespace + "BASE_URL" + 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 { + BaseURL string + 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 *internal.Client + + domainIDs map[string]int + domainIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for KeyHelp. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvBaseURL, EnvAPIKey) + if err != nil { + return nil, fmt.Errorf("keyhelp: %w", err) + } + + config := NewDefaultConfig() + config.BaseURL = values[EnvBaseURL] + config.APIKey = values[EnvAPIKey] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for KeyHelp. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("keyhelp: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.BaseURL, config.APIKey) + if err != nil { + return nil, fmt.Errorf("keyhelp: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + domainIDs: 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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("keyhelp: could not find zone for domain %q: %w", domain, err) + } + + ctx := context.Background() + + domainInfo, err := d.findDomain(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("keyhelp: %w", err) + } + + domainRecords, err := d.client.ListDomainRecords(ctx, domainInfo.ID) + if err != nil { + return fmt.Errorf("keyhelp: list domain records: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("keyhelp: %w", err) + } + + records := domainRecords.Records.Other + records = append(records, internal.Record{ + Host: subDomain, + TTL: d.config.TTL, + Type: "TXT", + Value: info.Value, + }) + + req := internal.DomainRecords{ + DkimRecord: domainRecords.DkimRecord, + Records: &internal.Records{ + Soa: domainRecords.Records.Soa, + Other: records, + }, + } + + _, err = d.client.UpdateDomainRecords(ctx, domainInfo.ID, req) + if err != nil { + return fmt.Errorf("keyhelp: update domain records (add): %w", err) + } + + d.domainIDsMu.Lock() + d.domainIDs[token] = domainInfo.ID + d.domainIDsMu.Unlock() + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + // get the domain's unique ID from when we created it + d.domainIDsMu.Lock() + domainID, ok := d.domainIDs[token] + d.domainIDsMu.Unlock() + if !ok { + return fmt.Errorf("keyhelp: unknown record ID for '%s'", info.EffectiveFQDN) + } + + domainRecords, err := d.client.ListDomainRecords(ctx, domainID) + if err != nil { + return fmt.Errorf("keyhelp: list domain records: %w", err) + } + + var records []internal.Record + for _, record := range domainRecords.Records.Other { + if record.Type == "TXT" && record.Value == info.Value { + continue + } + + records = append(records, record) + } + + req := internal.DomainRecords{ + DkimRecord: domainRecords.DkimRecord, + Records: &internal.Records{ + Soa: domainRecords.Records.Soa, + Other: records, + }, + } + + _, err = d.client.UpdateDomainRecords(ctx, domainID, req) + if err != nil { + return fmt.Errorf("keyhelp: update domain records (delete): %w", err) + } + + // Delete domain ID from map + d.domainIDsMu.Lock() + delete(d.domainIDs, token) + d.domainIDsMu.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 +} + +func (d *DNSProvider) findDomain(ctx context.Context, zone string) (internal.Domain, error) { + domains, err := d.client.ListDomains(ctx) + if err != nil { + return internal.Domain{}, fmt.Errorf("list domains: %w", err) + } + + for _, domain := range domains { + if domain.DomainUTF8 == zone || domain.Domain == zone { + return domain, nil + } + } + + return internal.Domain{}, fmt.Errorf("domain not found: %s", zone) +} diff --git a/providers/dns/keyhelp/keyhelp.toml b/providers/dns/keyhelp/keyhelp.toml new file mode 100644 index 000000000..d6f84e34e --- /dev/null +++ b/providers/dns/keyhelp/keyhelp.toml @@ -0,0 +1,24 @@ +Name = "KeyHelp" +Description = '''''' +URL = "https://www.keyweb.de/en/keyhelp/keyhelp/" +Code = "keyhelp" +Since = "v4.26.0" + +Example = ''' +KEYHELP_BASE_URL="https://keyhelp.example.com" \ +KEYHELP_API_KEY="xxx" \ +lego --email you@example.com --dns keyhelp -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + KEYHELP_BASE_URL= "Server URL" + KEYHELP_API_KEY = "API key" + [Configuration.Additional] + KEYHELP_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + KEYHELP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + KEYHELP_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + KEYHELP_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://app.swaggerhub.com/apis-docs/keyhelp/api/" diff --git a/providers/dns/keyhelp/keyhelp_test.go b/providers/dns/keyhelp/keyhelp_test.go new file mode 100644 index 000000000..bdcf26ad4 --- /dev/null +++ b/providers/dns/keyhelp/keyhelp_test.go @@ -0,0 +1,195 @@ +package keyhelp + +import ( + "net/http/httptest" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/go-acme/lego/v4/providers/dns/keyhelp/internal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvBaseURL, EnvAPIKey). + WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvBaseURL: "https://keyhelp.example.com", + EnvAPIKey: "secret", + }, + }, + { + desc: "missing base URL", + envVars: map[string]string{ + EnvAPIKey: "secret", + }, + expected: "keyhelp: some credentials information are missing: KEYHELP_BASE_URL", + }, + { + desc: "missing API key", + envVars: map[string]string{ + EnvBaseURL: "https://keyhelp.example.com", + }, + expected: "keyhelp: some credentials information are missing: KEYHELP_API_KEY", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "keyhelp: some credentials information are missing: KEYHELP_BASE_URL,KEYHELP_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 + baseURL string + apiKey string + expected string + }{ + { + desc: "success", + baseURL: "https://keyhelp.example.com", + apiKey: "secret", + }, + { + desc: "missing base URL", + apiKey: "secret", + expected: "keyhelp: missing base URL", + }, + { + desc: "missing API key", + baseURL: "https://keyhelp.example.com", + expected: "keyhelp: credentials missing", + }, + { + desc: "missing credentials", + expected: "keyhelp: missing base URL", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.BaseURL = test.baseURL + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.HTTPClient = server.Client() + config.APIKey = "secret" + config.BaseURL = server.URL + + return NewDNSProviderConfig(config) + }, + servermock.CheckHeader(). + With(internal.APIKeyHeader, "secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("GET /api/v2/domains", + servermock.ResponseFromInternal("get_domains.json"), + servermock.CheckQueryParameter(). + With("sort", "domain_utf8"). + Strict()). + Route("GET /api/v2/dns/8", + servermock.ResponseFromInternal("get_domain_records.json")). + Route("PUT /api/v2/dns/8", + servermock.ResponseFromInternal("update_domain_records.json"), + servermock.CheckRequestJSONBodyFromInternal("update_domain_records-request.json")). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) + + assert.Equal(t, 8, provider.domainIDs["abc"]) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("GET /api/v2/dns/8", + servermock.ResponseFromInternal("get_domain_records2.json")). + Route("PUT /api/v2/dns/8", + servermock.ResponseFromInternal("update_domain_records.json"), + servermock.CheckRequestJSONBodyFromInternal("update_domain_records-request2.json")). + Build(t) + + provider.domainIDs["abc"] = 8 + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 44ab9d493..04e7eb5e7 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -88,6 +88,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/ipv64" "github.com/go-acme/lego/v4/providers/dns/iwantmyname" "github.com/go-acme/lego/v4/providers/dns/joker" + "github.com/go-acme/lego/v4/providers/dns/keyhelp" "github.com/go-acme/lego/v4/providers/dns/liara" "github.com/go-acme/lego/v4/providers/dns/lightsail" "github.com/go-acme/lego/v4/providers/dns/limacity" @@ -335,6 +336,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return iwantmyname.NewDNSProvider() case "joker": return joker.NewDNSProvider() + case "keyhelp": + return keyhelp.NewDNSProvider() case "liara": return liara.NewDNSProvider() case "lightsail": From 42f057cc723ef14b96119e6579e56cdbe9a63521 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sat, 13 Sep 2025 13:16:13 +0200 Subject: [PATCH 166/298] Prepare release v4.26.0 --- CHANGELOG.md | 20 +++++++++++++++++++ acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eb967c56..d531b3034 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [v4.26.0](https://github.com/go-acme/lego/releases/tag/v4.26.0) (2025-09-13) + +### Added + +- **[dnsprovider]** Add DNS provider for KeyHelp +- **[dnsprovider]** Add DNS provider for Binary Lane +- **[dnsprovider]** Add DNS provider for Tencent EdgeOne +- **[dnsprovider]** azuredns: pipeline credential support +- **[dnsprovider]** oraclecloud: handle instance_principal authentication + +### Changed + +- **[dnsprovider]** oraclecloud: add env var aliases +- **[dnsprovider]** simply: update to API v2 +- **[lib,cli]** EAB: fallback to base64.URLEncoding + +### Fixed + +- **[dnsprovider]** selectelv2: add missing options + ## [v4.25.2](https://github.com/go-acme/lego/releases/tag/v4.25.2) (2025-08-06) ### Changed diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index d0994497d..d5add0f5f 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.25.2" + ourUserAgent = "xenolf-acme/4.26.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 3a89e1924..a43f4260c 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.25.2+dev-detach" +const defaultVersion = "v4.26.0+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index ce4242a51..98386776c 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.25.2" + ourUserAgent = "goacme-lego/4.26.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From 0d567188f6fc2f2dac2f707bba888629ff92c6d4 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sat, 13 Sep 2025 13:16:37 +0200 Subject: [PATCH 167/298] Detach v4.26.0 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index d5add0f5f..6cf5cb7df 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index a43f4260c..a97cd3c5c 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.26.0+dev-release" +const defaultVersion = "v4.26.0+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 98386776c..7cbe439ef 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From bf0e89cdd92fdef4f0f0e9fab15d7cc9205a7454 Mon Sep 17 00:00:00 2001 From: Nikita Shashkov <75970449+Tearix@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:39:50 +0300 Subject: [PATCH 168/298] otc: adds option to use private zone (#2649) Co-authored-by: Fernandez Ludovic --- cmd/zz_gen_cmd_dnshelp.go | 3 +- docs/content/dns/zz_gen_otc.md | 3 +- providers/dns/otc/internal/client.go | 9 +++-- providers/dns/otc/internal/client_test.go | 19 ++++++++- providers/dns/otc/otc.go | 21 ++++++---- providers/dns/otc/otc.toml | 3 +- providers/dns/otc/otc_test.go | 48 ++++++++++++++++++++--- 7 files changed, 85 insertions(+), 21 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 55199386e..5b0da36d4 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -2572,7 +2572,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Credentials:`) ew.writeln(` - "OTC_DOMAIN_NAME": Domain name`) - ew.writeln(` - "OTC_IDENTITY_ENDPOINT": Identity endpoint URL`) ew.writeln(` - "OTC_PASSWORD": Password`) ew.writeln(` - "OTC_PROJECT_NAME": Project name`) ew.writeln(` - "OTC_USER_NAME": User name`) @@ -2580,7 +2579,9 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "OTC_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "OTC_IDENTITY_ENDPOINT": Identity endpoint URL`) ew.writeln(` - "OTC_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "OTC_PRIVATE_ZONE": Set to true to use private zones only (default: use public zones only)`) ew.writeln(` - "OTC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln(` - "OTC_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) ew.writeln(` - "OTC_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) diff --git a/docs/content/dns/zz_gen_otc.md b/docs/content/dns/zz_gen_otc.md index fe92f3001..e85850829 100644 --- a/docs/content/dns/zz_gen_otc.md +++ b/docs/content/dns/zz_gen_otc.md @@ -35,7 +35,6 @@ _Please contribute by adding a CLI example._ | Environment Variable Name | Description | |-----------------------|-------------| | `OTC_DOMAIN_NAME` | Domain name | -| `OTC_IDENTITY_ENDPOINT` | Identity endpoint URL | | `OTC_PASSWORD` | Password | | `OTC_PROJECT_NAME` | Project name | | `OTC_USER_NAME` | User name | @@ -49,7 +48,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `OTC_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `OTC_IDENTITY_ENDPOINT` | Identity endpoint URL | | `OTC_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `OTC_PRIVATE_ZONE` | Set to true to use private zones only (default: use public zones only) | | `OTC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | | `OTC_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | | `OTC_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | diff --git a/providers/dns/otc/internal/client.go b/providers/dns/otc/internal/client.go index e3e225314..0bb4dd323 100644 --- a/providers/dns/otc/internal/client.go +++ b/providers/dns/otc/internal/client.go @@ -42,8 +42,8 @@ func NewClient(username, password, domainName, projectName string) *Client { } } -func (c *Client) GetZoneID(ctx context.Context, zone string) (string, error) { - zonesResp, err := c.getZones(ctx, zone) +func (c *Client) GetZoneID(ctx context.Context, zone string, privateZone bool) (string, error) { + zonesResp, err := c.getZones(ctx, zone, privateZone) if err != nil { return "", err } @@ -62,13 +62,16 @@ func (c *Client) GetZoneID(ctx context.Context, zone string) (string, error) { } // https://docs.otc.t-systems.com/domain-name-service/api-ref/apis/public_zone_management/querying_public_zones.html -func (c *Client) getZones(ctx context.Context, zone string) (*ZonesResponse, error) { +func (c *Client) getZones(ctx context.Context, zone string, privateZone bool) (*ZonesResponse, error) { c.muBaseURL.Lock() endpoint := c.baseURL.JoinPath("zones") c.muBaseURL.Unlock() query := endpoint.Query() query.Set("name", zone) + if privateZone { + query.Set("type", "private") + } endpoint.RawQuery = query.Encode() req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) diff --git a/providers/dns/otc/internal/client_test.go b/providers/dns/otc/internal/client_test.go index a69cd1d45..74b5bb3af 100644 --- a/providers/dns/otc/internal/client_test.go +++ b/providers/dns/otc/internal/client_test.go @@ -33,7 +33,22 @@ func TestClient_GetZoneID(t *testing.T) { With("name", "example.com.")). Build(t) - zoneID, err := client.GetZoneID(context.Background(), "example.com.") + zoneID, err := client.GetZoneID(context.Background(), "example.com.", false) + require.NoError(t, err) + + assert.Equal(t, "123123", zoneID) +} + +func TestClient_GetZoneID_private(t *testing.T) { + client := mockBuilder(). + Route("GET /zones", + servermock.ResponseFromFixture("zones_GET.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com."). + With("type", "private")). + Build(t) + + zoneID, err := client.GetZoneID(context.Background(), "example.com.", true) require.NoError(t, err) assert.Equal(t, "123123", zoneID) @@ -47,7 +62,7 @@ func TestClient_GetZoneID_error(t *testing.T) { With("name", "example.com.")). Build(t) - _, err := client.GetZoneID(context.Background(), "example.com.") + _, err := client.GetZoneID(context.Background(), "example.com.", false) require.EqualError(t, err, "zone example.com. not found") } diff --git a/providers/dns/otc/otc.go b/providers/dns/otc/otc.go index 3569e6343..a6374f822 100644 --- a/providers/dns/otc/otc.go +++ b/providers/dns/otc/otc.go @@ -23,6 +23,7 @@ const ( EnvPassword = envNamespace + "PASSWORD" EnvProjectName = envNamespace + "PROJECT_NAME" EnvIdentityEndpoint = envNamespace + "IDENTITY_ENDPOINT" + EnvPrivateZone = envNamespace + "PRIVATE_ZONE" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -40,11 +41,13 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - IdentityEndpoint string - DomainName string - ProjectName string - UserName string - Password string + DomainName string + ProjectName string + UserName string + Password string + IdentityEndpoint string + PrivateZone bool + PropagationTimeout time.Duration PollingInterval time.Duration SequenceInterval time.Duration @@ -65,10 +68,12 @@ func NewDefaultConfig() *Config { tr.DisableKeepAlives = true return &Config{ + PrivateZone: env.GetOrDefaultBool(EnvPrivateZone, false), + IdentityEndpoint: env.GetOrDefaultString(EnvIdentityEndpoint, defaultIdentityEndpoint), + TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), - IdentityEndpoint: env.GetOrDefaultString(EnvIdentityEndpoint, defaultIdentityEndpoint), SequenceInterval: env.GetOrDefaultSecond(EnvSequenceInterval, dns01.DefaultPropagationTimeout), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second), @@ -144,7 +149,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("otc: %w", err) } - zoneID, err := d.client.GetZoneID(ctx, authZone) + zoneID, err := d.client.GetZoneID(ctx, authZone, d.config.PrivateZone) if err != nil { return fmt.Errorf("otc: unable to get zone: %w", err) } @@ -181,7 +186,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("otc: %w", err) } - zoneID, err := d.client.GetZoneID(ctx, authZone) + zoneID, err := d.client.GetZoneID(ctx, authZone, d.config.PrivateZone) if err != nil { return fmt.Errorf("otc: %w", err) } diff --git a/providers/dns/otc/otc.toml b/providers/dns/otc/otc.toml index cb1910d26..4f2f4ac0e 100644 --- a/providers/dns/otc/otc.toml +++ b/providers/dns/otc/otc.toml @@ -12,8 +12,9 @@ Example = '''''' OTC_PASSWORD = "Password" OTC_PROJECT_NAME = "Project name" OTC_DOMAIN_NAME = "Domain name" - OTC_IDENTITY_ENDPOINT = "Identity endpoint URL" [Configuration.Additional] + OTC_IDENTITY_ENDPOINT = "Identity endpoint URL" + OTC_PRIVATE_ZONE = "Set to true to use private zones only (default: use public zones only)" OTC_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" OTC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" OTC_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" diff --git a/providers/dns/otc/otc_test.go b/providers/dns/otc/otc_test.go index b5cb3a9d4..0b59e02ac 100644 --- a/providers/dns/otc/otc_test.go +++ b/providers/dns/otc/otc_test.go @@ -18,6 +18,7 @@ var envTest = tester.NewEnvTest( EnvDomainName, EnvUserName, EnvPassword, + EnvPrivateZone, EnvProjectName, EnvIdentityEndpoint). WithDomain(envDomain) @@ -213,7 +214,7 @@ func TestLiveCleanUp(t *testing.T) { } func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). + provider := mockBuilder(false). Route("GET /v2/zones", servermock.ResponseFromInternal("zones_GET.json"), servermock.CheckQueryParameter().Strict(). @@ -227,8 +228,24 @@ func TestDNSProvider_Present(t *testing.T) { require.NoError(t, err) } +func TestDNSProvider_Present_private(t *testing.T) { + provider := mockBuilder(true). + Route("GET /v2/zones", + servermock.ResponseFromInternal("zones_GET.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com."). + With("type", "private")). + Route("POST /v2/zones/123123/recordsets", + servermock.Noop(), + servermock.CheckRequestJSONBodyFromInternal("zones-recordsets_POST-request.json")). + Build(t) + + err := provider.Present("example.com", "", "123d==") + require.NoError(t, err) +} + func TestDNSProvider_Present_emptyZone(t *testing.T) { - provider := mockBuilder(). + provider := mockBuilder(false). Route("GET /v2/zones", servermock.ResponseFromInternal("zones_GET_empty.json"), servermock.CheckQueryParameter().Strict(). @@ -240,7 +257,7 @@ func TestDNSProvider_Present_emptyZone(t *testing.T) { } func TestDNSProvider_Cleanup(t *testing.T) { - provider := mockBuilder(). + provider := mockBuilder(false). Route("GET /v2/zones", servermock.ResponseFromInternal("zones_GET.json"), servermock.CheckQueryParameter().Strict(). @@ -258,8 +275,28 @@ func TestDNSProvider_Cleanup(t *testing.T) { require.NoError(t, err) } +func TestDNSProvider_Cleanup_private(t *testing.T) { + provider := mockBuilder(true). + Route("GET /v2/zones", + servermock.ResponseFromInternal("zones_GET.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com."). + With("type", "private")). + Route("GET /v2/zones/123123/recordsets", + servermock.ResponseFromInternal("zones-recordsets_GET.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "_acme-challenge.example.com."). + With("type", "TXT")). + Route("DELETE /v2/zones/123123/recordsets/321321", + servermock.ResponseFromInternal("zones-recordsets_DELETE.json")). + Build(t) + + err := provider.CleanUp("example.com", "", "123d==") + require.NoError(t, err) +} + func TestDNSProvider_Cleanup_emptyRecordset(t *testing.T) { - provider := mockBuilder(). + provider := mockBuilder(false). Route("GET /v2/zones", servermock.ResponseFromInternal("zones_GET.json"), servermock.CheckQueryParameter().Strict(). @@ -275,7 +312,7 @@ func TestDNSProvider_Cleanup_emptyRecordset(t *testing.T) { require.EqualError(t, err, "otc: unable to get record _acme-challenge.example.com. for zone example.com: record not found") } -func mockBuilder() *servermock.Builder[*DNSProvider] { +func mockBuilder(private bool) *servermock.Builder[*DNSProvider] { return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() @@ -285,6 +322,7 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { config.DomainName = "example.com" config.ProjectName = "test" config.IdentityEndpoint = fmt.Sprintf("%s/v3/auth/token", server.URL) + config.PrivateZone = private return NewDNSProviderConfig(config) }, From f432d2141e1b61490550a36a37ca2ad028641f86 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 22 Sep 2025 17:56:19 +0200 Subject: [PATCH 169/298] Add DNS provider for Hostinger (#2651) --- README.md | 50 ++-- cmd/zz_gen_cmd_dnshelp.go | 21 ++ docs/content/dns/zz_gen_hostinger.md | 67 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/hostinger/hostinger.go | 213 ++++++++++++++++++ providers/dns/hostinger/hostinger.toml | 22 ++ providers/dns/hostinger/hostinger_test.go | 166 ++++++++++++++ providers/dns/hostinger/internal/client.go | 141 ++++++++++++ .../dns/hostinger/internal/client_test.go | 130 +++++++++++ .../internal/fixtures/error_401.json | 4 + .../internal/fixtures/error_422.json | 10 + .../internal/fixtures/get_dns_records.json | 24 ++ .../fixtures/get_dns_records_acme.json | 27 +++ .../fixtures/update_dns_records-request.json | 28 +++ .../internal/fixtures/update_dns_records.json | 3 + .../update_dns_records_base-request.json | 25 ++ providers/dns/hostinger/internal/types.go | 39 ++++ providers/dns/zz_gen_dns_providers.go | 3 + 18 files changed, 949 insertions(+), 26 deletions(-) create mode 100644 docs/content/dns/zz_gen_hostinger.md create mode 100644 providers/dns/hostinger/hostinger.go create mode 100644 providers/dns/hostinger/hostinger.toml create mode 100644 providers/dns/hostinger/hostinger_test.go create mode 100644 providers/dns/hostinger/internal/client.go create mode 100644 providers/dns/hostinger/internal/client_test.go create mode 100644 providers/dns/hostinger/internal/fixtures/error_401.json create mode 100644 providers/dns/hostinger/internal/fixtures/error_422.json create mode 100644 providers/dns/hostinger/internal/fixtures/get_dns_records.json create mode 100644 providers/dns/hostinger/internal/fixtures/get_dns_records_acme.json create mode 100644 providers/dns/hostinger/internal/fixtures/update_dns_records-request.json create mode 100644 providers/dns/hostinger/internal/fixtures/update_dns_records.json create mode 100644 providers/dns/hostinger/internal/fixtures/update_dns_records_base-request.json create mode 100644 providers/dns/hostinger/internal/types.go diff --git a/README.md b/README.md index 20348d87b..9c2e81cfc 100644 --- a/README.md +++ b/README.md @@ -133,130 +133,130 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Hetzner Hosting.de + Hostinger Hosttech HTTP request http.net - Huawei Cloud + Huawei Cloud Hurricane Electric DNS HyperOne IBM Cloud (SoftLayer) - IIJ DNS Platform Service + IIJ DNS Platform Service Infoblox Infomaniak Internet Initiative Japan - Internet.bs + Internet.bs INWX Ionos IPv64 - iwantmyname + iwantmyname Joker Joohoi's ACME-DNS KeyHelp - Liara + Liara Lima-City Linode (v4) Liquid Web - Loopia + Loopia LuaDNS Mail-in-a-Box ManageEngine CloudDNS - Manual + Manual Metaname Metaregistrar mijn.host - Mittwald + Mittwald myaddr.{tools,dev,io} MyDNS.jp MythicBeasts - Name.com + Name.com Namecheap Namesilo NearlyFreeSpeech.NET - Netcup + Netcup Netlify Nicmanager NIFCloud - Njalla + Njalla Nodion NS1 Open Telekom Cloud - Oracle Cloud + Oracle Cloud OVH plesk.com Porkbun - PowerDNS + PowerDNS Rackspace Rain Yun/雨云 RcodeZero - reg.ru + reg.ru Regfish RFC2136 RimuHosting - RU CENTER + RU CENTER Sakura Cloud Scaleway Selectel - Selectel v2 + Selectel v2 SelfHost.(de|eu) Servercow Shellrent - Simply.com + Simply.com Sonic Spaceship Stackpath - Technitium + Technitium Tencent Cloud DNS Tencent EdgeOne Timeweb Cloud - TransIP + TransIP UKFast SafeDNS Ultradns Variomedia - VegaDNS + VegaDNS Vercel Versio.[nl|eu|uk] VinylDNS - VK Cloud + VK Cloud Volcano Engine/火山引擎 Vscale Vultr - Webnames + Webnames Websupport WEDOS West.cn/西部数码 - Yandex 360 + Yandex 360 Yandex Cloud Yandex PDD Zone.ee - ZoneEdit + ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 5b0da36d4..573c355df 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -77,6 +77,7 @@ func allDNSCodes() string { "googledomains", "hetzner", "hostingde", + "hostinger", "hosttech", "httpnet", "httpreq", @@ -1559,6 +1560,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/hostingde`) + case "hostinger": + // generated from: providers/dns/hostinger/hostinger.toml + ew.writeln(`Configuration for Hostinger.`) + ew.writeln(`Code: 'hostinger'`) + ew.writeln(`Since: 'v4.27.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "HOSTINGER_API_TOKEN": API Token`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "HOSTINGER_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "HOSTINGER_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "HOSTINGER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "HOSTINGER_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/hostinger`) + case "hosttech": // generated from: providers/dns/hosttech/hosttech.toml ew.writeln(`Configuration for Hosttech.`) diff --git a/docs/content/dns/zz_gen_hostinger.md b/docs/content/dns/zz_gen_hostinger.md new file mode 100644 index 000000000..193455f63 --- /dev/null +++ b/docs/content/dns/zz_gen_hostinger.md @@ -0,0 +1,67 @@ +--- +title: "Hostinger" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: hostinger +dnsprovider: + since: "v4.27.0" + code: "hostinger" + url: "https://www.hostinger.com/" +--- + + + + + + +Configuration for [Hostinger](https://www.hostinger.com/). + + + + +- Code: `hostinger` +- Since: v4.27.0 + + +Here is an example bash command using the Hostinger provider: + +```bash +HOSTINGER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns hostinger -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `HOSTINGER_API_TOKEN` | API Token | + +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 | +|--------------------------------|-------------| +| `HOSTINGER_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `HOSTINGER_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `HOSTINGER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `HOSTINGER_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://developers.hostinger.com/#tag/dns-zone) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 6e788392c..c54206ffb 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, 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, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/hostinger/hostinger.go b/providers/dns/hostinger/hostinger.go new file mode 100644 index 000000000..34033b5df --- /dev/null +++ b/providers/dns/hostinger/hostinger.go @@ -0,0 +1,213 @@ +// Package hostinger implements a DNS provider for solving the DNS-01 challenge using Hostinger. +package hostinger + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/hostinger/internal" +) + +// Environment variables names. +const ( + envNamespace = "HOSTINGER_" + + EnvAPIToken = envNamespace + "API_TOKEN" + + 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 { + APIToken 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for Hostinger. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIToken) + if err != nil { + return nil, fmt.Errorf("hostinger: %w", err) + } + + config := NewDefaultConfig() + config.APIToken = values[EnvAPIToken] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Hostinger. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("hostinger: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.APIToken) + if err != nil { + return nil, fmt.Errorf("hostinger: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("hostinger: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("hostinger: %w", err) + } + + ctx := context.Background() + + recordSets, err := d.client.GetDNSRecords(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("hostinger: get DNS records: %w", err) + } + + var newRecordSet []internal.RecordSet + + var added bool + + for _, recordSet := range recordSets { + if recordSet.Name == subDomain && recordSet.Type == "TXT" { + recordSet.Records = append(recordSet.Records, internal.Record{Content: info.Value}) + } + + added = true + + newRecordSet = append(newRecordSet, recordSet) + } + + if !added { + newRecordSet = append(newRecordSet, internal.RecordSet{ + Name: subDomain, + Type: "TXT", + TTL: d.config.TTL, + Records: []internal.Record{ + {Content: info.Value}, + }, + }) + } + + request := internal.ZoneRequest{ + Overwrite: false, + Zone: newRecordSet, + } + + err = d.client.UpdateDNSRecords(ctx, dns01.UnFqdn(authZone), request) + if err != nil { + return fmt.Errorf("hostinger: update DNS records (add): %w", err) + } + + 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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("hostinger: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("hostinger: %w", err) + } + + ctx := context.Background() + + recordSets, err := d.client.GetDNSRecords(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("hostinger: get DNS records: %w", err) + } + + var changed bool + + var newRecordSet []internal.RecordSet + + for _, recordSet := range recordSets { + if recordSet.Name == subDomain && recordSet.Type == "TXT" { + var rs []internal.Record + + for _, record := range recordSet.Records { + if record.Content == info.Value { + changed = true + } else { + rs = append(rs, record) + } + } + + recordSet.Records = rs + } + + newRecordSet = append(newRecordSet, recordSet) + } + + if !changed { + return nil + } + + request := internal.ZoneRequest{ + Overwrite: false, + Zone: newRecordSet, + } + + err = d.client.UpdateDNSRecords(ctx, dns01.UnFqdn(authZone), request) + if err != nil { + return fmt.Errorf("hostinger: update DNS records (delete): %w", err) + } + + 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/hostinger/hostinger.toml b/providers/dns/hostinger/hostinger.toml new file mode 100644 index 000000000..f49e447ed --- /dev/null +++ b/providers/dns/hostinger/hostinger.toml @@ -0,0 +1,22 @@ +Name = "Hostinger" +Description = '''''' +URL = "https://www.hostinger.com/" +Code = "hostinger" +Since = "v4.27.0" + +Example = ''' +HOSTINGER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns hostinger -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + HOSTINGER_API_TOKEN = "API Token" + [Configuration.Additional] + HOSTINGER_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + HOSTINGER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + HOSTINGER_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + HOSTINGER_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://developers.hostinger.com/#tag/dns-zone" diff --git a/providers/dns/hostinger/hostinger_test.go b/providers/dns/hostinger/hostinger_test.go new file mode 100644 index 000000000..43285920b --- /dev/null +++ b/providers/dns/hostinger/hostinger_test.go @@ -0,0 +1,166 @@ +package hostinger + +import ( + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvAPIToken).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvAPIToken: "secret", + }, + }, + { + desc: "missing API token", + envVars: map[string]string{ + EnvAPIToken: "", + }, + expected: "hostinger: some credentials information are missing: HOSTINGER_API_TOKEN", + }, + } + + 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 + apiToken string + expected string + }{ + { + desc: "success", + apiToken: "secret", + }, + { + desc: "missing API token", + expected: "hostinger: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIToken = test.apiToken + + 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 mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.APIToken = "secret" + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.HTTPClient = server.Client() + p.client.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + WithAuthorization("Bearer secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("GET /api/dns/v1/zones/example.com", + servermock.ResponseFromInternal("get_dns_records.json")). + Route("PUT /api/dns/v1/zones/example.com", + servermock.ResponseFromInternal("update_dns_records.json"), + servermock.CheckRequestJSONBodyFromInternal("update_dns_records-request.json")). + Build(t) + + err := provider.Present("example.com", "", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("GET /api/dns/v1/zones/example.com", + servermock.ResponseFromInternal("get_dns_records_acme.json")). + Route("PUT /api/dns/v1/zones/example.com", + servermock.ResponseFromInternal("update_dns_records.json"), + servermock.CheckRequestJSONBodyFromInternal("update_dns_records_base-request.json")). + Build(t) + + err := provider.CleanUp("example.com", "", "123d==") + require.NoError(t, err) +} + +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/hostinger/internal/client.go b/providers/dns/hostinger/internal/client.go new file mode 100644 index 000000000..b4681bb2b --- /dev/null +++ b/providers/dns/hostinger/internal/client.go @@ -0,0 +1,141 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const defaultBaseURL = "https://developers.hostinger.com" + +const authorizationHeader = "Authorization" + +// Client the Hostinger API client. +type Client struct { + token string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(token string) (*Client, error) { + if token == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + token: token, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +// GetDNSRecords retrieves DNS zone records for a specific domain. +// https://developers.hostinger.com/#tag/dns-zone/get/api/dns/v1/zones/{domain} +func (c *Client) GetDNSRecords(ctx context.Context, domain string) ([]RecordSet, error) { + endpoint := c.BaseURL.JoinPath("/api/dns/v1/zones/", domain) + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var result []RecordSet + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result, nil +} + +// UpdateDNSRecords updates DNS records for the selected domain. +// https://developers.hostinger.com/#tag/dns-zone/put/api/dns/v1/zones/{domain} +func (c *Client) UpdateDNSRecords(ctx context.Context, domain string, zone ZoneRequest) error { + endpoint := c.BaseURL.JoinPath("/api/dns/v1/zones/", domain) + + req, err := newJSONRequest(ctx, http.MethodPut, endpoint, zone) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) do(req *http.Request, result any) error { + req.Header.Set(authorizationHeader, "Bearer "+c.token) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/hostinger/internal/client_test.go b/providers/dns/hostinger/internal/client_test.go new file mode 100644 index 000000000..a031b0b5c --- /dev/null +++ b/providers/dns/hostinger/internal/client_test.go @@ -0,0 +1,130 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("secret") + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + With("Authorization", "Bearer secret"), + ) +} + +func TestClient_GetDNSRecords(t *testing.T) { + client := mockBuilder(). + Route("GET /api/dns/v1/zones/example.com", + servermock.ResponseFromFixture("get_dns_records.json")). + Build(t) + + records, err := client.GetDNSRecords(t.Context(), "example.com") + require.NoError(t, err) + + expected := []RecordSet{ + { + Name: "_acme-challenge", + Records: []Record{{ + Content: "aaa", + }}, + TTL: 14400, + Type: "TXT", + }, + { + Name: "_acme-challenge", + Records: []Record{{ + Content: "example.com.", + }}, + TTL: 14400, + Type: "A", + }, + } + + assert.Equal(t, expected, records) +} + +func TestClient_GetDNSRecords_error(t *testing.T) { + client := mockBuilder(). + Route("GET /api/dns/v1/zones/example.com", + servermock.ResponseFromFixture("error_401.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + _, err := client.GetDNSRecords(t.Context(), "example.com") + + require.EqualError(t, err, "26a91bd9-f8c8-4a83-9df9-83e23d696fe3: Unauthenticated") +} + +func TestClient_UpdateDNSRecords(t *testing.T) { + client := mockBuilder(). + Route("PUT /api/dns/v1/zones/example.com", + servermock.ResponseFromFixture("update_dns_records.json"), + servermock.CheckRequestJSONBodyFromFixture("update_dns_records-request.json")). + Build(t) + + zone := ZoneRequest{ + Overwrite: false, + Zone: []RecordSet{ + { + Name: "_acme-challenge", + Records: []Record{ + {Content: "aaa"}, + {Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"}, + }, + TTL: 14400, + Type: "TXT", + }, + { + Name: "_acme-challenge", + Records: []Record{{ + Content: "example.com.", + }}, + TTL: 14400, + Type: "A", + }, + }, + } + + err := client.UpdateDNSRecords(t.Context(), "example.com", zone) + require.NoError(t, err) +} + +func TestClient_UpdateDNSRecords_error(t *testing.T) { + client := mockBuilder(). + Route("PUT /api/dns/v1/zones/example.com", + servermock.ResponseFromFixture("error_422.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) + + zone := ZoneRequest{ + Zone: []RecordSet{{ + Name: "_acme-challenge", + Records: []Record{{ + Content: "aaa", + }}, + TTL: 14400, + Type: "TXT", + }}, + } + + err := client.UpdateDNSRecords(t.Context(), "example.com", zone) + + require.EqualError(t, err, "26a91bd9-f8c8-4a83-9df9-83e23d696fe3: The name field is required. (and 1 more error): field_1: The field_1 field is required., The field_1 must be a number.") +} diff --git a/providers/dns/hostinger/internal/fixtures/error_401.json b/providers/dns/hostinger/internal/fixtures/error_401.json new file mode 100644 index 000000000..1b7381ff6 --- /dev/null +++ b/providers/dns/hostinger/internal/fixtures/error_401.json @@ -0,0 +1,4 @@ +{ + "message": "Unauthenticated", + "correlation_id": "26a91bd9-f8c8-4a83-9df9-83e23d696fe3" +} diff --git a/providers/dns/hostinger/internal/fixtures/error_422.json b/providers/dns/hostinger/internal/fixtures/error_422.json new file mode 100644 index 000000000..6ec286823 --- /dev/null +++ b/providers/dns/hostinger/internal/fixtures/error_422.json @@ -0,0 +1,10 @@ +{ + "message": "The name field is required. (and 1 more error)", + "errors": { + "field_1": [ + "The field_1 field is required.", + "The field_1 must be a number." + ] + }, + "correlation_id": "26a91bd9-f8c8-4a83-9df9-83e23d696fe3" +} diff --git a/providers/dns/hostinger/internal/fixtures/get_dns_records.json b/providers/dns/hostinger/internal/fixtures/get_dns_records.json new file mode 100644 index 000000000..e51edd4dc --- /dev/null +++ b/providers/dns/hostinger/internal/fixtures/get_dns_records.json @@ -0,0 +1,24 @@ +[ + { + "name": "_acme-challenge", + "records": [ + { + "content": "aaa", + "is_disabled": false + } + ], + "ttl": 14400, + "type": "TXT" + }, + { + "name": "_acme-challenge", + "records": [ + { + "content": "example.com.", + "is_disabled": false + } + ], + "ttl": 14400, + "type": "A" + } +] diff --git a/providers/dns/hostinger/internal/fixtures/get_dns_records_acme.json b/providers/dns/hostinger/internal/fixtures/get_dns_records_acme.json new file mode 100644 index 000000000..99a574514 --- /dev/null +++ b/providers/dns/hostinger/internal/fixtures/get_dns_records_acme.json @@ -0,0 +1,27 @@ +[ + { + "name": "_acme-challenge", + "records": [ + { + "content": "aaa", + "is_disabled": false + }, + { + "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" + } + ], + "ttl": 14400, + "type": "TXT" + }, + { + "name": "_acme-challenge", + "records": [ + { + "content": "example.com.", + "is_disabled": false + } + ], + "ttl": 14400, + "type": "A" + } +] diff --git a/providers/dns/hostinger/internal/fixtures/update_dns_records-request.json b/providers/dns/hostinger/internal/fixtures/update_dns_records-request.json new file mode 100644 index 000000000..83d4ab6fb --- /dev/null +++ b/providers/dns/hostinger/internal/fixtures/update_dns_records-request.json @@ -0,0 +1,28 @@ +{ + "overwrite": false, + "zone": [ + { + "name": "_acme-challenge", + "records": [ + { + "content": "aaa" + }, + { + "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" + } + ], + "ttl": 14400, + "type": "TXT" + }, + { + "name": "_acme-challenge", + "records": [ + { + "content": "example.com." + } + ], + "ttl": 14400, + "type": "A" + } + ] +} diff --git a/providers/dns/hostinger/internal/fixtures/update_dns_records.json b/providers/dns/hostinger/internal/fixtures/update_dns_records.json new file mode 100644 index 000000000..11d2582b4 --- /dev/null +++ b/providers/dns/hostinger/internal/fixtures/update_dns_records.json @@ -0,0 +1,3 @@ +{ + "message": "Request accepted" +} diff --git a/providers/dns/hostinger/internal/fixtures/update_dns_records_base-request.json b/providers/dns/hostinger/internal/fixtures/update_dns_records_base-request.json new file mode 100644 index 000000000..a348db233 --- /dev/null +++ b/providers/dns/hostinger/internal/fixtures/update_dns_records_base-request.json @@ -0,0 +1,25 @@ +{ + "overwrite": false, + "zone": [ + { + "name": "_acme-challenge", + "records": [ + { + "content": "aaa" + } + ], + "ttl": 14400, + "type": "TXT" + }, + { + "name": "_acme-challenge", + "records": [ + { + "content": "example.com." + } + ], + "ttl": 14400, + "type": "A" + } + ] +} diff --git a/providers/dns/hostinger/internal/types.go b/providers/dns/hostinger/internal/types.go new file mode 100644 index 000000000..a79e9f5dc --- /dev/null +++ b/providers/dns/hostinger/internal/types.go @@ -0,0 +1,39 @@ +package internal + +import ( + "fmt" + "strings" +) + +type APIError struct { + Message string `json:"message,omitempty"` + Errors map[string][]string `json:"errors,omitempty"` + CorrelationID string `json:"correlation_id,omitempty"` +} + +func (a *APIError) Error() string { + msg := fmt.Sprintf("%s: %s", a.CorrelationID, a.Message) + + for field, values := range a.Errors { + msg += fmt.Sprintf(": %s: %s", field, strings.Join(values, ", ")) + } + + return msg +} + +type ZoneRequest struct { + Overwrite bool `json:"overwrite"` + Zone []RecordSet `json:"zone,omitempty"` +} + +type RecordSet struct { + Name string `json:"name,omitempty"` + Records []Record `json:"records,omitempty"` + TTL int `json:"ttl,omitempty"` + Type string `json:"type,omitempty"` +} + +type Record struct { + Content string `json:"content,omitempty"` + IsDisabled bool `json:"is_disabled,omitempty"` +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 04e7eb5e7..1e6586e89 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -71,6 +71,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/googledomains" "github.com/go-acme/lego/v4/providers/dns/hetzner" "github.com/go-acme/lego/v4/providers/dns/hostingde" + "github.com/go-acme/lego/v4/providers/dns/hostinger" "github.com/go-acme/lego/v4/providers/dns/hosttech" "github.com/go-acme/lego/v4/providers/dns/httpnet" "github.com/go-acme/lego/v4/providers/dns/httpreq" @@ -302,6 +303,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return hetzner.NewDNSProvider() case "hostingde": return hostingde.NewDNSProvider() + case "hostinger": + return hostinger.NewDNSProvider() case "hosttech": return hosttech.NewDNSProvider() case "httpnet": From bb33817a6130a4781b9512af09c953dd15095135 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 22 Sep 2025 21:32:25 +0200 Subject: [PATCH 170/298] chore: update linter (#2652) --- .github/workflows/pr.yml | 2 +- providers/dns/bindman/bindman_test.go | 1 - providers/dns/cloudflare/internal/client.go | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b68a7810b..6b9bce14e 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: stable - GOLANGCI_LINT_VERSION: v2.2.1 + GOLANGCI_LINT_VERSION: v2.5.0 HUGO_VERSION: 0.148.2 CGO_ENABLED: 0 LEGO_E2E_TESTS: CI diff --git a/providers/dns/bindman/bindman_test.go b/providers/dns/bindman/bindman_test.go index 1f339dae8..15db9c424 100644 --- a/providers/dns/bindman/bindman_test.go +++ b/providers/dns/bindman/bindman_test.go @@ -1,4 +1,3 @@ -// Package bindman implements a DNS provider for solving the DNS-01 challenge. package bindman import ( diff --git a/providers/dns/cloudflare/internal/client.go b/providers/dns/cloudflare/internal/client.go index 495ba5618..7ba4b06e0 100644 --- a/providers/dns/cloudflare/internal/client.go +++ b/providers/dns/cloudflare/internal/client.go @@ -84,7 +84,7 @@ func (c *Client) CreateDNSRecord(ctx context.Context, zoneID string, record Reco return &result.Result, nil } -// DeleteDNSRecord Delete DNS record. +// DeleteDNSRecord deletes DNS record. // https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/delete/ func (c *Client) DeleteDNSRecord(ctx context.Context, zoneID, recordID string) error { endpoint := c.baseURL.JoinPath("zones", zoneID, "dns_records", recordID) @@ -97,6 +97,7 @@ func (c *Client) DeleteDNSRecord(ctx context.Context, zoneID, recordID string) e return c.do(req, nil) } +// ZonesByName returns a list of zones matching the given name. // https://developers.cloudflare.com/api/resources/zones/methods/list/ func (c *Client) ZonesByName(ctx context.Context, name string) ([]Zone, error) { endpoint := c.baseURL.JoinPath("zones") From ba156d5344411a8226392c74f77325d1b7402d27 Mon Sep 17 00:00:00 2001 From: Philip Kannegaard Hayes Date: Mon, 22 Sep 2025 14:33:24 -0700 Subject: [PATCH 171/298] feat: support --private-key with a PKCS#8 keypair (#2653) --- cmd/accounts_storage.go | 15 ++++----------- cmd/certs_storage.go | 25 +++---------------------- 2 files changed, 7 insertions(+), 33 deletions(-) diff --git a/cmd/accounts_storage.go b/cmd/accounts_storage.go index b3e4986dd..c234c0060 100644 --- a/cmd/accounts_storage.go +++ b/cmd/accounts_storage.go @@ -2,10 +2,8 @@ package cmd import ( "crypto" - "crypto/x509" "encoding/json" "encoding/pem" - "errors" "net/url" "os" "path/filepath" @@ -209,16 +207,11 @@ func loadPrivateKey(file string) (crypto.PrivateKey, error) { return nil, err } - keyBlock, _ := pem.Decode(keyBytes) - - switch keyBlock.Type { - case "RSA PRIVATE KEY": - return x509.ParsePKCS1PrivateKey(keyBlock.Bytes) - case "EC PRIVATE KEY": - return x509.ParseECPrivateKey(keyBlock.Bytes) + privateKey, err := certcrypto.ParsePEMPrivateKey(keyBytes) + if err != nil { + return nil, err } - - return nil, errors.New("unknown private key type") + return privateKey, nil } func tryRecoverRegistration(ctx *cli.Context, privateKey crypto.PrivateKey) (*registration.Resource, error) { diff --git a/cmd/certs_storage.go b/cmd/certs_storage.go index f9bcdade8..e0b1a387a 100644 --- a/cmd/certs_storage.go +++ b/cmd/certs_storage.go @@ -2,7 +2,6 @@ package cmd import ( "bytes" - "crypto" "crypto/x509" "encoding/json" "encoding/pem" @@ -233,27 +232,9 @@ func (s *CertificatesStorage) WritePFXFile(domain string, certRes *certificate.R return fmt.Errorf("unable to get certificate chain for domain %s: %w", domain, err) } - keyPemBlock, _ := pem.Decode(certRes.PrivateKey) - if keyPemBlock == nil { - return fmt.Errorf("unable to parse PrivateKey for domain %s", domain) - } - - var privateKey crypto.Signer - var keyErr error - - switch keyPemBlock.Type { - case "RSA PRIVATE KEY": - privateKey, keyErr = x509.ParsePKCS1PrivateKey(keyPemBlock.Bytes) - if keyErr != nil { - return fmt.Errorf("unable to load RSA PrivateKey for domain %s: %w", domain, keyErr) - } - case "EC PRIVATE KEY": - privateKey, keyErr = x509.ParseECPrivateKey(keyPemBlock.Bytes) - if keyErr != nil { - return fmt.Errorf("unable to load EC PrivateKey for domain %s: %w", domain, keyErr) - } - default: - return fmt.Errorf("unsupported PrivateKey type '%s' for domain %s", keyPemBlock.Type, domain) + privateKey, err := certcrypto.ParsePEMPrivateKey(certRes.PrivateKey) + if err != nil { + return fmt.Errorf("unable to parse PrivateKey for domain %s: %w", domain, err) } encoder, err := getPFXEncoder(s.pfxFormat) From 95eb44ccbe70d75cea01fa7a3fcccf831d5d147a Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 23 Sep 2025 12:22:54 +0200 Subject: [PATCH 172/298] hostinger: fix Present (#2654) --- providers/dns/hostinger/hostinger.go | 3 +-- providers/dns/hostinger/hostinger_test.go | 26 +++++++++++++++++++ .../fixtures/get_dns_records_empty.json | 13 ++++++++++ .../update_dns_records_empty-request.json | 25 ++++++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 providers/dns/hostinger/internal/fixtures/get_dns_records_empty.json create mode 100644 providers/dns/hostinger/internal/fixtures/update_dns_records_empty-request.json diff --git a/providers/dns/hostinger/hostinger.go b/providers/dns/hostinger/hostinger.go index 34033b5df..14c8f1605 100644 --- a/providers/dns/hostinger/hostinger.go +++ b/providers/dns/hostinger/hostinger.go @@ -115,10 +115,9 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { for _, recordSet := range recordSets { if recordSet.Name == subDomain && recordSet.Type == "TXT" { recordSet.Records = append(recordSet.Records, internal.Record{Content: info.Value}) + added = true } - added = true - newRecordSet = append(newRecordSet, recordSet) } diff --git a/providers/dns/hostinger/hostinger_test.go b/providers/dns/hostinger/hostinger_test.go index 43285920b..305a7a99c 100644 --- a/providers/dns/hostinger/hostinger_test.go +++ b/providers/dns/hostinger/hostinger_test.go @@ -1,6 +1,7 @@ package hostinger import ( + "net/http" "net/http/httptest" "net/url" "testing" @@ -126,6 +127,19 @@ func TestDNSProvider_Present(t *testing.T) { require.NoError(t, err) } +func TestDNSProvider_Present_empty(t *testing.T) { + provider := mockBuilder(). + Route("GET /api/dns/v1/zones/example.com", + servermock.ResponseFromInternal("get_dns_records_empty.json")). + Route("PUT /api/dns/v1/zones/example.com", + servermock.ResponseFromInternal("update_dns_records.json"), + servermock.CheckRequestJSONBodyFromInternal("update_dns_records_empty-request.json")). + Build(t) + + err := provider.Present("example.com", "", "123d==") + require.NoError(t, err) +} + func TestDNSProvider_CleanUp(t *testing.T) { provider := mockBuilder(). Route("GET /api/dns/v1/zones/example.com", @@ -139,6 +153,18 @@ func TestDNSProvider_CleanUp(t *testing.T) { require.NoError(t, err) } +func TestDNSProvider_CleanUp_empty(t *testing.T) { + provider := mockBuilder(). + Route("GET /api/dns/v1/zones/example.com", + servermock.ResponseFromInternal("get_dns_records_empty.json")). + Route("PUT /api/dns/v1/zones/example.com", + servermock.Noop().WithStatusCode(http.StatusServiceUnavailable)). + Build(t) + + err := provider.CleanUp("example.com", "", "123d==") + require.NoError(t, err) +} + func TestLivePresent(t *testing.T) { if !envTest.IsLiveTest() { t.Skip("skipping live test") diff --git a/providers/dns/hostinger/internal/fixtures/get_dns_records_empty.json b/providers/dns/hostinger/internal/fixtures/get_dns_records_empty.json new file mode 100644 index 000000000..89a0ebf9d --- /dev/null +++ b/providers/dns/hostinger/internal/fixtures/get_dns_records_empty.json @@ -0,0 +1,13 @@ +[ + { + "name": "_acme-challenge", + "records": [ + { + "content": "example.com.", + "is_disabled": false + } + ], + "ttl": 14400, + "type": "A" + } +] diff --git a/providers/dns/hostinger/internal/fixtures/update_dns_records_empty-request.json b/providers/dns/hostinger/internal/fixtures/update_dns_records_empty-request.json new file mode 100644 index 000000000..81b0c3cf0 --- /dev/null +++ b/providers/dns/hostinger/internal/fixtures/update_dns_records_empty-request.json @@ -0,0 +1,25 @@ +{ + "overwrite": false, + "zone": [ + { + "name": "_acme-challenge", + "records": [ + { + "content": "example.com." + } + ], + "ttl": 14400, + "type": "A" + }, + { + "name": "_acme-challenge", + "records": [ + { + "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" + } + ], + "ttl": 120, + "type": "TXT" + } + ] +} From 26920e75f7fc5cc49a217fd58329847bed6d5788 Mon Sep 17 00:00:00 2001 From: bartjanssens92 Date: Tue, 23 Sep 2025 16:36:13 +0200 Subject: [PATCH 173/298] otc: add example (#2655) Co-authored-by: Fernandez Ludovic --- cmd/zz_gen_cmd_dnshelp.go | 2 +- docs/content/dns/zz_gen_otc.md | 14 ++++++++++---- providers/dns/otc/otc.toml | 10 ++++++++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 573c355df..bffb0696f 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -2600,7 +2600,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "OTC_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) - ew.writeln(` - "OTC_IDENTITY_ENDPOINT": Identity endpoint URL`) + ew.writeln(` - "OTC_IDENTITY_ENDPOINT": Identity endpoint URL (default: https://iam.eu-de.otc.t-systems.com:443/v3/auth/tokens)`) ew.writeln(` - "OTC_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "OTC_PRIVATE_ZONE": Set to true to use private zones only (default: use public zones only)`) ew.writeln(` - "OTC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) diff --git a/docs/content/dns/zz_gen_otc.md b/docs/content/dns/zz_gen_otc.md index e85850829..4f3679fa2 100644 --- a/docs/content/dns/zz_gen_otc.md +++ b/docs/content/dns/zz_gen_otc.md @@ -23,9 +23,15 @@ Configuration for [Open Telekom Cloud](https://cloud.telekom.de/en). - Since: v0.4.1 -{{% notice note %}} -_Please contribute by adding a CLI example._ -{{% /notice %}} +Here is an example bash command using the Open Telekom Cloud provider: + +```bash +OTC_DOMAIN_NAME=domain_name \ +OTC_USER_NAME=user_name \ +OTC_PASSWORD=password \ +OTC_PROJECT_NAME=project_name \ +lego --email you@example.com --dns otc -d '*.example.com' -d example.com run +``` @@ -48,7 +54,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `OTC_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | -| `OTC_IDENTITY_ENDPOINT` | Identity endpoint URL | +| `OTC_IDENTITY_ENDPOINT` | Identity endpoint URL (default: https://iam.eu-de.otc.t-systems.com:443/v3/auth/tokens) | | `OTC_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `OTC_PRIVATE_ZONE` | Set to true to use private zones only (default: use public zones only) | | `OTC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | diff --git a/providers/dns/otc/otc.toml b/providers/dns/otc/otc.toml index 4f2f4ac0e..91f9f5455 100644 --- a/providers/dns/otc/otc.toml +++ b/providers/dns/otc/otc.toml @@ -4,7 +4,13 @@ URL = "https://cloud.telekom.de/en" Code = "otc" Since = "v0.4.1" -Example = '''''' +Example = ''' +OTC_DOMAIN_NAME=domain_name \ +OTC_USER_NAME=user_name \ +OTC_PASSWORD=password \ +OTC_PROJECT_NAME=project_name \ +lego --email you@example.com --dns otc -d '*.example.com' -d example.com run +''' [Configuration] [Configuration.Credentials] @@ -13,7 +19,7 @@ Example = '''''' OTC_PROJECT_NAME = "Project name" OTC_DOMAIN_NAME = "Domain name" [Configuration.Additional] - OTC_IDENTITY_ENDPOINT = "Identity endpoint URL" + OTC_IDENTITY_ENDPOINT = "Identity endpoint URL (default: https://iam.eu-de.otc.t-systems.com:443/v3/auth/tokens)" OTC_PRIVATE_ZONE = "Set to true to use private zones only (default: use public zones only)" OTC_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" OTC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" From 8249f73fa2fef71542a5019e089bbc938cd6c0f8 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 28 Sep 2025 21:15:41 +0200 Subject: [PATCH 174/298] fix: deduplicate order identifiers (#2656) --- acme/api/identifier.go | 22 ++++++++++++++++++++++ acme/api/order.go | 31 ++++++++++++------------------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/acme/api/identifier.go b/acme/api/identifier.go index 27337ccba..42a8fd391 100644 --- a/acme/api/identifier.go +++ b/acme/api/identifier.go @@ -2,11 +2,33 @@ package api import ( "cmp" + "maps" + "net" "slices" "github.com/go-acme/lego/v4/acme" ) +func createIdentifiers(domains []string) []acme.Identifier { + uniqIdentifiers := make(map[string]acme.Identifier) + + for _, domain := range domains { + if _, ok := uniqIdentifiers[domain]; ok { + continue + } + + ident := acme.Identifier{Value: domain, Type: "dns"} + + if net.ParseIP(domain) != nil { + ident.Type = "ip" + } + + uniqIdentifiers[domain] = ident + } + + return slices.AppendSeq(make([]acme.Identifier, 0, len(uniqIdentifiers)), maps.Values(uniqIdentifiers)) +} + // compareIdentifiers compares 2 slices of [acme.Identifier]. func compareIdentifiers(a, b []acme.Identifier) int { // Clones slices to avoid modifying original slices. diff --git a/acme/api/order.go b/acme/api/order.go index 96cd4d287..13caa0d08 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -4,7 +4,6 @@ import ( "encoding/base64" "errors" "fmt" - "net" "slices" "time" @@ -36,18 +35,7 @@ func (o *OrderService) New(domains []string) (acme.ExtendedOrder, error) { // NewWithOptions Creates a new order. func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acme.ExtendedOrder, error) { - var identifiers []acme.Identifier - for _, domain := range domains { - ident := acme.Identifier{Value: domain, Type: "dns"} - - if net.ParseIP(domain) != nil { - ident.Type = "ip" - } - - identifiers = append(identifiers, ident) - } - - orderReq := acme.Order{Identifiers: identifiers} + orderReq := acme.Order{Identifiers: createIdentifiers(domains)} if opts != nil { if !opts.NotAfter.IsZero() { @@ -86,18 +74,23 @@ func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acm } } - // The elements of the "authorizations" and "identifiers" arrays are immutable once set. - // The server MUST NOT change the contents of either array after they are created. - // If a client observes a change in the contents of either array, - // then it SHOULD consider the order invalid. - // https://www.rfc-editor.org/rfc/rfc8555#section-7.1.3 + // The server MUST return an error if it cannot fulfill the request as specified, + // and it MUST NOT issue a certificate with contents other than those requested. + // If the server requires the request to be modified in a certain way, + // it should indicate the required changes using an appropriate error type and description. + // https://www.rfc-editor.org/rfc/rfc8555#section-7.4 + // + // Some ACME servers don't return an error, + // and/or change the order identifiers in the response, + // so we need to ensure that the identifiers are the same as requested. + // Deduplication by the server is allowed. if compareIdentifiers(orderReq.Identifiers, order.Identifiers) != 0 { // Sorts identifiers to avoid error message ambiguities about the order of the identifiers. slices.SortStableFunc(orderReq.Identifiers, compareIdentifier) slices.SortStableFunc(order.Identifiers, compareIdentifier) return acme.ExtendedOrder{}, - fmt.Errorf("order identifiers have been by the ACME server (RFC8555 §7.1.3): %+v != %+v", + fmt.Errorf("order identifiers have been modified by the ACME server (RFC8555 §7.4): %+v != %+v", orderReq.Identifiers, order.Identifiers) } From 621d9d0d0e1ffbf57f225752ce8e0de1839a937b Mon Sep 17 00:00:00 2001 From: cfif-31 <51353678+cfif-31@users.noreply.github.com> Date: Mon, 29 Sep 2025 13:36:53 +0400 Subject: [PATCH 175/298] Add DNS provider for Beget.com (#1879) Co-authored-by: Fernandez Ludovic --- README.md | 76 +++--- cmd/zz_gen_cmd_dnshelp.go | 22 ++ docs/content/dns/zz_gen_beget.md | 69 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/beget/beget.go | 156 ++++++++++++ providers/dns/beget/beget.toml | 24 ++ providers/dns/beget/beget_test.go | 229 ++++++++++++++++++ providers/dns/beget/internal/client.go | 135 +++++++++++ providers/dns/beget/internal/client_test.go | 103 ++++++++ .../beget/internal/fixtures/answer_error.json | 12 + .../internal/fixtures/changeRecords-doc.json | 31 +++ .../dns/beget/internal/fixtures/error.json | 5 + .../beget/internal/fixtures/getData-doc.json | 58 +++++ .../beget/internal/fixtures/getData-real.json | 67 +++++ .../dns/beget/internal/fixtures/getData.json | 67 +++++ .../internal/fixtures/getData_empty.json | 13 + providers/dns/beget/internal/types.go | 100 ++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 18 files changed, 1133 insertions(+), 39 deletions(-) create mode 100644 docs/content/dns/zz_gen_beget.md create mode 100644 providers/dns/beget/beget.go create mode 100644 providers/dns/beget/beget.toml create mode 100644 providers/dns/beget/beget_test.go create mode 100644 providers/dns/beget/internal/client.go create mode 100644 providers/dns/beget/internal/client_test.go create mode 100644 providers/dns/beget/internal/fixtures/answer_error.json create mode 100644 providers/dns/beget/internal/fixtures/changeRecords-doc.json create mode 100644 providers/dns/beget/internal/fixtures/error.json create mode 100644 providers/dns/beget/internal/fixtures/getData-doc.json create mode 100644 providers/dns/beget/internal/fixtures/getData-real.json create mode 100644 providers/dns/beget/internal/fixtures/getData.json create mode 100644 providers/dns/beget/internal/fixtures/getData_empty.json create mode 100644 providers/dns/beget/internal/types.go diff --git a/README.md b/README.md index 9c2e81cfc..51944b9d8 100644 --- a/README.md +++ b/README.md @@ -70,193 +70,193 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Azure DNS Baidu Cloud + Beget.com Binary Lane - Bindman + Bindman Bluecat BookMyName Brandit (deprecated) - Bunny + Bunny Checkdomain Civo Cloud.ru - CloudDNS + CloudDNS Cloudflare ClouDNS CloudXNS (Deprecated) - ConoHa v2 + ConoHa v2 ConoHa v3 Constellix Core-Networks - CPanel/WHM + CPanel/WHM Derak Cloud deSEC.io Designate DNSaaS for Openstack - Digital Ocean + Digital Ocean DirectAdmin DNS Made Easy dnsHome.de - DNSimple + DNSimple DNSPod (deprecated) Domain Offensive (do.de) Domeneshop - DreamHost + DreamHost Duck DNS Dyn DynDnsFree.de - Dynu + Dynu EasyDNS Efficient IP Epik - Exoscale + Exoscale External program F5 XC freemyip.com - G-Core + G-Core Gandi Gandi Live DNS (v5) Glesys - Go Daddy + Go Daddy Google Cloud Google Domains Hetzner - Hosting.de + Hosting.de Hostinger Hosttech HTTP request - http.net + http.net Huawei Cloud Hurricane Electric DNS HyperOne - IBM Cloud (SoftLayer) + IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox Infomaniak - Internet Initiative Japan + Internet Initiative Japan Internet.bs INWX Ionos - IPv64 + IPv64 iwantmyname Joker Joohoi's ACME-DNS - KeyHelp + KeyHelp Liara Lima-City Linode (v4) - Liquid Web + Liquid Web Loopia LuaDNS Mail-in-a-Box - ManageEngine CloudDNS + ManageEngine CloudDNS Manual Metaname Metaregistrar - mijn.host + mijn.host Mittwald myaddr.{tools,dev,io} MyDNS.jp - MythicBeasts + MythicBeasts Name.com Namecheap Namesilo - NearlyFreeSpeech.NET + NearlyFreeSpeech.NET Netcup Netlify Nicmanager - NIFCloud + NIFCloud Njalla Nodion NS1 - Open Telekom Cloud + Open Telekom Cloud Oracle Cloud OVH plesk.com - Porkbun + Porkbun PowerDNS Rackspace Rain Yun/雨云 - RcodeZero + RcodeZero reg.ru Regfish RFC2136 - RimuHosting + RimuHosting RU CENTER Sakura Cloud Scaleway - Selectel + Selectel Selectel v2 SelfHost.(de|eu) Servercow - Shellrent + Shellrent Simply.com Sonic Spaceship - Stackpath + Stackpath Technitium Tencent Cloud DNS Tencent EdgeOne - 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 - West.cn/西部数码 + West.cn/西部数码 Yandex 360 Yandex Cloud Yandex PDD - Zone.ee + Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index bffb0696f..f3f603cf1 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -25,6 +25,7 @@ func allDNSCodes() string { "azure", "azuredns", "baiducloud", + "beget", "binarylane", "bindman", "bluecat", @@ -451,6 +452,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/baiducloud`) + case "beget": + // generated from: providers/dns/beget/beget.toml + ew.writeln(`Configuration for Beget.com.`) + ew.writeln(`Code: 'beget'`) + ew.writeln(`Since: 'v4.27.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "BEGET_PASSWORD": API password`) + ew.writeln(` - "BEGET_USERNAME": API username`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "BEGET_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "BEGET_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 30)`) + ew.writeln(` - "BEGET_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) + ew.writeln(` - "BEGET_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/beget`) + case "binarylane": // generated from: providers/dns/binarylane/binarylane.toml ew.writeln(`Configuration for Binary Lane.`) diff --git a/docs/content/dns/zz_gen_beget.md b/docs/content/dns/zz_gen_beget.md new file mode 100644 index 000000000..ae1d16a7c --- /dev/null +++ b/docs/content/dns/zz_gen_beget.md @@ -0,0 +1,69 @@ +--- +title: "Beget.com" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: beget +dnsprovider: + since: "v4.27.0" + code: "beget" + url: "https://beget.com/" +--- + + + + + + +Configuration for [Beget.com](https://beget.com/). + + + + +- Code: `beget` +- Since: v4.27.0 + + +Here is an example bash command using the Beget.com provider: + +```bash +BEGET_USERNAME=xxxxxx \ +BEGET_PASSWORD=yyyyyy \ +lego --email you@example.com --dns beget -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `BEGET_PASSWORD` | API password | +| `BEGET_USERNAME` | API username | + +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 | +|--------------------------------|-------------| +| `BEGET_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `BEGET_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 30) | +| `BEGET_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | +| `BEGET_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://beget.com/ru/kb/api/funkczii-upravleniya-dns) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index c54206ffb..edcf0b7a7 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/beget/beget.go b/providers/dns/beget/beget.go new file mode 100644 index 000000000..d5354ac86 --- /dev/null +++ b/providers/dns/beget/beget.go @@ -0,0 +1,156 @@ +// Package beget implements a DNS provider for solving the DNS-01 challenge using beget.com DNS. +package beget + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/beget/internal" +) + +// Environment variables names. +const ( + envNamespace = "BEGET_" + + EnvUsername = envNamespace + "USERNAME" + EnvPassword = envNamespace + "PASSWORD" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + Username string + Password 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, 300), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 30*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for beget.com. +// Credentials must be passed in the environment variables: +// BEGET_USERNAME and BEGET_PASSWORD. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvUsername, EnvPassword) + if err != nil { + return nil, fmt.Errorf("beget: %w", err) + } + + config := NewDefaultConfig() + config.Username = values[EnvUsername] + config.Password = values[EnvPassword] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for beget.com. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("beget: the configuration of the DNS provider is nil") + } + + if config.Username == "" || config.Password == "" { + return nil, errors.New("beget: incomplete credentials, missing username and/or password") + } + + client := internal.NewClient(config.Username, config.Password) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{config: config, client: client}, 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 +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + records, err := d.client.GetTXTRecords(context.Background(), dns01.UnFqdn(info.EffectiveFQDN)) + if err != nil { + return fmt.Errorf("beget: get TXT records: %w", err) + } + + records = append(records, internal.Record{ + Value: info.Value, + Data: "", // NOTE: there are 2 fields in the API for the same thing. + Priority: 10, + TTL: d.config.TTL, + }) + + err = d.client.ChangeTXTRecord(context.Background(), dns01.UnFqdn(info.EffectiveFQDN), records) + if err != nil { + return fmt.Errorf("beget: failed to create TXT records [domain: %s]: %w", + dns01.UnFqdn(info.EffectiveFQDN), err) + } + + 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) + + records, err := d.client.GetTXTRecords(context.Background(), dns01.UnFqdn(info.EffectiveFQDN)) + if err != nil { + return fmt.Errorf("beget: get TXT records: %w", err) + } + + if len(records) == 0 { + return nil + } + + var updatedRecords []internal.Record + for _, record := range records { + if record.Data == info.Value { + continue + } + + updatedRecords = append(updatedRecords, record) + } + + err = d.client.ChangeTXTRecord(context.Background(), dns01.UnFqdn(info.EffectiveFQDN), updatedRecords) + if err != nil { + return fmt.Errorf("beget: failed to remove TXT records [domain: %s]: %w", + dns01.UnFqdn(info.EffectiveFQDN), err) + } + + return nil +} diff --git a/providers/dns/beget/beget.toml b/providers/dns/beget/beget.toml new file mode 100644 index 000000000..3cef2f38c --- /dev/null +++ b/providers/dns/beget/beget.toml @@ -0,0 +1,24 @@ +Name = "Beget.com" +Description = '''''' +URL = "https://beget.com/" +Code = "beget" +Since = "v4.27.0" + +Example = ''' +BEGET_USERNAME=xxxxxx \ +BEGET_PASSWORD=yyyyyy \ +lego --email you@example.com --dns beget -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + BEGET_USERNAME = "API username" + BEGET_PASSWORD = "API password" + [Configuration.Additional] + BEGET_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 30)" + BEGET_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" + BEGET_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + BEGET_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://beget.com/ru/kb/api/funkczii-upravleniya-dns" diff --git a/providers/dns/beget/beget_test.go b/providers/dns/beget/beget_test.go new file mode 100644 index 000000000..7ceb7b140 --- /dev/null +++ b/providers/dns/beget/beget_test.go @@ -0,0 +1,229 @@ +package beget + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "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: "beget: some credentials information are missing: BEGET_USERNAME,BEGET_PASSWORD", + }, + { + desc: "missing username", + envVars: map[string]string{ + EnvUsername: "", + EnvPassword: "456", + }, + expected: "beget: some credentials information are missing: BEGET_USERNAME", + }, + { + desc: "missing password", + envVars: map[string]string{ + EnvUsername: "123", + EnvPassword: "", + }, + expected: "beget: some credentials information are missing: BEGET_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 test.expected == "" { + 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: "123", + password: "456", + }, + { + desc: "missing credentials", + username: "", + password: "", + expected: "beget: incomplete credentials, missing username and/or password", + }, + { + desc: "missing username", + username: "", + password: "456", + expected: "beget: incomplete credentials, missing username and/or password", + }, + { + desc: "missing password", + username: "123", + password: "", + expected: "beget: incomplete credentials, missing username and/or password", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Username = test.username + config.Password = test.password + + p, err := NewDNSProviderConfig(config) + + if test.expected == "" { + 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() + assert.NoError(t, err) + + err = provider.Present(envTest.GetDomain(), "", "123d==") + assert.NoError(t, err) +} + +func TestLiveCleanUp(t *testing.T) { + if !envTest.IsLiveTest() { + t.Skip("skipping live test") + } + + envTest.RestoreEnv() + provider, err := NewDNSProvider() + assert.NoError(t, err) + + err = provider.CleanUp(envTest.GetDomain(), "", "123d==") + assert.NoError(t, err) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.Username = "user" + config.Password = "secret" + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.HTTPClient = server.Client() + p.client.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckQueryParameter(). + With("login", "user"). + With("passwd", "secret"). + With("input_format", "json"). + With("output_format", "json"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("GET /dns/getData", + servermock.ResponseFromInternal("getData-real.json"), + servermock.CheckQueryParameter(). + With("input_data", `{"fqdn":"_acme-challenge.example.com"}`), + ). + Route("GET /dns/changeRecords", + servermock.ResponseFromInternal("changeRecords-doc.json"), + servermock.CheckQueryParameter(). + With("input_data", `{"fqdn":"_acme-challenge.example.com","records":{"TXT":[{"txtdata":"v=spf1 redirect=beget.com","ttl":300},{"value":"ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY","priority":10,"ttl":300}]}}`), + ). + Build(t) + + err := provider.Present("example.com", "", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("GET /dns/getData", + servermock.ResponseFromInternal("getData.json"), + servermock.CheckQueryParameter(). + With("input_data", `{"fqdn":"_acme-challenge.example.com"}`), + ). + Route("GET /dns/changeRecords", + servermock.ResponseFromInternal("changeRecords-doc.json"), + servermock.CheckQueryParameter(). + With("input_data", `{"fqdn":"_acme-challenge.example.com","records":{"TXT":[{"txtdata":"foo","ttl":300}]}}`), + ). + Build(t) + + err := provider.CleanUp("example.com", "", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp_empty(t *testing.T) { + provider := mockBuilder(). + Route("GET /dns/getData", + servermock.ResponseFromInternal("getData_empty.json"), + servermock.CheckQueryParameter(). + With("input_data", `{"fqdn":"_acme-challenge.example.com"}`), + ). + Route("/", + servermock.Noop().WithStatusCode(http.StatusInternalServerError)). + Build(t) + + err := provider.CleanUp("example.com", "", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/beget/internal/client.go b/providers/dns/beget/internal/client.go new file mode 100644 index 000000000..d8d300606 --- /dev/null +++ b/providers/dns/beget/internal/client.go @@ -0,0 +1,135 @@ +package internal + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const defaultBaseURL = "https://api.beget.com/api/" + +// Client the beget.com client. +type Client struct { + login string + password string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient Creates a beget.com client. +func NewClient(login, password string) *Client { + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + login: login, + password: password, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 5 * time.Second}, + } +} + +// GetTXTRecords returns TXT records. +// https://beget.com/ru/kb/api/funkczii-upravleniya-dns#getdata +func (c *Client) GetTXTRecords(ctx context.Context, domain string) ([]Record, error) { + request := GetRecordsRequest{Fqdn: domain} + + resp, err := c.doRequest(ctx, request, "dns", "getData") + if err != nil { + return nil, err + } + + err = resp.HasError() + if err != nil { + return nil, err + } + + result := GetRecordsResult{} + + err = json.Unmarshal(resp.Answer.Result, &result) + if err != nil { + return nil, fmt.Errorf("unmarshal result: %s: %w", string(resp.Answer.Result), err) + } + + return result.Records.TXT, nil +} + +// ChangeTXTRecord changes TXT records. +// https://beget.com/ru/kb/api/funkczii-upravleniya-dns#changerecords +func (c *Client) ChangeTXTRecord(ctx context.Context, domain string, records []Record) error { + request := ChangeRecordsRequest{ + Fqdn: domain, + Records: RecordList{TXT: records}, + } + + resp, err := c.doRequest(ctx, request, "dns", "changeRecords") + if err != nil { + return err + } + + return resp.HasError() +} + +func (c *Client) doRequest(ctx context.Context, data any, fragments ...string) (*APIResponse, error) { + endpoint := c.BaseURL.JoinPath(fragments...) + + inputData, err := json.Marshal(data) + if err != nil { + return nil, fmt.Errorf("failed to mashall input data: %w", err) + } + + query := endpoint.Query() + query.Add("input_data", string(inputData)) + query.Add("login", c.login) + query.Add("passwd", c.password) + query.Add("input_format", "json") + query.Add("output_format", "json") + endpoint.RawQuery = query.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), http.NoBody) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return nil, errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return nil, parseError(req, resp) + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return nil, errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + var apiResp APIResponse + err = json.Unmarshal(raw, &apiResp) + if err != nil { + return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return &apiResp, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var apiResp APIResponse + err := json.Unmarshal(raw, &apiResp) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return fmt.Errorf("[status code %d] %w", resp.StatusCode, apiResp) +} diff --git a/providers/dns/beget/internal/client_test.go b/providers/dns/beget/internal/client_test.go new file mode 100644 index 000000000..4c127abf1 --- /dev/null +++ b/providers/dns/beget/internal/client_test.go @@ -0,0 +1,103 @@ +package internal + +import ( + "context" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient("user", "secret") + + client.HTTPClient = server.Client() + client.BaseURL, _ = url.Parse(server.URL) + + return client, nil + }, + servermock.CheckQueryParameter(). + With("login", "user"). + With("passwd", "secret"). + With("input_format", "json"). + With("output_format", "json"), + ) +} + +func TestClient_GetTXTRecords(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/getData", + servermock.ResponseFromFixture("getData-real.json"), + servermock.CheckQueryParameter(). + With("input_data", `{"fqdn":"example.com"}`), + ). + Build(t) + + data, err := client.GetTXTRecords(context.Background(), "example.com") + require.NoError(t, err) + + expected := []Record{{Data: "v=spf1 redirect=beget.com", TTL: 300}} + + assert.Equal(t, expected, data) +} + +func TestClient_ChangeTXTRecord(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/changeRecords", + servermock.ResponseFromFixture("changeRecords-doc.json"), + servermock.CheckQueryParameter(). + With("input_data", `{"fqdn":"sub.example.com","records":{"TXT":[{"value":"txtTXTtxt","priority":10,"ttl":300}]}}`), + ). + Build(t) + + records := []Record{{Value: "txtTXTtxt", TTL: 300, Priority: 10}} + + err := client.ChangeTXTRecord(context.Background(), "sub.example.com", records) + require.NoError(t, err) +} + +func TestClient_ChangeTXTRecord_error(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/changeRecords", + servermock.ResponseFromFixture("error.json")). + Build(t) + + records := []Record{{Data: "txtTXTtxt", TTL: 300}} + + err := client.ChangeTXTRecord(context.Background(), "sub.example.com", records) + require.Error(t, err) + + require.EqualError(t, err, "API error: NO_SUCH_METHOD: No such method") +} + +func TestClient_ChangeTXTRecord_answer_error(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/changeRecords", + servermock.ResponseFromFixture("answer_error.json")). + Build(t) + + records := []Record{{Data: "txtTXTtxt", TTL: 300}} + + err := client.ChangeTXTRecord(context.Background(), "sub.example.com", records) + require.Error(t, err) + + require.EqualError(t, err, "API answer error: INVALID_DATA: Login length cannot be greater than 12 characters") +} + +func TestClient_ChangeTXTRecord_remove(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/changeRecords", + servermock.ResponseFromFixture("changeRecords-doc.json"), + servermock.CheckQueryParameter(). + With("input_data", `{"fqdn":"sub.example.com","records":{}}`), + ). + Build(t) + + err := client.ChangeTXTRecord(context.Background(), "sub.example.com", nil) + require.NoError(t, err) +} diff --git a/providers/dns/beget/internal/fixtures/answer_error.json b/providers/dns/beget/internal/fixtures/answer_error.json new file mode 100644 index 000000000..12f5fdda7 --- /dev/null +++ b/providers/dns/beget/internal/fixtures/answer_error.json @@ -0,0 +1,12 @@ +{ + "status": "success", + "answer": { + "status": "error", + "errors": [ + { + "error_code": "INVALID_DATA", + "error_text": "Login length cannot be greater than 12 characters" + } + ] + } +} diff --git a/providers/dns/beget/internal/fixtures/changeRecords-doc.json b/providers/dns/beget/internal/fixtures/changeRecords-doc.json new file mode 100644 index 000000000..4c182d4e6 --- /dev/null +++ b/providers/dns/beget/internal/fixtures/changeRecords-doc.json @@ -0,0 +1,31 @@ +{ + "status": "success", + "answer": { + "status": "success", + "result": { + "A": [ + { + "priority": 10, + "value": "127.0.0.1" + } + ], + "MX": [ + { + "priority": 10, + "value": "mx1.beget.ru" + }, + { + "priority": 20, + "value": "mx2.beget.ru" + } + ], + "TXT": [ + { + "priority": 10, + "value": "TXT record" + } + ] + } + } +} + diff --git a/providers/dns/beget/internal/fixtures/error.json b/providers/dns/beget/internal/fixtures/error.json new file mode 100644 index 000000000..1dd2a111e --- /dev/null +++ b/providers/dns/beget/internal/fixtures/error.json @@ -0,0 +1,5 @@ +{ + "status": "error", + "error_text": "No such method", + "error_code": "NO_SUCH_METHOD" +} diff --git a/providers/dns/beget/internal/fixtures/getData-doc.json b/providers/dns/beget/internal/fixtures/getData-doc.json new file mode 100644 index 000000000..bed5b7461 --- /dev/null +++ b/providers/dns/beget/internal/fixtures/getData-doc.json @@ -0,0 +1,58 @@ +{ + "status": "success", + "answer": { + "status": "success", + "result": { + "is_under_control": 1, + "is_beget_dns": 1, + "is_subdomain": 0, + "fqdn": "beget.ru", + "records": { + "DNS": [ + { + "value": "ns1.beget.ru", + "priority": 10 + }, + { + "value": "ns2.beget.ru", + "priority": 20 + } + ], + "DNS_IP": [ + { + "value": null, + "priority": 10 + }, + { + "value": null, + "priority": 20 + } + ], + "A": [ + { + "value": "91.106.201.65", + "priority": "0" + } + ], + "MX": [ + { + "value": "mx1.beget.ru", + "priority": "10" + }, + { + "value": "mx2.beget.ru", + "priority": "20" + } + ], + "TXT": [ + { + "value": "", + "priority": 0 + } + ] + }, + "set_type": 1 + } + } +} + diff --git a/providers/dns/beget/internal/fixtures/getData-real.json b/providers/dns/beget/internal/fixtures/getData-real.json new file mode 100644 index 000000000..700c756e8 --- /dev/null +++ b/providers/dns/beget/internal/fixtures/getData-real.json @@ -0,0 +1,67 @@ +{ + "status": "success", + "answer": { + "status": "success", + "result": { + "is_under_control": true, + "is_beget_dns": true, + "is_subdomain": false, + "fqdn": "example.com", + "records": { + "MX": [ + { + "ttl": 300, + "exchange": "mx2.beget.com.", + "preference": 20 + }, + { + "ttl": 300, + "exchange": "mx1.beget.com.", + "preference": 10 + } + ], + "TXT": [ + { + "ttl": 300, + "txtdata": "v=spf1 redirect=beget.com" + } + ], + "A": [ + { + "ttl": 300, + "address": "1.2.3.4" + } + ], + "DNS": [ + { + "value": "ns1.beget.pro" + }, + { + "value": "ns2.beget.pro" + }, + { + "value": "ns1.beget.com" + }, + { + "value": "ns2.beget.com" + } + ], + "DNS_IP": [ + { + "value": "" + }, + { + "value": "" + }, + { + "value": "" + }, + { + "value": "" + } + ] + }, + "set_type": 1 + } + } +} diff --git a/providers/dns/beget/internal/fixtures/getData.json b/providers/dns/beget/internal/fixtures/getData.json new file mode 100644 index 000000000..571b6ac31 --- /dev/null +++ b/providers/dns/beget/internal/fixtures/getData.json @@ -0,0 +1,67 @@ +{ + "status": "success", + "answer": { + "status": "success", + "result": { + "is_under_control": true, + "is_beget_dns": true, + "is_subdomain": false, + "fqdn": "_acme-challenge.example.com", + "records": { + "MX": [ + { + "ttl": 300, + "exchange": "mx2.beget.com.", + "preference": 20 + }, + { + "ttl": 300, + "exchange": "mx1.beget.com.", + "preference": 10 + } + ], + "TXT": [ + { + "ttl": 300, + "txtdata": "foo" + } + ], + "A": [ + { + "ttl": 300, + "address": "1.2.3.4" + } + ], + "DNS": [ + { + "value": "ns1.beget.pro" + }, + { + "value": "ns2.beget.pro" + }, + { + "value": "ns1.beget.com" + }, + { + "value": "ns2.beget.com" + } + ], + "DNS_IP": [ + { + "value": "" + }, + { + "value": "" + }, + { + "value": "" + }, + { + "value": "" + } + ] + }, + "set_type": 1 + } + } +} diff --git a/providers/dns/beget/internal/fixtures/getData_empty.json b/providers/dns/beget/internal/fixtures/getData_empty.json new file mode 100644 index 000000000..ea819eeca --- /dev/null +++ b/providers/dns/beget/internal/fixtures/getData_empty.json @@ -0,0 +1,13 @@ +{ + "status": "success", + "answer": { + "status": "success", + "result": { + "is_under_control": true, + "is_beget_dns": true, + "is_subdomain": false, + "fqdn": "_acme-challenge.example.com", + "set_type": 1 + } + } +} diff --git a/providers/dns/beget/internal/types.go b/providers/dns/beget/internal/types.go new file mode 100644 index 000000000..90766da79 --- /dev/null +++ b/providers/dns/beget/internal/types.go @@ -0,0 +1,100 @@ +package internal + +import ( + "encoding/json" + "fmt" + "strings" +) + +const successResult = "success" + +// APIResponse is the representation of an API response. +type APIResponse struct { + Status string `json:"status"` + + Answer *Answer `json:"answer,omitempty"` + + ErrorCode string `json:"error_code,omitempty"` + ErrorText string `json:"error_text,omitempty"` +} + +func (a APIResponse) Error() string { + return fmt.Sprintf("API %s: %s: %s", a.Status, a.ErrorCode, a.ErrorText) +} + +// HasError returns an error is the response contains an error. +func (a APIResponse) HasError() error { + if a.Status != successResult { + return a + } + + if a.Answer == nil || a.Status != successResult || a.Answer.Status != successResult { + return a.Answer + } + + return nil +} + +// Answer is the representation of an API response answer. +type Answer struct { + Status string `json:"status,omitempty"` + Result json.RawMessage `json:"result,omitempty"` + + Errors []AnswerError `json:"errors,omitempty"` + ErrorCode string `json:"error_code,omitempty"` + ErrorText string `json:"error_text,omitempty"` +} + +type AnswerError struct { + ErrorCode string `json:"error_code,omitempty"` + ErrorText string `json:"error_text,omitempty"` +} + +func (a Answer) Error() string { + parts := []string{fmt.Sprintf("API answer %s", a.Status)} + + if a.ErrorCode != "" { + parts = append(parts, a.ErrorCode) + } + + if a.ErrorText != "" { + parts = append(parts, a.ErrorText) + } + + if len(a.Errors) > 0 { + for _, e := range a.Errors { + parts = append(parts, e.ErrorCode, e.ErrorText) + } + } + + return strings.Join(parts, ": ") +} + +// GetRecordsRequest data representation for data get request. +type GetRecordsRequest struct { + Fqdn string `json:"fqdn,omitempty"` +} + +// ChangeRecordsRequest data representation for data change request. +type ChangeRecordsRequest struct { + Fqdn string `json:"fqdn,omitempty"` + Records RecordList `json:"records,omitempty"` +} + +// RecordList List of entries (in this case only described TXT). +type RecordList struct { + TXT []Record `json:"TXT,omitempty"` +} + +// Record data representation for TXT record. +type Record struct { + Value string `json:"value,omitempty"` + Data string `json:"txtdata,omitempty"` + Priority int `json:"priority,omitempty"` + TTL int `json:"ttl,omitempty"` +} + +type GetRecordsResult struct { + Fqdn string `json:"fqdn"` + Records RecordList `json:"records"` +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 1e6586e89..df0c59bf6 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -19,6 +19,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/azure" "github.com/go-acme/lego/v4/providers/dns/azuredns" "github.com/go-acme/lego/v4/providers/dns/baiducloud" + "github.com/go-acme/lego/v4/providers/dns/beget" "github.com/go-acme/lego/v4/providers/dns/binarylane" "github.com/go-acme/lego/v4/providers/dns/bindman" "github.com/go-acme/lego/v4/providers/dns/bluecat" @@ -199,6 +200,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return azuredns.NewDNSProvider() case "baiducloud": return baiducloud.NewDNSProvider() + case "beget": + return beget.NewDNSProvider() case "binarylane": return binarylane.NewDNSProvider() case "bindman": From 7a6aa1110a215bdfd6c6f8e54aa84eb1047b7478 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 29 Sep 2025 18:06:38 +0200 Subject: [PATCH 176/298] chore: update release workflow (#2657) --- .github/workflows/release.yml | 14 ++++- .goreleaser.yml | 103 ++++++++-------------------------- buildx.Dockerfile | 4 +- 3 files changed, 40 insertions(+), 81 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ee3ea21dd..c2d946694 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,11 @@ on: tags: - v* +permissions: + # Allow the workflow to write attestations. + id-token: write + attestations: write + jobs: release: @@ -66,9 +71,16 @@ jobs: - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6 with: - version: v2.8.1 + version: v2.12.3 args: release -p 1 --clean --timeout=90m env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN_REPO }} SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} AUR_KEY: ${{ secrets.AUR_KEY }} + + - uses: actions/attest-build-provenance@v3 + with: + subject-checksums: ./dist/checksums.txt + - uses: actions/attest-build-provenance@v3 + with: + subject-checksums: ./dist/digests.txt diff --git a/.goreleaser.yml b/.goreleaser.yml index 9bf101420..25252850b 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -42,6 +42,10 @@ builds: goarch: 386 - goos: openbsd goarch: arm + # Deprecated in go1.25, Removed in go1.26 + # https://go.dev/doc/go1.25#windows + - goos: windows + goarch: arm changelog: sort: asc @@ -62,87 +66,28 @@ archives: - LICENSE - CHANGELOG.md -docker_manifests: - - name_template: 'goacme/lego:{{ .Tag }}' - image_templates: - - 'goacme/lego:{{ .Tag }}-amd64' - - 'goacme/lego:{{ .Tag }}-arm64' - - 'goacme/lego:{{ .Tag }}-armv7' - - name_template: 'goacme/lego:latest' - image_templates: - - 'goacme/lego:{{ .Tag }}-amd64' - - 'goacme/lego:{{ .Tag }}-arm64' - - 'goacme/lego:{{ .Tag }}-armv7' - - name_template: 'goacme/lego:v{{ .Major }}.{{ .Minor }}' - image_templates: - - 'goacme/lego:v{{ .Major }}.{{ .Minor }}-amd64' - - 'goacme/lego:v{{ .Major }}.{{ .Minor }}-arm64' - - 'goacme/lego:v{{ .Major }}.{{ .Minor }}-armv7' - -dockers: - - use: buildx - goos: linux - goarch: amd64 +dockers_v2: + - images: + - 'goacme/lego' dockerfile: buildx.Dockerfile - image_templates: - - 'goacme/lego:latest-amd64' - - 'goacme/lego:{{ .Tag }}-amd64' - - 'goacme/lego:v{{ .Major }}.{{ .Minor }}-amd64' - build_flag_templates: - - '--pull' + platforms: + - linux/amd64 + - linux/arm64 + - linux/arm/v7 + tags: + - 'latest' + - '{{ .Tag }}' + - 'v{{ .Major }}.{{ .Minor }}' + labels: # https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys - - '--label=org.opencontainers.image.title={{.ProjectName}}' - - '--label=org.opencontainers.image.description=Lets Encrypt/ACME client and library written in Go' - - '--label=org.opencontainers.image.source={{.GitURL}}' - - '--label=org.opencontainers.image.url={{.GitURL}}' - - '--label=org.opencontainers.image.documentation=https://go-acme.github.io/lego' - - '--label=org.opencontainers.image.created={{.Date}}' - - '--label=org.opencontainers.image.revision={{.FullCommit}}' - - '--label=org.opencontainers.image.version={{.Version}}' - - '--platform=linux/amd64' - - - use: buildx - goos: linux - goarch: arm64 - dockerfile: buildx.Dockerfile - image_templates: - - 'goacme/lego:latest-arm64' - - 'goacme/lego:{{ .Tag }}-arm64' - - 'goacme/lego:v{{ .Major }}.{{ .Minor }}-arm64' - build_flag_templates: - - '--pull' - # https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys - - '--label=org.opencontainers.image.title={{.ProjectName}}' - - '--label=org.opencontainers.image.description=Lets Encrypt/ACME client and library written in Go' - - '--label=org.opencontainers.image.source={{.GitURL}}' - - '--label=org.opencontainers.image.url={{.GitURL}}' - - '--label=org.opencontainers.image.documentation=https://go-acme.github.io/lego' - - '--label=org.opencontainers.image.created={{.Date}}' - - '--label=org.opencontainers.image.revision={{.FullCommit}}' - - '--label=org.opencontainers.image.version={{.Version}}' - - '--platform=linux/arm64' - - - use: buildx - goos: linux - goarch: arm - goarm: '7' - dockerfile: buildx.Dockerfile - image_templates: - - 'goacme/lego:latest-armv7' - - 'goacme/lego:{{ .Tag }}-armv7' - - 'goacme/lego:v{{ .Major }}.{{ .Minor }}-armv7' - build_flag_templates: - - '--pull' - # https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys - - '--label=org.opencontainers.image.title={{.ProjectName}}' - - '--label=org.opencontainers.image.description=Lets Encrypt/ACME client and library written in Go' - - '--label=org.opencontainers.image.source={{.GitURL}}' - - '--label=org.opencontainers.image.url={{.GitURL}}' - - '--label=org.opencontainers.image.documentation=https://go-acme.github.io/lego' - - '--label=org.opencontainers.image.created={{.Date}}' - - '--label=org.opencontainers.image.revision={{.FullCommit}}' - - '--label=org.opencontainers.image.version={{.Version}}' - - '--platform=linux/arm/v7' + 'org.opencontainers.image.title': '{{.ProjectName}}' + 'org.opencontainers.image.description': 'Lets Encrypt/ACME client and library written in Go' + 'org.opencontainers.image.source': '{{.GitURL}}' + 'org.opencontainers.image.url': '{{.GitURL}}' + 'org.opencontainers.image.documentation': 'https://go-acme.github.io/lego' + 'org.opencontainers.image.created': '{{.Date}}' + 'org.opencontainers.image.revision': '{{.FullCommit}}' + 'org.opencontainers.image.version': '{{.Version}}' snapcrafts: - name_template: "{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" diff --git a/buildx.Dockerfile b/buildx.Dockerfile index 92a86dd3d..37f1dde94 100644 --- a/buildx.Dockerfile +++ b/buildx.Dockerfile @@ -1,10 +1,12 @@ # syntax=docker/dockerfile:1.4 FROM alpine:3 +ARG TARGETPLATFORM + RUN apk --no-cache --no-progress add git ca-certificates tzdata \ && rm -rf /var/cache/apk/* -COPY lego / +COPY $TARGETPLATFORM/lego / ENTRYPOINT ["/lego"] EXPOSE 80 From a3f3c620e990b5cc8a5d3cd7d06d65d71f3cfde0 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 7 Oct 2025 16:41:10 +0200 Subject: [PATCH 177/298] Add DNS provider for Octenium (#2661) --- README.md | 26 +- cmd/zz_gen_cmd_dnshelp.go | 21 ++ docs/content/dns/zz_gen_octenium.md | 67 ++++++ docs/data/zz_cli_help.toml | 2 +- .../dns/octenium/fixtures/add_dns_record.json | 14 ++ .../octenium/fixtures/delete_dns_record.json | 11 + .../octenium/fixtures/list_dns_records.json | 27 +++ .../dns/octenium/fixtures/list_domains.json | 13 + providers/dns/octenium/internal/client.go | 204 ++++++++++++++++ .../dns/octenium/internal/client_test.go | 224 ++++++++++++++++++ .../internal/fixtures/add_dns_record.json | 14 ++ .../internal/fixtures/delete_dns_record.json | 11 + .../dns/octenium/internal/fixtures/error.json | 5 + .../internal/fixtures/list_dns_records.json | 38 +++ .../internal/fixtures/list_domains.json | 25 ++ providers/dns/octenium/internal/types.go | 45 ++++ providers/dns/octenium/octenium.go | 198 ++++++++++++++++ providers/dns/octenium/octenium.toml | 22 ++ providers/dns/octenium/octenium_test.go | 195 +++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 20 files changed, 1151 insertions(+), 14 deletions(-) create mode 100644 docs/content/dns/zz_gen_octenium.md create mode 100644 providers/dns/octenium/fixtures/add_dns_record.json create mode 100644 providers/dns/octenium/fixtures/delete_dns_record.json create mode 100644 providers/dns/octenium/fixtures/list_dns_records.json create mode 100644 providers/dns/octenium/fixtures/list_domains.json create mode 100644 providers/dns/octenium/internal/client.go create mode 100644 providers/dns/octenium/internal/client_test.go create mode 100644 providers/dns/octenium/internal/fixtures/add_dns_record.json create mode 100644 providers/dns/octenium/internal/fixtures/delete_dns_record.json create mode 100644 providers/dns/octenium/internal/fixtures/error.json create mode 100644 providers/dns/octenium/internal/fixtures/list_dns_records.json create mode 100644 providers/dns/octenium/internal/fixtures/list_domains.json create mode 100644 providers/dns/octenium/internal/types.go create mode 100644 providers/dns/octenium/octenium.go create mode 100644 providers/dns/octenium/octenium.toml create mode 100644 providers/dns/octenium/octenium_test.go diff --git a/README.md b/README.md index 51944b9d8..303f8824b 100644 --- a/README.md +++ b/README.md @@ -193,70 +193,70 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Nodion NS1 + Octenium Open Telekom Cloud Oracle Cloud OVH - plesk.com + plesk.com Porkbun PowerDNS Rackspace - Rain Yun/雨云 + Rain Yun/雨云 RcodeZero reg.ru Regfish - RFC2136 + RFC2136 RimuHosting RU CENTER Sakura Cloud - Scaleway + Scaleway Selectel Selectel v2 SelfHost.(de|eu) - Servercow + Servercow Shellrent Simply.com Sonic - Spaceship + Spaceship Stackpath Technitium Tencent Cloud DNS - Tencent EdgeOne + Tencent EdgeOne Timeweb Cloud TransIP UKFast SafeDNS - Ultradns + Ultradns Variomedia VegaDNS Vercel - Versio.[nl|eu|uk] + Versio.[nl|eu|uk] VinylDNS VK Cloud Volcano Engine/火山引擎 - Vscale + Vscale Vultr Webnames Websupport - WEDOS + WEDOS West.cn/西部数码 Yandex 360 Yandex Cloud - Yandex PDD + Yandex PDD Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index f3f603cf1..92f7b5a07 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -125,6 +125,7 @@ func allDNSCodes() string { "njalla", "nodion", "ns1", + "octenium", "oraclecloud", "otc", "ovh", @@ -2574,6 +2575,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/ns1`) + case "octenium": + // generated from: providers/dns/octenium/octenium.toml + ew.writeln(`Configuration for Octenium.`) + ew.writeln(`Code: 'octenium'`) + ew.writeln(`Since: 'v4.27.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "OCTENIUM_API_KEY": API key`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "OCTENIUM_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "OCTENIUM_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "OCTENIUM_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "OCTENIUM_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/octenium`) + case "oraclecloud": // generated from: providers/dns/oraclecloud/oraclecloud.toml ew.writeln(`Configuration for Oracle Cloud.`) diff --git a/docs/content/dns/zz_gen_octenium.md b/docs/content/dns/zz_gen_octenium.md new file mode 100644 index 000000000..874c4e780 --- /dev/null +++ b/docs/content/dns/zz_gen_octenium.md @@ -0,0 +1,67 @@ +--- +title: "Octenium" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: octenium +dnsprovider: + since: "v4.27.0" + code: "octenium" + url: "https://octenium.com/" +--- + + + + + + +Configuration for [Octenium](https://octenium.com/). + + + + +- Code: `octenium` +- Since: v4.27.0 + + +Here is an example bash command using the Octenium provider: + +```bash +OCTENIUM_API_KEY="xxx" \ +lego --email you@example.com --dns octenium -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `OCTENIUM_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 | +|--------------------------------|-------------| +| `OCTENIUM_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `OCTENIUM_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `OCTENIUM_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `OCTENIUM_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://octenium.com/api#tag/Domains-DNS) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index edcf0b7a7..8943057d0 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/octenium/fixtures/add_dns_record.json b/providers/dns/octenium/fixtures/add_dns_record.json new file mode 100644 index 000000000..25edcdf11 --- /dev/null +++ b/providers/dns/octenium/fixtures/add_dns_record.json @@ -0,0 +1,14 @@ +{ + "api-status": "success", + "api-response": { + "record": { + "type": "TXT", + "name": "_acme-challenge.example.com.", + "ttl": 120, + "value": "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI", + "raw": { + "txtdata": "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI" + } + } + } +} diff --git a/providers/dns/octenium/fixtures/delete_dns_record.json b/providers/dns/octenium/fixtures/delete_dns_record.json new file mode 100644 index 000000000..2aa9415cc --- /dev/null +++ b/providers/dns/octenium/fixtures/delete_dns_record.json @@ -0,0 +1,11 @@ +{ + "api-status": "success", + "api-response": { + "deleted": { + "count": 1, + "lines": [ + 123 + ] + } + } +} diff --git a/providers/dns/octenium/fixtures/list_dns_records.json b/providers/dns/octenium/fixtures/list_dns_records.json new file mode 100644 index 000000000..405afff11 --- /dev/null +++ b/providers/dns/octenium/fixtures/list_dns_records.json @@ -0,0 +1,27 @@ +{ + "api-status": "success", + "api-response": { + "records": [ + { + "line": 31, + "type": "TXT", + "name": "_dmarc.example.com.", + "ttl": 300, + "value": "xxx", + "raw": { + "txtdata": "xxx" + } + }, + { + "line": 123, + "type": "TXT", + "name": "_acme-challenge.example.com.", + "ttl": 300, + "value": "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI", + "raw": { + "txtdata": "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI" + } + } + ] + } +} diff --git a/providers/dns/octenium/fixtures/list_domains.json b/providers/dns/octenium/fixtures/list_domains.json new file mode 100644 index 000000000..a62febcda --- /dev/null +++ b/providers/dns/octenium/fixtures/list_domains.json @@ -0,0 +1,13 @@ +{ + "api-status": "success", + "api-response": { + "domains": { + "2976": { + "domain-name": "example.com", + "registration-date": "21\/08\/2025", + "expiration-date": "-", + "status": "active" + } + } + } +} diff --git a/providers/dns/octenium/internal/client.go b/providers/dns/octenium/internal/client.go new file mode 100644 index 000000000..474770aeb --- /dev/null +++ b/providers/dns/octenium/internal/client.go @@ -0,0 +1,204 @@ +package internal + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + querystring "github.com/google/go-querystring/query" +) + +const defaultBaseURL = "https://api.panel.octenium.com/" + +const statusSuccess = "success" + +// Client the Octenium API client. +type Client struct { + apiKey string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(apiKey string) (*Client, error) { + if apiKey == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + apiKey: apiKey, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +// ListDomains retrieves a list of domains. +// https://octenium.com/api#tag/Domains/operation/listdomains +func (c *Client) ListDomains(ctx context.Context, domain string) (map[string]Domain, error) { + endpoint := c.BaseURL.JoinPath("domains") + + data := endpoint.Query() + data.Set("domain-name", domain) + endpoint.RawQuery = data.Encode() + + req, err := newRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + result := &DomainsResponse{} + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result.Domains, nil +} + +// ListDNSRecords retrieves a list of DNS records. +// https://octenium.com/api#tag/Domains-DNS/operation/domains-dns-records-list +func (c *Client) ListDNSRecords(ctx context.Context, orderID, recordType string) ([]Record, error) { + endpoint := c.BaseURL.JoinPath("domains", "dns-records", "list") + + data := make(url.Values) + data.Set("order-id", orderID) + data.Set("types[]", recordType) + + req, err := newRequest(ctx, http.MethodPost, endpoint, data) + if err != nil { + return nil, err + } + + result := &ListRecordsResponse{} + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result.Records, nil +} + +// AddDNSRecord adds a DNS record. +// https://octenium.com/api#tag/Domains-DNS/operation/domains-dns-records-add +func (c *Client) AddDNSRecord(ctx context.Context, orderID string, record Record) (*Record, error) { + endpoint := c.BaseURL.JoinPath("domains", "dns-records", "add") + + data, err := querystring.Values(record) + if err != nil { + return nil, err + } + + data.Set("order-id", orderID) + + req, err := newRequest(ctx, http.MethodPost, endpoint, data) + if err != nil { + return nil, err + } + + result := &AddRecordResponse{} + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result.Record, nil +} + +// DeleteDNSRecord deletes a DNS record. +// https://octenium.com/api#tag/Domains-DNS/operation/domains-dns-records-delete +func (c *Client) DeleteDNSRecord(ctx context.Context, orderID string, recordID int) (*DeletedRecordInfo, error) { + endpoint := c.BaseURL.JoinPath("domains", "dns-records", "delete") + + data := make(url.Values) + data.Set("order-id", orderID) + data.Set("line", strconv.Itoa(recordID)) + + req, err := newRequest(ctx, http.MethodPost, endpoint, data) + if err != nil { + return nil, err + } + + result := &DeleteRecordResponse{} + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result.Deleted, nil +} + +func (c *Client) do(req *http.Request, result any) error { + req.Header.Set("X-Api-Key", c.apiKey) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + raw, _ := io.ReadAll(resp.Body) + + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + var response APIResponse + + err = json.Unmarshal(raw, &response) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + if response.Status != statusSuccess { + return fmt.Errorf("unexpected status: %s: %s", response.Status, response.Error) + } + + err = json.Unmarshal(response.Response, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, response.Response, err) + } + + return nil +} + +func newRequest(ctx context.Context, method string, endpoint *url.URL, payload url.Values) (*http.Request, error) { + var body io.Reader = http.NoBody + + if method == http.MethodPost && payload != nil { + body = strings.NewReader(payload.Encode()) + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), body) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if method == http.MethodPost && payload != nil { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } + + return req, nil +} diff --git a/providers/dns/octenium/internal/client_test.go b/providers/dns/octenium/internal/client_test.go new file mode 100644 index 000000000..ff1b21961 --- /dev/null +++ b/providers/dns/octenium/internal/client_test.go @@ -0,0 +1,224 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("secret") + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + WithAccept("application/json"). + With("X-Api-Key", "secret"), + ) +} + +func TestClient_ListDomains(t *testing.T) { + client := mockBuilder(). + Route("GET /domains", + servermock.ResponseFromFixture("list_domains.json"), + servermock.CheckQueryParameter().Strict(). + With("domain-name", "example.com")). + Build(t) + + domains, err := client.ListDomains(t.Context(), "example.com") + require.NoError(t, err) + + expected := map[string]Domain{ + "2976": {DomainName: "example.com", RegistrationDate: "12/09/2021", ExpirationDate: "12/09/2024", Status: "active"}, + "2977": {DomainName: "example.org", RegistrationDate: "01/10/2021", ExpirationDate: "01/10/2024", Status: "active"}, + "2978": {DomainName: "example.net", RegistrationDate: "21/08/2025", ExpirationDate: "-", Status: "active"}, + } + + assert.Equal(t, expected, domains) +} + +func TestClient_ListDomains_error(t *testing.T) { + client := mockBuilder(). + Route("GET /domains", + servermock.Noop().WithStatusCode(http.StatusBadRequest)). + Build(t) + + _, err := client.ListDomains(t.Context(), "example.com") + require.EqualError(t, err, "unexpected status code: [status code: 400] body: ") +} + +func TestClient_ListDomains_api_error(t *testing.T) { + client := mockBuilder(). + Route("GET /domains", + servermock.ResponseFromFixture("error.json")). + Build(t) + + _, err := client.ListDomains(t.Context(), "example.com") + require.EqualError(t, err, "unexpected status: error: missing required fields (type, name, ttl)") +} + +func TestClient_ListDNSRecords(t *testing.T) { + client := mockBuilder(). + Route("POST /domains/dns-records/list", + servermock.ResponseFromFixture("list_dns_records.json"), + servermock.CheckHeader(). + WithContentType("application/x-www-form-urlencoded"), + servermock.CheckForm().Strict(). + With("order-id", "abc"). + With("types[]", "TXT")). + Build(t) + + records, err := client.ListDNSRecords(t.Context(), "abc", "TXT") + require.NoError(t, err) + + expected := []Record{ + {ID: 15, Type: "A", Name: "example.com.", TTL: 14400, Value: "203.0.113.10"}, + {ID: 22, Type: "MX", Name: "example.com.", TTL: 14400, Value: "10 mail.example.com."}, + {ID: 31, Type: "TXT", Name: "_dmarc.example.com.", TTL: 300, Value: "v=DMARC1; p=none; rua=mailto:dmarc@example.com"}, + } + + assert.Equal(t, expected, records) +} + +func TestClient_ListDNSRecords_error(t *testing.T) { + client := mockBuilder(). + Route("POST /domains/dns-records/list", + servermock.Noop().WithStatusCode(http.StatusBadRequest)). + Build(t) + + _, err := client.ListDNSRecords(t.Context(), "abc", "TXT") + require.EqualError(t, err, "unexpected status code: [status code: 400] body: ") +} + +func TestClient_ListDNSRecords_api_error(t *testing.T) { + client := mockBuilder(). + Route("POST /domains/dns-records/list", + servermock.ResponseFromFixture("error.json")). + Build(t) + + _, err := client.ListDNSRecords(t.Context(), "abc", "TXT") + require.EqualError(t, err, "unexpected status: error: missing required fields (type, name, ttl)") +} + +func TestClient_AddDNSRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /domains/dns-records/add", + servermock.ResponseFromFixture("add_dns_record.json"), + servermock.CheckHeader(). + WithContentType("application/x-www-form-urlencoded"), + servermock.CheckForm().Strict(). + With("order-id", "abc"). + With("name", "example.com."). + With("ttl", "120"). + With("type", "TXT"). + With("value", "txtTXTtxt")). + Build(t) + + record := Record{ + Type: "TXT", + Name: "example.com.", + TTL: 120, + Value: "txtTXTtxt", + } + + result, err := client.AddDNSRecord(t.Context(), "abc", record) + require.NoError(t, err) + + expected := &Record{ + Type: "A", + Name: "example.com.", + TTL: 14400, + Value: "203.0.113.10", + } + + assert.Equal(t, expected, result) +} + +func TestClient_AddDNSRecord_error(t *testing.T) { + client := mockBuilder(). + Route("POST /domains/dns-records/add", + servermock.Noop().WithStatusCode(http.StatusBadRequest)). + Build(t) + + record := Record{ + Type: "TXT", + Name: "example.com.", + TTL: 120, + Value: "txtTXTtxt", + } + + _, err := client.AddDNSRecord(t.Context(), "abc", record) + require.EqualError(t, err, "unexpected status code: [status code: 400] body: ") +} + +func TestClient_AddDNSRecord_api_error(t *testing.T) { + client := mockBuilder(). + Route("POST /domains/dns-records/add", + servermock.ResponseFromFixture("error.json")). + Build(t) + + record := Record{ + Type: "TXT", + Name: "example.com.", + TTL: 120, + Value: "txtTXTtxt", + } + + _, err := client.AddDNSRecord(t.Context(), "abc", record) + require.EqualError(t, err, "unexpected status: error: missing required fields (type, name, ttl)") +} + +func TestClient_DeleteDNSRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /domains/dns-records/delete", + servermock.ResponseFromFixture("delete_dns_record.json"), + servermock.CheckHeader(). + WithContentType("application/x-www-form-urlencoded"), + servermock.CheckForm().Strict(). + With("order-id", "abc"). + With("line", "123")). + Build(t) + + result, err := client.DeleteDNSRecord(t.Context(), "abc", 123) + require.NoError(t, err) + + expected := &DeletedRecordInfo{ + Count: 1, + Lines: []int{15}, + } + + assert.Equal(t, expected, result) +} + +func TestClient_DeleteDNSRecord_error(t *testing.T) { + client := mockBuilder(). + Route("POST /domains/dns-records/delete", + servermock.Noop().WithStatusCode(http.StatusBadRequest)). + Build(t) + + _, err := client.DeleteDNSRecord(t.Context(), "abc", 123) + require.EqualError(t, err, "unexpected status code: [status code: 400] body: ") +} + +func TestClient_DeleteDNSRecord_api_error(t *testing.T) { + client := mockBuilder(). + Route("POST /domains/dns-records/delete", + servermock.ResponseFromFixture("error.json")). + Build(t) + + _, err := client.DeleteDNSRecord(t.Context(), "abc", 123) + require.EqualError(t, err, "unexpected status: error: missing required fields (type, name, ttl)") +} diff --git a/providers/dns/octenium/internal/fixtures/add_dns_record.json b/providers/dns/octenium/internal/fixtures/add_dns_record.json new file mode 100644 index 000000000..6c73ea1f9 --- /dev/null +++ b/providers/dns/octenium/internal/fixtures/add_dns_record.json @@ -0,0 +1,14 @@ +{ + "api-status": "success", + "api-response": { + "record": { + "type": "A", + "name": "example.com.", + "ttl": 14400, + "value": "203.0.113.10", + "raw": { + "address": "203.0.113.10" + } + } + } +} diff --git a/providers/dns/octenium/internal/fixtures/delete_dns_record.json b/providers/dns/octenium/internal/fixtures/delete_dns_record.json new file mode 100644 index 000000000..0d4692ffd --- /dev/null +++ b/providers/dns/octenium/internal/fixtures/delete_dns_record.json @@ -0,0 +1,11 @@ +{ + "api-status": "success", + "api-response": { + "deleted": { + "count": 1, + "lines": [ + 15 + ] + } + } +} diff --git a/providers/dns/octenium/internal/fixtures/error.json b/providers/dns/octenium/internal/fixtures/error.json new file mode 100644 index 000000000..85a90e425 --- /dev/null +++ b/providers/dns/octenium/internal/fixtures/error.json @@ -0,0 +1,5 @@ +{ + "api-status": "error", + "api-response": [], + "api-error": "missing required fields (type, name, ttl)" +} diff --git a/providers/dns/octenium/internal/fixtures/list_dns_records.json b/providers/dns/octenium/internal/fixtures/list_dns_records.json new file mode 100644 index 000000000..8fa60d86f --- /dev/null +++ b/providers/dns/octenium/internal/fixtures/list_dns_records.json @@ -0,0 +1,38 @@ +{ + "api-status": "success", + "api-response": { + "records": [ + { + "line": 15, + "type": "A", + "name": "example.com.", + "ttl": 14400, + "value": "203.0.113.10", + "raw": { + "address": "203.0.113.10" + } + }, + { + "line": 22, + "type": "MX", + "name": "example.com.", + "ttl": 14400, + "value": "10 mail.example.com.", + "raw": { + "preference": 10, + "exchange": "mail.example.com." + } + }, + { + "line": 31, + "type": "TXT", + "name": "_dmarc.example.com.", + "ttl": 300, + "value": "v=DMARC1; p=none; rua=mailto:dmarc@example.com", + "raw": { + "txtdata": "v=DMARC1; p=none; rua=mailto:dmarc@example.com" + } + } + ] + } +} diff --git a/providers/dns/octenium/internal/fixtures/list_domains.json b/providers/dns/octenium/internal/fixtures/list_domains.json new file mode 100644 index 000000000..b10b705c9 --- /dev/null +++ b/providers/dns/octenium/internal/fixtures/list_domains.json @@ -0,0 +1,25 @@ +{ + "api-status": "success", + "api-response": { + "domains": { + "2976": { + "domain-name": "example.com", + "registration-date": "12/09/2021", + "expiration-date": "12/09/2024", + "status": "active" + }, + "2977": { + "domain-name": "example.org", + "registration-date": "01/10/2021", + "expiration-date": "01/10/2024", + "status": "active" + }, + "2978": { + "domain-name": "example.net", + "registration-date": "21\/08\/2025", + "expiration-date": "-", + "status": "active" + } + } + } +} diff --git a/providers/dns/octenium/internal/types.go b/providers/dns/octenium/internal/types.go new file mode 100644 index 000000000..a31e40921 --- /dev/null +++ b/providers/dns/octenium/internal/types.go @@ -0,0 +1,45 @@ +package internal + +import "encoding/json" + +type APIResponse struct { + Status string `json:"api-status,omitempty"` + Response json.RawMessage `json:"api-response,omitempty"` + Error string `json:"api-error,omitempty"` +} + +type Domain struct { + DomainName string `json:"domain-name,omitempty"` + RegistrationDate string `json:"registration-date,omitempty"` + ExpirationDate string `json:"expiration-date,omitempty"` + Status string `json:"status,omitempty"` +} + +type Record struct { + ID int `json:"line,omitempty" url:"-"` + Type string `json:"type,omitempty" url:"type,omitempty"` + Name string `json:"name,omitempty" url:"name,omitempty"` + TTL int `json:"ttl,omitempty" url:"ttl,omitempty"` + Value string `json:"value,omitempty" url:"value,omitempty"` +} + +type DomainsResponse struct { + Domains map[string]Domain `json:"domains,omitempty"` +} + +type AddRecordResponse struct { + Record *Record `json:"record,omitempty"` +} + +type ListRecordsResponse struct { + Records []Record `json:"records,omitempty"` +} + +type DeleteRecordResponse struct { + Deleted *DeletedRecordInfo `json:"deleted,omitempty"` +} + +type DeletedRecordInfo struct { + Count int `json:"count,omitempty"` + Lines []int `json:"lines,omitempty"` +} diff --git a/providers/dns/octenium/octenium.go b/providers/dns/octenium/octenium.go new file mode 100644 index 000000000..1ace82bf5 --- /dev/null +++ b/providers/dns/octenium/octenium.go @@ -0,0 +1,198 @@ +// Package octenium implements a DNS provider for solving the DNS-01 challenge using Octenium. +package octenium + +import ( + "context" + "errors" + "fmt" + "net/http" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/log" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/octenium/internal" + "github.com/hashicorp/go-retryablehttp" +) + +// Environment variables names. +const ( + envNamespace = "OCTENIUM_" + + 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 *internal.Client + + domainIDs map[string]string + domainIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for Octenium. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIKey) + if err != nil { + return nil, fmt.Errorf("octenium: %w", err) + } + + config := NewDefaultConfig() + config.APIKey = values[EnvAPIKey] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Octenium. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("octenium: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.APIKey) + if err != nil { + return nil, fmt.Errorf("octenium: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + retryClient := retryablehttp.NewClient() + retryClient.RetryMax = 5 + if config.HTTPClient != nil { + retryClient.HTTPClient = config.HTTPClient + } + retryClient.Logger = log.Logger + + return &DNSProvider{ + config: config, + client: client, + domainIDs: make(map[string]string), + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("octenium: could not find zone for domain '%s': %w", domain, err) + } + + domainID, err := d.getDomainID(ctx, authZone) + if err != nil { + return fmt.Errorf("octenium: get domain ID: %w", err) + } + + d.domainIDsMu.Lock() + d.domainIDs[token] = domainID + d.domainIDsMu.Unlock() + + record := internal.Record{ + Type: "TXT", + Name: info.EffectiveFQDN, + TTL: d.config.TTL, + Value: info.Value, + } + + _, err = d.client.AddDNSRecord(ctx, domainID, record) + if err != nil { + return fmt.Errorf("octenium: add record: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + d.domainIDsMu.Lock() + domainID, ok := d.domainIDs[token] + d.domainIDsMu.Unlock() + if !ok { + return fmt.Errorf("octenium: unknown domain ID for '%s'", info.EffectiveFQDN) + } + + records, err := d.client.ListDNSRecords(ctx, domainID, "TXT") + if err != nil { + return fmt.Errorf("octenium: list records: %w", err) + } + + for _, record := range records { + if record.Type != "TXT" || record.Name != info.EffectiveFQDN || record.Value != info.Value { + continue + } + + _, err = d.client.DeleteDNSRecord(ctx, domainID, record.ID) + if err != nil { + return fmt.Errorf("octenium: delete record: %w", err) + } + + break + } + + 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 +} + +func (d *DNSProvider) getDomainID(ctx context.Context, authZone string) (string, error) { + domains, err := d.client.ListDomains(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return "", fmt.Errorf("list domains: %w", err) + } + + if len(domains) == 0 { + return "", errors.New("domain not found") + } + + if len(domains) > 1 { + return "", errors.New("multiple domains found") + } + + for id := range domains { + return id, nil + } + + return "", errors.New("domain ID not found") +} diff --git a/providers/dns/octenium/octenium.toml b/providers/dns/octenium/octenium.toml new file mode 100644 index 000000000..5084526fd --- /dev/null +++ b/providers/dns/octenium/octenium.toml @@ -0,0 +1,22 @@ +Name = "Octenium" +Description = '''''' +URL = "https://octenium.com/" +Code = "octenium" +Since = "v4.27.0" + +Example = ''' +OCTENIUM_API_KEY="xxx" \ +lego --email you@example.com --dns octenium -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + OCTENIUM_API_KEY = "API key" + [Configuration.Additional] + OCTENIUM_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + OCTENIUM_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + OCTENIUM_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + OCTENIUM_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://octenium.com/api#tag/Domains-DNS" diff --git a/providers/dns/octenium/octenium_test.go b/providers/dns/octenium/octenium_test.go new file mode 100644 index 000000000..c3d0ef558 --- /dev/null +++ b/providers/dns/octenium/octenium_test.go @@ -0,0 +1,195 @@ +package octenium + +import ( + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "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 API key", + envVars: map[string]string{ + EnvAPIKey: "", + }, + expected: "octenium: some credentials information are missing: OCTENIUM_API_KEY", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "octenium: some credentials information are missing: OCTENIUM_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 API key", + expected: "octenium: credentials missing", + }, + { + desc: "missing credentials", + expected: "octenium: 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.APIKey = "secret" + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.HTTPClient = server.Client() + p.client.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckHeader(). + WithAccept("application/json"). + With("X-Api-Key", "secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("GET /domains", + servermock.ResponseFromFixture("list_domains.json"), + servermock.CheckQueryParameter().Strict(). + With("domain-name", "example.com")). + Route("POST /domains/dns-records/add", + servermock.ResponseFromFixture("add_dns_record.json"), + servermock.CheckHeader(). + WithContentType("application/x-www-form-urlencoded"), + servermock.CheckForm().Strict(). + With("order-id", "2976"). + With("name", "_acme-challenge.example.com."). + With("ttl", "120"). + With("type", "TXT"). + With("value", "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI")). + Build(t) + + err := provider.Present("example.com", "", "foobar") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("POST /domains/dns-records/list", + servermock.ResponseFromFixture("list_dns_records.json"), + servermock.CheckHeader(). + WithContentType("application/x-www-form-urlencoded"), + servermock.CheckForm().Strict(). + With("order-id", "2976"). + With("types[]", "TXT")). + Route("POST /domains/dns-records/delete", + servermock.ResponseFromFixture("delete_dns_record.json"), + servermock.CheckHeader(). + WithContentType("application/x-www-form-urlencoded"), + servermock.CheckForm().Strict(). + With("order-id", "2976"). + With("line", "123")). + Build(t) + + provider.domainIDs["token"] = "2976" + + err := provider.CleanUp("example.com", "token", "foobar") + require.NoError(t, err) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index df0c59bf6..4dcf5317c 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -119,6 +119,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/njalla" "github.com/go-acme/lego/v4/providers/dns/nodion" "github.com/go-acme/lego/v4/providers/dns/ns1" + "github.com/go-acme/lego/v4/providers/dns/octenium" "github.com/go-acme/lego/v4/providers/dns/oraclecloud" "github.com/go-acme/lego/v4/providers/dns/otc" "github.com/go-acme/lego/v4/providers/dns/ovh" @@ -400,6 +401,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return nodion.NewDNSProvider() case "ns1": return ns1.NewDNSProvider() + case "octenium": + return octenium.NewDNSProvider() case "oraclecloud": return oraclecloud.NewDNSProvider() case "otc": From 213d7b8fa33cdb84f1cee9f1c4f8babbd7d8bb48 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 13 Oct 2025 12:15:13 +0200 Subject: [PATCH 178/298] chore: wait.For stop with error (#2665) --- platform/wait/wait.go | 3 ++- platform/wait/wait_test.go | 42 +++++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/platform/wait/wait.go b/platform/wait/wait.go index 6ad817b26..a1fa80903 100644 --- a/platform/wait/wait.go +++ b/platform/wait/wait.go @@ -25,8 +25,9 @@ func For(msg string, timeout, interval time.Duration, f func() (bool, error)) er stop, err := f() if stop { - return nil + return err } + if err != nil { lastErr = err } diff --git a/platform/wait/wait_test.go b/platform/wait/wait_test.go index 9722e6f2e..9d1a4ac34 100644 --- a/platform/wait/wait_test.go +++ b/platform/wait/wait_test.go @@ -1,11 +1,12 @@ package wait import ( + "errors" "testing" "time" ) -func TestForTimeout(t *testing.T) { +func TestFor_timeout(t *testing.T) { c := make(chan error) go func() { c <- For("", 3*time.Second, 1*time.Second, func() (bool, error) { @@ -24,3 +25,42 @@ func TestForTimeout(t *testing.T) { t.Logf("%v", err) } } + +func TestFor_stop(t *testing.T) { + c := make(chan error) + go func() { + c <- For("", 3*time.Second, 1*time.Second, func() (bool, error) { + return true, nil + }) + }() + + timeout := time.After(6 * time.Second) + select { + case <-timeout: + t.Fatal("timeout exceeded") + case err := <-c: + if err != nil { + t.Errorf("expected no timeout error; got %v", err) + } + } +} + +func TestFor_stop_error(t *testing.T) { + c := make(chan error) + go func() { + c <- For("", 3*time.Second, 1*time.Second, func() (bool, error) { + return true, errors.New("oops") + }) + }() + + timeout := time.After(6 * time.Second) + select { + case <-timeout: + t.Fatal("timeout exceeded") + case err := <-c: + if err == nil { + t.Errorf("expected error; got %v", err) + } + t.Logf("%v", err) + } +} From f0c314c3ef0ed3e0f213ae63c699384e441f8132 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 14 Oct 2025 21:56:06 +0200 Subject: [PATCH 179/298] hetzner: update to new API (#2663) --- cmd/zz_gen_cmd_dnshelp.go | 6 +- docs/content/dns/zz_gen_hetzner.md | 10 +- docs/content/dns/zz_gen_hetznerv1.md | 67 +++++ providers/dns/hetzner/hetzner.go | 179 ++++++-------- providers/dns/hetzner/hetzner.toml | 10 +- providers/dns/hetzner/hetzner_test.go | 114 ++++----- .../fixtures/add_rrset_records-request.json | 8 + .../hetznerv1/fixtures/add_rrset_records.json | 17 ++ .../hetznerv1/fixtures/get_action_error.json | 20 ++ .../fixtures/get_action_running.json | 16 ++ .../fixtures/get_action_success.json | 16 ++ .../remove_rrset_records-request.json | 7 + .../fixtures/remove_rrset_records.json | 17 ++ .../hetzner/internal/hetznerv1/hetznerv1.go | 201 +++++++++++++++ .../internal/hetznerv1/hetznerv1_test.go | 229 ++++++++++++++++++ .../internal/hetznerv1/internal/client.go | 179 ++++++++++++++ .../hetznerv1/internal/client_test.go | 154 ++++++++++++ .../fixtures/add_rrset_records-request.json | 9 + .../internal/fixtures/add_rrset_records.json | 17 ++ .../error-deprecated_api_endpoint.json | 9 + .../fixtures/error-invalid_input.json | 16 ++ .../error-resource_limit_exceeded.json | 13 + .../internal/fixtures/get_action.json | 20 ++ .../remove_rrset_records-request.json | 8 + .../fixtures/remove_rrset_records.json | 17 ++ .../internal/hetznerv1/internal/types.go | 96 ++++++++ .../dns/hetzner/internal/legacy/hetzner.go | 174 +++++++++++++ .../hetzner/internal/legacy/hetzner_test.go | 125 ++++++++++ .../internal/{ => legacy/internal}/client.go | 0 .../{ => legacy/internal}/client_test.go | 0 .../fixtures/create_txt_record-request.json | 0 .../internal}/fixtures/create_txt_record.json | 0 .../internal}/fixtures/get_txt_record.json | 0 .../internal}/fixtures/get_zone_id.json | 0 .../internal/{ => legacy/internal}/types.go | 0 35 files changed, 1586 insertions(+), 168 deletions(-) create mode 100644 docs/content/dns/zz_gen_hetznerv1.md create mode 100644 providers/dns/hetzner/internal/hetznerv1/fixtures/add_rrset_records-request.json create mode 100644 providers/dns/hetzner/internal/hetznerv1/fixtures/add_rrset_records.json create mode 100644 providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_error.json create mode 100644 providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_running.json create mode 100644 providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_success.json create mode 100644 providers/dns/hetzner/internal/hetznerv1/fixtures/remove_rrset_records-request.json create mode 100644 providers/dns/hetzner/internal/hetznerv1/fixtures/remove_rrset_records.json create mode 100644 providers/dns/hetzner/internal/hetznerv1/hetznerv1.go create mode 100644 providers/dns/hetzner/internal/hetznerv1/hetznerv1_test.go create mode 100644 providers/dns/hetzner/internal/hetznerv1/internal/client.go create mode 100644 providers/dns/hetzner/internal/hetznerv1/internal/client_test.go create mode 100644 providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records-request.json create mode 100644 providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records.json create mode 100644 providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-deprecated_api_endpoint.json create mode 100644 providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-invalid_input.json create mode 100644 providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-resource_limit_exceeded.json create mode 100644 providers/dns/hetzner/internal/hetznerv1/internal/fixtures/get_action.json create mode 100644 providers/dns/hetzner/internal/hetznerv1/internal/fixtures/remove_rrset_records-request.json create mode 100644 providers/dns/hetzner/internal/hetznerv1/internal/fixtures/remove_rrset_records.json create mode 100644 providers/dns/hetzner/internal/hetznerv1/internal/types.go create mode 100644 providers/dns/hetzner/internal/legacy/hetzner.go create mode 100644 providers/dns/hetzner/internal/legacy/hetzner_test.go rename providers/dns/hetzner/internal/{ => legacy/internal}/client.go (100%) rename providers/dns/hetzner/internal/{ => legacy/internal}/client_test.go (100%) rename providers/dns/hetzner/internal/{ => legacy/internal}/fixtures/create_txt_record-request.json (100%) rename providers/dns/hetzner/internal/{ => legacy/internal}/fixtures/create_txt_record.json (100%) rename providers/dns/hetzner/internal/{ => legacy/internal}/fixtures/get_txt_record.json (100%) rename providers/dns/hetzner/internal/{ => legacy/internal}/fixtures/get_zone_id.json (100%) rename providers/dns/hetzner/internal/{ => legacy/internal}/types.go (100%) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 92f7b5a07..2c7c1acc0 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -1550,14 +1550,14 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Credentials:`) - ew.writeln(` - "HETZNER_API_KEY": API key`) + ew.writeln(` - "HETZNER_API_TOKEN": API token`) ew.writeln() ew.writeln(`Additional Configuration:`) ew.writeln(` - "HETZNER_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "HETZNER_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "HETZNER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) - ew.writeln(` - "HETZNER_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) + ew.writeln(` - "HETZNER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "HETZNER_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/hetzner`) diff --git a/docs/content/dns/zz_gen_hetzner.md b/docs/content/dns/zz_gen_hetzner.md index a42175e8d..5778a64ce 100644 --- a/docs/content/dns/zz_gen_hetzner.md +++ b/docs/content/dns/zz_gen_hetzner.md @@ -26,7 +26,7 @@ Configuration for [Hetzner](https://hetzner.com). Here is an example bash command using the Hetzner provider: ```bash -HETZNER_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ +HETZNER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ lego --email you@example.com --dns hetzner -d '*.example.com' -d example.com run ``` @@ -37,7 +37,7 @@ lego --email you@example.com --dns hetzner -d '*.example.com' -d example.com run | Environment Variable Name | Description | |-----------------------|-------------| -| `HETZNER_API_KEY` | API key | +| `HETZNER_API_TOKEN` | API token | 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" %}}). @@ -49,8 +49,8 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). |--------------------------------|-------------| | `HETZNER_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `HETZNER_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `HETZNER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | -| `HETZNER_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | +| `HETZNER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `HETZNER_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | 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" %}}). @@ -60,7 +60,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). ## More information -- [API documentation](https://dns.hetzner.com/api-docs) +- [API documentation](https://docs.hetzner.cloud/reference/cloud#dns) diff --git a/docs/content/dns/zz_gen_hetznerv1.md b/docs/content/dns/zz_gen_hetznerv1.md new file mode 100644 index 000000000..737004fe4 --- /dev/null +++ b/docs/content/dns/zz_gen_hetznerv1.md @@ -0,0 +1,67 @@ +--- +title: "Hetzner" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: hetznerv1 +dnsprovider: + since: "v4.27.0" + code: "hetznerv1" + url: "https://hetzner.com" +--- + + + + + + +Configuration for [Hetzner](https://hetzner.com). + + + + +- Code: `hetznerv1` +- Since: v4.27.0 + + +Here is an example bash command using the Hetzner provider: + +```bash +HETZNER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns hetznerv1 -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `HETZNER_API_TOKEN` | API token | + +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 | +|--------------------------------|-------------| +| `HETZNER_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `HETZNER_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `HETZNER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `HETZNER_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://docs.hetzner.cloud/reference/cloud#dns) + + + + diff --git a/providers/dns/hetzner/hetzner.go b/providers/dns/hetzner/hetzner.go index 4dcd8e071..4d74a29cd 100644 --- a/providers/dns/hetzner/hetzner.go +++ b/providers/dns/hetzner/hetzner.go @@ -2,28 +2,28 @@ package hetzner import ( - "context" "errors" - "fmt" "net/http" + "os" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/hetzner/internal" + "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/hetznerv1" + "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/legacy" ) // Environment variables names. const ( - envNamespace = "HETZNER_" + // Deprecated: use EnvAPIToken instead. + EnvAPIKey = legacy.EnvAPIKey + EnvAPIToken = hetznerv1.EnvAPIToken - EnvAPIKey = envNamespace + "API_KEY" - - EnvTTL = envNamespace + "TTL" - EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" - EnvPollingInterval = envNamespace + "POLLING_INTERVAL" - EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" + EnvTTL = hetznerv1.EnvTTL + EnvPropagationTimeout = hetznerv1.EnvPropagationTimeout + EnvPollingInterval = hetznerv1.EnvPollingInterval + EnvHTTPTimeout = hetznerv1.EnvHTTPTimeout ) const minTTL = 60 @@ -32,7 +32,11 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - APIKey string + // Deprecated: use APIToken instead + APIKey string + + APIToken string + PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -53,22 +57,40 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *internal.Client + provider challenge.ProviderTimeout } // NewDNSProvider returns a DNSProvider instance configured for hetzner. // Credentials must be passed in the environment variable: HETZNER_API_KEY. func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIKey) - if err != nil { - return nil, fmt.Errorf("hetzner: %w", err) + _, foundAPIToken := os.LookupEnv(EnvAPIToken) + _, foundAPIKey := os.LookupEnv(EnvAPIKey) + + switch { + case foundAPIToken: + provider, err := hetznerv1.NewDNSProvider() + if err != nil { + return nil, err + } + + return &DNSProvider{provider: provider}, nil + + case foundAPIKey: + provider, err := legacy.NewDNSProvider() + if err != nil { + return nil, err + } + + return &DNSProvider{provider: provider}, nil + + default: + provider, err := hetznerv1.NewDNSProvider() + if err != nil { + return nil, err + } + + return &DNSProvider{provider: provider}, nil } - - config := NewDefaultConfig() - config.APIKey = values[EnvAPIKey] - - return NewDNSProviderConfig(config) } // NewDNSProviderConfig return a DNSProvider instance configured for hetzner. @@ -77,98 +99,55 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("hetzner: the configuration of the DNS provider is nil") } - if config.APIKey == "" { - return nil, errors.New("hetzner: credentials missing") + switch { + case config.APIToken != "": + cfg := &hetznerv1.Config{ + APIToken: config.APIToken, + PropagationTimeout: config.PropagationTimeout, + PollingInterval: config.PollingInterval, + TTL: config.TTL, + HTTPClient: config.HTTPClient, + } + + provider, err := hetznerv1.NewDNSProviderConfig(cfg) + if err != nil { + return nil, err + } + + return &DNSProvider{provider: provider}, nil + + case config.APIKey != "": + cfg := &legacy.Config{ + APIKey: config.APIKey, + PropagationTimeout: config.PropagationTimeout, + PollingInterval: config.PollingInterval, + TTL: config.TTL, + HTTPClient: config.HTTPClient, + } + + provider, err := legacy.NewDNSProviderConfig(cfg) + if err != nil { + return nil, err + } + + return &DNSProvider{provider: provider}, nil } - if config.TTL < minTTL { - return nil, fmt.Errorf("hetzner: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) - } - - client := internal.NewClient(config.APIKey) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - return &DNSProvider{config: config, client: client}, nil + return nil, errors.New("hetzner: credentials missing") } // 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 + return d.provider.Timeout() } // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("hetzner: could not find zone for domain %q: %w", domain, err) - } - - zone := dns01.UnFqdn(authZone) - - ctx := context.Background() - - zoneID, err := d.client.GetZoneID(ctx, zone) - if err != nil { - return fmt.Errorf("hetzner: %w", err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone) - if err != nil { - return fmt.Errorf("hetzner: %w", err) - } - - record := internal.DNSRecord{ - Type: "TXT", - Name: subDomain, - Value: info.Value, - TTL: d.config.TTL, - ZoneID: zoneID, - } - - if err := d.client.CreateRecord(ctx, record); err != nil { - return fmt.Errorf("hetzner: failed to add TXT record: fqdn=%s, zoneID=%s: %w", info.EffectiveFQDN, zoneID, err) - } - - return nil + return d.provider.Present(domain, token, keyAuth) } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("hetzner: could not find zone for domain %q: %w", domain, err) - } - - zone := dns01.UnFqdn(authZone) - - ctx := context.Background() - - zoneID, err := d.client.GetZoneID(ctx, zone) - if err != nil { - return fmt.Errorf("hetzner: %w", err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone) - if err != nil { - return fmt.Errorf("hetzner: %w", err) - } - - record, err := d.client.GetTxtRecord(ctx, subDomain, info.Value, zoneID) - if err != nil { - return fmt.Errorf("hetzner: %w", err) - } - - if err := d.client.DeleteRecord(ctx, record.ID); err != nil { - return fmt.Errorf("hetzner: failed to delete TXT record: id=%s, name=%s: %w", record.ID, record.Name, err) - } - - return nil + return d.provider.CleanUp(domain, token, keyAuth) } diff --git a/providers/dns/hetzner/hetzner.toml b/providers/dns/hetzner/hetzner.toml index 033ae2d2f..ee1f9a970 100644 --- a/providers/dns/hetzner/hetzner.toml +++ b/providers/dns/hetzner/hetzner.toml @@ -5,18 +5,18 @@ Code = "hetzner" Since = "v3.7.0" Example = ''' -HETZNER_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ +HETZNER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ lego --email you@example.com --dns hetzner -d '*.example.com' -d example.com run ''' [Configuration] [Configuration.Credentials] - HETZNER_API_KEY = "API key" + HETZNER_API_TOKEN = "API token" [Configuration.Additional] HETZNER_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - HETZNER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" - HETZNER_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + HETZNER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + HETZNER_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" HETZNER_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] - API = "https://dns.hetzner.com/api-docs" + API = "https://docs.hetzner.cloud/reference/cloud#dns" diff --git a/providers/dns/hetzner/hetzner_test.go b/providers/dns/hetzner/hetzner_test.go index d028fd06b..7f59e6323 100644 --- a/providers/dns/hetzner/hetzner_test.go +++ b/providers/dns/hetzner/hetzner_test.go @@ -3,34 +3,53 @@ package hetzner import ( "testing" + "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/hetznerv1" + "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/legacy" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -const envDomain = envNamespace + "DOMAIN" - -var envTest = tester.NewEnvTest( - EnvAPIKey). - WithDomain(envDomain) +var envTest = tester.NewEnvTest(EnvAPIKey, EnvAPIToken) func TestNewDNSProvider(t *testing.T) { testCases := []struct { - desc string - envVars map[string]string - expected string + desc string + envVars map[string]string + + expectedProvider challenge.ProviderTimeout + expectedError string }{ { - desc: "success", + desc: "success (v1)", + envVars: map[string]string{ + EnvAPIToken: "123", + }, + expectedProvider: &hetznerv1.DNSProvider{}, + }, + { + desc: "success (legacy)", envVars: map[string]string{ EnvAPIKey: "123", }, + expectedProvider: &legacy.DNSProvider{}, + }, + { + desc: "success (both)", + envVars: map[string]string{ + EnvAPIKey: "123", + EnvAPIToken: "123", + }, + expectedProvider: &hetznerv1.DNSProvider{}, }, { desc: "missing credentials", envVars: map[string]string{ - EnvAPIKey: "", + EnvAPIKey: "", + EnvAPIToken: "", }, - expected: "hetzner: some credentials information are missing: HETZNER_API_KEY", + expectedError: "hetzner: some credentials information are missing: HETZNER_API_TOKEN", }, } @@ -43,12 +62,12 @@ func TestNewDNSProvider(t *testing.T) { p, err := NewDNSProvider() - if test.expected == "" { + if test.expectedError == "" { require.NoError(t, err) + assert.IsType(t, test.expectedProvider, p.provider) require.NotNil(t, p) - require.NotNil(t, p.config) } else { - require.EqualError(t, err, test.expected) + require.EqualError(t, err, test.expectedError) } }) } @@ -58,68 +77,53 @@ func TestNewDNSProviderConfig(t *testing.T) { testCases := []struct { desc string apiKey string + apiToken string ttl int - expected string + + expectedProvider challenge.ProviderTimeout + expectedError string }{ { - desc: "success", - ttl: minTTL, - apiKey: "123", + desc: "success (v1)", + ttl: minTTL, + apiToken: "123", + expectedProvider: &hetznerv1.DNSProvider{}, }, { - desc: "missing credentials", - ttl: minTTL, - expected: "hetzner: credentials missing", + desc: "success (legacy)", + ttl: minTTL, + apiKey: "456", + expectedProvider: &legacy.DNSProvider{}, }, { - desc: "invalid TTL", - apiKey: "123", - ttl: 10, - expected: "hetzner: invalid TTL, TTL (10) must be greater than 60", + desc: "success (both)", + ttl: minTTL, + apiToken: "123", + apiKey: "456", + expectedProvider: &hetznerv1.DNSProvider{}, + }, + { + desc: "missing credentials", + ttl: minTTL, + expectedError: "hetzner: credentials missing", }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { config := NewDefaultConfig() + config.APIToken = test.apiToken config.APIKey = test.apiKey config.TTL = test.ttl p, err := NewDNSProviderConfig(config) - if test.expected == "" { + if test.expectedError == "" { require.NoError(t, err) - require.NotNil(t, p) - require.NotNil(t, p.config) + assert.IsType(t, test.expectedProvider, p.provider) } else { - require.EqualError(t, err, test.expected) + require.EqualError(t, err, test.expectedError) } }) } } - -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/hetzner/internal/hetznerv1/fixtures/add_rrset_records-request.json b/providers/dns/hetzner/internal/hetznerv1/fixtures/add_rrset_records-request.json new file mode 100644 index 000000000..210f84435 --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/fixtures/add_rrset_records-request.json @@ -0,0 +1,8 @@ +{ + "ttl": 120, + "records": [ + { + "value": "\"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI\"" + } + ] +} diff --git a/providers/dns/hetzner/internal/hetznerv1/fixtures/add_rrset_records.json b/providers/dns/hetzner/internal/hetznerv1/fixtures/add_rrset_records.json new file mode 100644 index 000000000..2341c7e6e --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/fixtures/add_rrset_records.json @@ -0,0 +1,17 @@ +{ + "action": { + "id": 1, + "command": "add_rrset_records", + "status": "running", + "progress": 50, + "started": "2016-01-30T23:55:00+00:00", + "finished": null, + "resources": [ + { + "id": 42, + "type": "zone" + } + ], + "error": null + } +} diff --git a/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_error.json b/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_error.json new file mode 100644 index 000000000..2a4472f67 --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_error.json @@ -0,0 +1,20 @@ +{ + "action": { + "id": 1, + "command": "remove_rrset_records", + "status": "error", + "started": "2016-01-30T23:55:00+00:00", + "finished": "2016-01-30T23:55:00+00:00", + "progress": 50, + "resources": [ + { + "id": 42, + "type": "zone" + } + ], + "error": { + "code": "action_failed", + "message": "Action failed" + } + } +} diff --git a/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_running.json b/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_running.json new file mode 100644 index 000000000..dcec6c2cd --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_running.json @@ -0,0 +1,16 @@ +{ + "action": { + "id": 1, + "command": "remove_rrset_records", + "status": "running", + "started": "2016-01-30T23:55:00+00:00", + "finished": "2016-01-30T23:55:00+00:00", + "progress": 50, + "resources": [ + { + "id": 42, + "type": "zone" + } + ] + } +} diff --git a/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_success.json b/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_success.json new file mode 100644 index 000000000..6b7267c07 --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/fixtures/get_action_success.json @@ -0,0 +1,16 @@ +{ + "action": { + "id": 1, + "command": "remove_rrset_records", + "status": "success", + "started": "2016-01-30T23:55:00+00:00", + "finished": "2016-01-30T23:55:00+00:00", + "progress": 100, + "resources": [ + { + "id": 42, + "type": "zone" + } + ] + } +} diff --git a/providers/dns/hetzner/internal/hetznerv1/fixtures/remove_rrset_records-request.json b/providers/dns/hetzner/internal/hetznerv1/fixtures/remove_rrset_records-request.json new file mode 100644 index 000000000..982273b67 --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/fixtures/remove_rrset_records-request.json @@ -0,0 +1,7 @@ +{ + "records": [ + { + "value": "\"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI\"" + } + ] +} diff --git a/providers/dns/hetzner/internal/hetznerv1/fixtures/remove_rrset_records.json b/providers/dns/hetzner/internal/hetznerv1/fixtures/remove_rrset_records.json new file mode 100644 index 000000000..1b10dfd5e --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/fixtures/remove_rrset_records.json @@ -0,0 +1,17 @@ +{ + "action": { + "id": 1, + "command": "remove_rrset_records", + "status": "running", + "progress": 50, + "started": "2016-01-30T23:55:00+00:00", + "finished": null, + "resources": [ + { + "id": 42, + "type": "zone" + } + ], + "error": null + } +} diff --git a/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go b/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go new file mode 100644 index 000000000..603177a0d --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go @@ -0,0 +1,201 @@ +// Package hetznerv1 implements a DNS provider for solving the DNS-01 challenge using Hetzner. +package hetznerv1 + +import ( + "context" + "errors" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/platform/wait" + "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/hetznerv1/internal" + "golang.org/x/net/idna" +) + +// Environment variables names. +const ( + envNamespace = "HETZNER_" + + EnvAPIToken = envNamespace + "API_TOKEN" + + 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 { + APIToken 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for Hetzner. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIToken) + if err != nil { + return nil, fmt.Errorf("hetzner: %w", err) + } + + config := NewDefaultConfig() + config.APIToken = values[EnvAPIToken] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Hetzner. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("hetzner: the configuration of the DNS provider is nil") + } + + if config.APIToken == "" { + return nil, errors.New("hetzner: credentials missing") + } + + client, err := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.APIToken)) + if err != nil { + return nil, fmt.Errorf("hetzner: %w", err) + } + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("hetzner: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("hetzner: %w", err) + } + + subDomainPunnycoded, err := idna.ToASCII(dns01.UnFqdn(subDomain)) + if err != nil { + return fmt.Errorf("hetzner: %w", err) + } + + zone, err := idna.ToASCII(dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("hetzner: %w", err) + } + + records := []internal.Record{{Value: strconv.Quote(info.Value)}} + + action, err := d.client.AddRRSetRecords(ctx, zone, "TXT", subDomainPunnycoded, d.config.TTL, records) + if err != nil { + return fmt.Errorf("hetzner: add RRSet records: %w", err) + } + + err = d.waitAction(ctx, "action: add RRSet records", action.ID) + if err != nil { + return fmt.Errorf("hetzner: wait (add): %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("hetzner: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("hetzner: %w", err) + } + + subDomainPunnycoded, err := idna.ToASCII(dns01.UnFqdn(subDomain)) + if err != nil { + return fmt.Errorf("hetzner: %w", err) + } + + zone, err := idna.ToASCII(dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("hetzner: %w", err) + } + + records := []internal.Record{{Value: strconv.Quote(info.Value)}} + + action, err := d.client.RemoveRRSetRecords(ctx, zone, "TXT", subDomainPunnycoded, records) + if err != nil { + return fmt.Errorf("hetzner: remove RRSet records: %w", err) + } + + err = d.waitAction(ctx, "action: remove RRSet records", action.ID) + if err != nil { + return fmt.Errorf("hetzner: wait (remove): %w", err) + } + + 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 +} + +func (d *DNSProvider) waitAction(ctx context.Context, msg string, actionID int) error { + return wait.For(msg, d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) { + result, err := d.client.GetAction(ctx, actionID) + if err != nil { + return false, fmt.Errorf("get action %d: %w", actionID, err) + } + + switch result.Status { + case internal.StatusRunning: + return false, fmt.Errorf("action %d is %s", actionID, internal.StatusRunning) + + case internal.StatusSuccess: + return true, nil + + case internal.StatusError: + return true, fmt.Errorf("action %d: %s: %w", actionID, internal.StatusError, result.ErrorInfo) + } + + return true, nil + }) +} diff --git a/providers/dns/hetzner/internal/hetznerv1/hetznerv1_test.go b/providers/dns/hetzner/internal/hetznerv1/hetznerv1_test.go new file mode 100644 index 000000000..597907e08 --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/hetznerv1_test.go @@ -0,0 +1,229 @@ +package hetznerv1 + +import ( + "net/http/httptest" + "net/url" + "testing" + "time" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvAPIToken).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvAPIToken: "secret", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "hetzner: some credentials information are missing: HETZNER_API_TOKEN", + }, + } + + 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 + apiToken string + expected string + }{ + { + desc: "success", + apiToken: "secret", + }, + { + desc: "missing credentials", + expected: "hetzner: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIToken = test.apiToken + + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.APIToken = "secret" + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + WithAuthorization("Bearer secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("POST /zones/example.com/rrsets/_acme-challenge/TXT/actions/add_records", + servermock.ResponseFromFixture("add_rrset_records.json"), + servermock.CheckRequestJSONBodyFromFixture("add_rrset_records-request.json")). + Route("GET /actions/1", + servermock.ResponseFromFixture("get_action_success.json")). + Build(t) + + err := provider.Present("example.com", "", "foobar") + require.NoError(t, err) +} + +func TestDNSProvider_Present_error(t *testing.T) { + provider := mockBuilder(). + Route("POST /zones/example.com/rrsets/_acme-challenge/TXT/actions/add_records", + servermock.ResponseFromFixture("add_rrset_records.json"), + servermock.CheckRequestJSONBodyFromFixture("add_rrset_records-request.json")). + Route("GET /actions/1", + servermock.ResponseFromFixture("get_action_error.json")). + Build(t) + + provider.config.PollingInterval = 20 * time.Millisecond + provider.config.PropagationTimeout = 1 * time.Second + + err := provider.Present("example.com", "", "foobar") + require.EqualError(t, err, "hetzner: wait (add): action 1: error: action_failed: Action failed") +} + +func TestDNSProvider_Present_running(t *testing.T) { + provider := mockBuilder(). + Route("POST /zones/example.com/rrsets/_acme-challenge/TXT/actions/add_records", + servermock.ResponseFromFixture("add_rrset_records.json"), + servermock.CheckRequestJSONBodyFromFixture("add_rrset_records-request.json")). + Route("GET /actions/1", + servermock.ResponseFromFixture("get_action_running.json")). + Build(t) + + provider.config.PollingInterval = 20 * time.Millisecond + provider.config.PropagationTimeout = 1 * time.Second + + err := provider.Present("example.com", "", "foobar") + require.EqualError(t, err, "hetzner: wait (add): action: add RRSet records: time limit exceeded: last error: action 1 is running") +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("POST /zones/example.com/rrsets/_acme-challenge/TXT/actions/remove_records", + servermock.ResponseFromFixture("remove_rrset_records.json"), + servermock.CheckRequestJSONBodyFromFixture("remove_rrset_records-request.json")). + Route("GET /actions/1", + servermock.ResponseFromFixture("get_action_success.json")). + Build(t) + + err := provider.CleanUp("example.com", "", "foobar") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp_error(t *testing.T) { + provider := mockBuilder(). + Route("POST /zones/example.com/rrsets/_acme-challenge/TXT/actions/remove_records", + servermock.ResponseFromFixture("remove_rrset_records.json"), + servermock.CheckRequestJSONBodyFromFixture("remove_rrset_records-request.json")). + Route("GET /actions/1", + servermock.ResponseFromFixture("get_action_error.json")). + Build(t) + + provider.config.PollingInterval = 20 * time.Millisecond + provider.config.PropagationTimeout = 1 * time.Second + + err := provider.CleanUp("example.com", "", "foobar") + require.EqualError(t, err, "hetzner: wait (remove): action 1: error: action_failed: Action failed") +} + +func TestDNSProvider_CleanUp_running(t *testing.T) { + provider := mockBuilder(). + Route("POST /zones/example.com/rrsets/_acme-challenge/TXT/actions/remove_records", + servermock.ResponseFromFixture("remove_rrset_records.json"), + servermock.CheckRequestJSONBodyFromFixture("remove_rrset_records-request.json")). + Route("GET /actions/1", + servermock.ResponseFromFixture("get_action_running.json")). + Build(t) + + provider.config.PollingInterval = 20 * time.Millisecond + provider.config.PropagationTimeout = 1 * time.Second + + err := provider.CleanUp("example.com", "", "foobar") + require.EqualError(t, err, "hetzner: wait (remove): action: remove RRSet records: time limit exceeded: last error: action 1 is running") +} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/client.go b/providers/dns/hetzner/internal/hetznerv1/internal/client.go new file mode 100644 index 000000000..2f87eb6a5 --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/internal/client.go @@ -0,0 +1,179 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "golang.org/x/oauth2" +) + +const defaultBaseURL = "https://api.hetzner.cloud/v1" + +const ( + StatusRunning = "running" + StatusSuccess = "success" + StatusError = "error" +) + +// Client the Hetzner API client. +type Client struct { + BaseURL *url.URL + httpClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(hc *http.Client) (*Client, error) { + baseURL, _ := url.Parse(defaultBaseURL) + + if hc == nil { + hc = &http.Client{Timeout: 10 * time.Second} + } + + return &Client{ + BaseURL: baseURL, + httpClient: hc, + }, nil +} + +// AddRRSetRecords adds records to an RRSet. +// https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-add-records-to-an-rrset +func (c *Client) AddRRSetRecords(ctx context.Context, zoneIDName, recordType, recordName string, ttl int, records []Record) (*Action, error) { + endpoint := c.BaseURL.JoinPath("zones", zoneIDName, "rrsets", recordName, recordType, "actions", "add_records") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, RRSet{TTL: ttl, Records: records}) + if err != nil { + return nil, err + } + + var result ActionResponse + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result.Action, nil +} + +// RemoveRRSetRecords removes records from an RRSet. +// https://docs.hetzner.cloud/reference/cloud#zone-rrset-actions-remove-records-from-an-rrset +func (c *Client) RemoveRRSetRecords(ctx context.Context, zoneIDName, recordType, recordName string, records []Record) (*Action, error) { + endpoint := c.BaseURL.JoinPath("zones", zoneIDName, "rrsets", recordName, recordType, "actions", "remove_records") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, RRSet{Records: records}) + if err != nil { + return nil, err + } + + var result ActionResponse + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result.Action, nil +} + +// GetAction gets an action. +// https://docs.hetzner.cloud/reference/cloud#actions-get-an-action +func (c *Client) GetAction(ctx context.Context, id int) (*Action, error) { + endpoint := c.BaseURL.JoinPath("actions", strconv.Itoa(id)) + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var result ActionResponse + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result.Action, nil +} + +func (c *Client) do(req *http.Request, result any) error { + resp, err := c.httpClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} + +func OAuthStaticAccessToken(client *http.Client, accessToken string) *http.Client { + if client == nil { + client = &http.Client{Timeout: 5 * time.Second} + } + + client.Transport = &oauth2.Transport{ + Source: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: accessToken}), + Base: client.Transport, + } + + return client +} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/client_test.go b/providers/dns/hetzner/internal/hetznerv1/internal/client_test.go new file mode 100644 index 000000000..fcbc7636f --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/internal/client_test.go @@ -0,0 +1,154 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(OAuthStaticAccessToken(server.Client(), "secret")) + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + WithAuthorization("Bearer secret"), + ) +} + +func TestClient_AddRRSetRecords(t *testing.T) { + client := mockBuilder(). + Route("POST /zones/example.com/rrsets/www/TXT/actions/add_records", + servermock.ResponseFromFixture("add_rrset_records.json"), + servermock.CheckRequestJSONBodyFromFixture("add_rrset_records-request.json")). + Build(t) + + records := []Record{{ + Value: "198.51.100.1", + Comment: "My web server at Hetzner Cloud.", + }} + + result, err := client.AddRRSetRecords(t.Context(), "example.com", "TXT", "www", 3600, records) + require.NoError(t, err) + + expected := &Action{ + ID: 1, + Command: "add_rrset_records", + Status: "running", + Progress: 50, + Resources: []Resources{{ID: 42, Type: "zone"}}, + } + + assert.Equal(t, expected, result) +} + +func TestClient_AddRRSetRecords_error_invalid_input(t *testing.T) { + client := mockBuilder(). + Route("POST /zones/example.com/rrsets/www/TXT/actions/add_records", + servermock.ResponseFromFixture("error-invalid_input.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) + + records := []Record{{ + Value: "198.51.100.1", + Comment: "My web server at Hetzner Cloud.", + }} + + _, err := client.AddRRSetRecords(t.Context(), "example.com", "TXT", "www", 0, records) + require.EqualError(t, err, "invalid_input: invalid input in field 'broken_field': is too longfield: broken_field: is too long") +} + +func TestClient_AddRRSetRecords_error_resource_limit_exceeded(t *testing.T) { + client := mockBuilder(). + Route("POST /zones/example.com/rrsets/www/TXT/actions/add_records", + servermock.ResponseFromFixture("error-resource_limit_exceeded.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) + + records := []Record{{ + Value: "198.51.100.1", + Comment: "My web server at Hetzner Cloud.", + }} + + _, err := client.AddRRSetRecords(t.Context(), "example.com", "TXT", "www", 0, records) + require.EqualError(t, err, "resource_limit_exceeded: project limit exceededlimit: project_limit") +} + +func TestClient_AddRRSetRecords_error_deprecated_api_endpoint(t *testing.T) { + client := mockBuilder(). + Route("POST /zones/example.com/rrsets/www/TXT/actions/add_records", + servermock.ResponseFromFixture("error-deprecated_api_endpoint.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) + + records := []Record{{ + Value: "198.51.100.1", + Comment: "My web server at Hetzner Cloud.", + }} + + _, err := client.AddRRSetRecords(t.Context(), "example.com", "TXT", "www", 0, records) + require.EqualError(t, err, "deprecated_api_endpoint: API functionality was removed: https://docs.hetzner.cloud/changelog#2023-07-20-foo-endpoint-is-deprecated") +} + +func TestClient_RemoveRRSetRecords(t *testing.T) { + client := mockBuilder(). + Route("POST /zones/example.com/rrsets/www/TXT/actions/remove_records", + servermock.ResponseFromFixture("remove_rrset_records.json"), + servermock.CheckRequestJSONBodyFromFixture("remove_rrset_records-request.json")). + Build(t) + + records := []Record{{ + Value: "198.51.100.1", + Comment: "My web server at Hetzner Cloud.", + }} + + result, err := client.RemoveRRSetRecords(t.Context(), "example.com", "TXT", "www", records) + require.NoError(t, err) + + expected := &Action{ + ID: 1, + Command: "remove_rrset_records", + Status: "running", + Progress: 50, + Resources: []Resources{{ID: 42, Type: "zone"}}, + } + + assert.Equal(t, expected, result) +} + +func TestClient_GetAction(t *testing.T) { + client := mockBuilder(). + Route("GET /actions/123", servermock.ResponseFromFixture("get_action.json")). + Route("/", servermock.DumpRequest()). + Build(t) + + result, err := client.GetAction(t.Context(), 123) + require.NoError(t, err) + + expected := &Action{ + ID: 42, + Command: "start_resource", + Status: "running", + Progress: 100, + Resources: []Resources{{ID: 42, Type: "server"}}, + ErrorInfo: &ErrorInfo{ + Code: "action_failed", + Message: "Action failed", + }, + } + + assert.Equal(t, expected, result) +} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records-request.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records-request.json new file mode 100644 index 000000000..cba0f34d3 --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records-request.json @@ -0,0 +1,9 @@ +{ + "ttl": 3600, + "records": [ + { + "value": "198.51.100.1", + "comment": "My web server at Hetzner Cloud." + } + ] +} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records.json new file mode 100644 index 000000000..2341c7e6e --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records.json @@ -0,0 +1,17 @@ +{ + "action": { + "id": 1, + "command": "add_rrset_records", + "status": "running", + "progress": 50, + "started": "2016-01-30T23:55:00+00:00", + "finished": null, + "resources": [ + { + "id": 42, + "type": "zone" + } + ], + "error": null + } +} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-deprecated_api_endpoint.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-deprecated_api_endpoint.json new file mode 100644 index 000000000..4d8fb945d --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-deprecated_api_endpoint.json @@ -0,0 +1,9 @@ +{ + "error": { + "code": "deprecated_api_endpoint", + "message": "API functionality was removed", + "details": { + "announcement": "https://docs.hetzner.cloud/changelog#2023-07-20-foo-endpoint-is-deprecated" + } + } +} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-invalid_input.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-invalid_input.json new file mode 100644 index 000000000..e05bf7a3e --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-invalid_input.json @@ -0,0 +1,16 @@ +{ + "error": { + "code": "invalid_input", + "message": "invalid input in field 'broken_field': is too long", + "details": { + "fields": [ + { + "name": "broken_field", + "messages": [ + "is too long" + ] + } + ] + } + } +} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-resource_limit_exceeded.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-resource_limit_exceeded.json new file mode 100644 index 000000000..9072d10e3 --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/error-resource_limit_exceeded.json @@ -0,0 +1,13 @@ +{ + "error": { + "code": "resource_limit_exceeded", + "message": "project limit exceeded", + "details": { + "limits": [ + { + "name": "project_limit" + } + ] + } + } +} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/get_action.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/get_action.json new file mode 100644 index 000000000..05f003b1e --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/get_action.json @@ -0,0 +1,20 @@ +{ + "action": { + "id": 42, + "command": "start_resource", + "status": "running", + "started": "2016-01-30T23:55:00+00:00", + "finished": "2016-01-30T23:55:00+00:00", + "progress": 100, + "resources": [ + { + "id": 42, + "type": "server" + } + ], + "error": { + "code": "action_failed", + "message": "Action failed" + } + } +} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/remove_rrset_records-request.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/remove_rrset_records-request.json new file mode 100644 index 000000000..778e051b4 --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/remove_rrset_records-request.json @@ -0,0 +1,8 @@ +{ + "records": [ + { + "value": "198.51.100.1", + "comment": "My web server at Hetzner Cloud." + } + ] +} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/remove_rrset_records.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/remove_rrset_records.json new file mode 100644 index 000000000..1b10dfd5e --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/remove_rrset_records.json @@ -0,0 +1,17 @@ +{ + "action": { + "id": 1, + "command": "remove_rrset_records", + "status": "running", + "progress": 50, + "started": "2016-01-30T23:55:00+00:00", + "finished": null, + "resources": [ + { + "id": 42, + "type": "zone" + } + ], + "error": null + } +} diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/types.go b/providers/dns/hetzner/internal/hetznerv1/internal/types.go new file mode 100644 index 000000000..0dd9fb9a1 --- /dev/null +++ b/providers/dns/hetzner/internal/hetznerv1/internal/types.go @@ -0,0 +1,96 @@ +package internal + +import ( + "fmt" + "strings" +) + +type APIError struct { + ErrorInfo ErrorInfo `json:"error"` +} + +type ErrorInfo struct { + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` + Details ErrorDetails `json:"details,omitempty"` +} + +func (i *ErrorInfo) Error() string { + msg := fmt.Sprintf("%s: %s", i.Code, i.Message) + + if i.Details.Announcement != "" { + msg += fmt.Sprintf(": %s", i.Details.Announcement) + } + + for _, limit := range i.Details.Limits { + msg += fmt.Sprintf("limit: %s", limit.Name) + } + + for _, field := range i.Details.Fields { + msg += fmt.Sprintf("field: %s: %s", field.Name, strings.Join(field.Messages, ", ")) + } + + return msg +} + +type ErrorDetails struct { + Announcement string `json:"announcement,omitempty"` + Limits []LimitError `json:"limits,omitempty"` + Fields []FieldError `json:"fields,omitempty"` +} + +type FieldError struct { + Name string `json:"name,omitempty"` + Messages []string `json:"messages,omitempty"` +} + +type LimitError struct { + Name string `json:"name,omitempty"` +} + +func (a *APIError) Error() string { + return a.ErrorInfo.Error() +} + +type RRSet struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + TTL int `json:"ttl,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Protection *Protection `json:"protection,omitempty"` + Records []Record `json:"records,omitempty"` + ZoneID int `json:"zone,omitempty"` +} + +type Protection struct { + Change bool `json:"change,omitempty"` +} + +type Record struct { + Value string `json:"value,omitempty"` + Comment string `json:"comment,omitempty"` +} + +type ActionResponse struct { + Action *Action `json:"action,omitempty"` +} + +type Action struct { + ID int `json:"id,omitempty"` + Command string `json:"command,omitempty"` + + // It can be: `running`, `success`, `error`. + // https://docs.hetzner.cloud/reference/cloud#zone-actions-get-an-action + // https://docs.hetzner.cloud/reference/cloud#zone-actions + Status string `json:"status,omitempty"` + Progress int `json:"progress,omitempty"` + + Resources []Resources `json:"resources,omitempty"` + ErrorInfo *ErrorInfo `json:"error,omitempty"` +} + +type Resources struct { + ID int `json:"id,omitempty"` + Type string `json:"type,omitempty"` +} diff --git a/providers/dns/hetzner/internal/legacy/hetzner.go b/providers/dns/hetzner/internal/legacy/hetzner.go new file mode 100644 index 000000000..5fb978415 --- /dev/null +++ b/providers/dns/hetzner/internal/legacy/hetzner.go @@ -0,0 +1,174 @@ +// Package legacy implements a DNS provider for solving the DNS-01 challenge using Hetzner DNS. +package legacy + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/legacy/internal" +) + +// Environment variables names. +const ( + envNamespace = "HETZNER_" + + EnvAPIKey = envNamespace + "API_KEY" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +const minTTL = 60 + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// 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, minTTL), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), + 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for hetzner. +// Credentials must be passed in the environment variable: HETZNER_API_KEY. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIKey) + if err != nil { + return nil, fmt.Errorf("hetzner (legacy): %w", err) + } + + config := NewDefaultConfig() + config.APIKey = values[EnvAPIKey] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for hetzner. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("hetzner (legacy): the configuration of the DNS provider is nil") + } + + if config.APIKey == "" { + return nil, errors.New("hetzner (legacy): credentials missing") + } + + if config.TTL < minTTL { + return nil, fmt.Errorf("hetzner (legacy): invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) + } + + client := internal.NewClient(config.APIKey) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{config: config, client: client}, 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 +} + +// Present creates a TXT record to fulfill the dns-01 challenge. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("hetzner (legacy): could not find zone for domain %q: %w", domain, err) + } + + zone := dns01.UnFqdn(authZone) + + ctx := context.Background() + + zoneID, err := d.client.GetZoneID(ctx, zone) + if err != nil { + return fmt.Errorf("hetzner (legacy): %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone) + if err != nil { + return fmt.Errorf("hetzner (legacy): %w", err) + } + + record := internal.DNSRecord{ + Type: "TXT", + Name: subDomain, + Value: info.Value, + TTL: d.config.TTL, + ZoneID: zoneID, + } + + if err := d.client.CreateRecord(ctx, record); err != nil { + return fmt.Errorf("hetzner (legacy): failed to add TXT record: fqdn=%s, zoneID=%s: %w", info.EffectiveFQDN, zoneID, err) + } + + 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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("hetzner (legacy): could not find zone for domain %q: %w", domain, err) + } + + zone := dns01.UnFqdn(authZone) + + ctx := context.Background() + + zoneID, err := d.client.GetZoneID(ctx, zone) + if err != nil { + return fmt.Errorf("hetzner (legacy): %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone) + if err != nil { + return fmt.Errorf("hetzner (legacy): %w", err) + } + + record, err := d.client.GetTxtRecord(ctx, subDomain, info.Value, zoneID) + if err != nil { + return fmt.Errorf("hetzner (legacy): %w", err) + } + + if err := d.client.DeleteRecord(ctx, record.ID); err != nil { + return fmt.Errorf("hetzner (legacy): failed to delete TXT record: id=%s, name=%s: %w", record.ID, record.Name, err) + } + + return nil +} diff --git a/providers/dns/hetzner/internal/legacy/hetzner_test.go b/providers/dns/hetzner/internal/legacy/hetzner_test.go new file mode 100644 index 000000000..07eae8149 --- /dev/null +++ b/providers/dns/hetzner/internal/legacy/hetzner_test.go @@ -0,0 +1,125 @@ +package legacy + +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: "123", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{ + EnvAPIKey: "", + }, + expected: "hetzner (legacy): some credentials information are missing: HETZNER_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) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + apiKey string + ttl int + expected string + }{ + { + desc: "success", + ttl: minTTL, + apiKey: "123", + }, + { + desc: "missing credentials", + ttl: minTTL, + expected: "hetzner (legacy): credentials missing", + }, + { + desc: "invalid TTL", + apiKey: "123", + ttl: 10, + expected: "hetzner (legacy): invalid TTL, TTL (10) must be greater than 60", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIKey = test.apiKey + config.TTL = test.ttl + + p, err := NewDNSProviderConfig(config) + + if test.expected == "" { + 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) + + err = provider.CleanUp(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/hetzner/internal/client.go b/providers/dns/hetzner/internal/legacy/internal/client.go similarity index 100% rename from providers/dns/hetzner/internal/client.go rename to providers/dns/hetzner/internal/legacy/internal/client.go diff --git a/providers/dns/hetzner/internal/client_test.go b/providers/dns/hetzner/internal/legacy/internal/client_test.go similarity index 100% rename from providers/dns/hetzner/internal/client_test.go rename to providers/dns/hetzner/internal/legacy/internal/client_test.go diff --git a/providers/dns/hetzner/internal/fixtures/create_txt_record-request.json b/providers/dns/hetzner/internal/legacy/internal/fixtures/create_txt_record-request.json similarity index 100% rename from providers/dns/hetzner/internal/fixtures/create_txt_record-request.json rename to providers/dns/hetzner/internal/legacy/internal/fixtures/create_txt_record-request.json diff --git a/providers/dns/hetzner/internal/fixtures/create_txt_record.json b/providers/dns/hetzner/internal/legacy/internal/fixtures/create_txt_record.json similarity index 100% rename from providers/dns/hetzner/internal/fixtures/create_txt_record.json rename to providers/dns/hetzner/internal/legacy/internal/fixtures/create_txt_record.json diff --git a/providers/dns/hetzner/internal/fixtures/get_txt_record.json b/providers/dns/hetzner/internal/legacy/internal/fixtures/get_txt_record.json similarity index 100% rename from providers/dns/hetzner/internal/fixtures/get_txt_record.json rename to providers/dns/hetzner/internal/legacy/internal/fixtures/get_txt_record.json diff --git a/providers/dns/hetzner/internal/fixtures/get_zone_id.json b/providers/dns/hetzner/internal/legacy/internal/fixtures/get_zone_id.json similarity index 100% rename from providers/dns/hetzner/internal/fixtures/get_zone_id.json rename to providers/dns/hetzner/internal/legacy/internal/fixtures/get_zone_id.json diff --git a/providers/dns/hetzner/internal/types.go b/providers/dns/hetzner/internal/legacy/internal/types.go similarity index 100% rename from providers/dns/hetzner/internal/types.go rename to providers/dns/hetzner/internal/legacy/internal/types.go From 07683e60d8159acc20aeac3077c5659331fea007 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 15 Oct 2025 13:42:00 +0200 Subject: [PATCH 180/298] chore: update to github.com/cenkalti/backoff/v5 (#2668) --- acme/api/api.go | 28 ++++++++++------------ challenge/resolver/solver_manager.go | 11 ++++++--- go.mod | 3 ++- go.sum | 2 ++ platform/wait/wait.go | 11 +++++++++ providers/dns/dynu/internal/client.go | 10 +++----- providers/dns/internal/hostingde/client.go | 22 +++++------------ 7 files changed, 45 insertions(+), 42 deletions(-) diff --git a/acme/api/api.go b/acme/api/api.go index a4e6398ea..cdad156dc 100644 --- a/acme/api/api.go +++ b/acme/api/api.go @@ -2,6 +2,7 @@ package api import ( "bytes" + "context" "crypto" "encoding/json" "errors" @@ -9,7 +10,7 @@ import ( "net/http" "time" - "github.com/cenkalti/backoff/v4" + "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api/internal/nonces" "github.com/go-acme/lego/v4/acme/api/internal/secure" @@ -76,39 +77,36 @@ func (a *Core) postAsGet(uri string, response any) (*http.Response, error) { } func (a *Core) retrievablePost(uri string, content []byte, response any) (*http.Response, error) { + ctx := context.Background() + // during tests, allow to support ~90% of bad nonce with a minimum of attempts. bo := backoff.NewExponentialBackOff() bo.InitialInterval = 200 * time.Millisecond bo.MaxInterval = 5 * time.Second - bo.MaxElapsedTime = 20 * time.Second - var resp *http.Response - operation := func() error { - var err error - resp, err = a.signedPost(uri, content, response) + operation := func() (*http.Response, error) { + resp, err := a.signedPost(uri, content, response) if err != nil { // Retry if the nonce was invalidated var e *acme.NonceError if errors.As(err, &e) { - return err + return resp, err } - return backoff.Permanent(err) + return resp, backoff.Permanent(err) } - return nil + return resp, nil } notify := func(err error, duration time.Duration) { log.Infof("retry due to: %v", err) } - err := backoff.RetryNotify(operation, bo, notify) - if err != nil { - return resp, err - } - - return resp, nil + return backoff.Retry(ctx, operation, + backoff.WithBackOff(bo), + backoff.WithMaxElapsedTime(20*time.Second), + backoff.WithNotify(notify)) } func (a *Core) signedPost(uri string, content []byte, response any) (*http.Response, error) { diff --git a/challenge/resolver/solver_manager.go b/challenge/resolver/solver_manager.go index dcde3a3ed..07687aaaf 100644 --- a/challenge/resolver/solver_manager.go +++ b/challenge/resolver/solver_manager.go @@ -1,13 +1,14 @@ package resolver import ( + "context" "errors" "fmt" "sort" "strconv" "time" - "github.com/cenkalti/backoff/v4" + "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/challenge" @@ -15,6 +16,7 @@ import ( "github.com/go-acme/lego/v4/challenge/http01" "github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/go-acme/lego/v4/log" + "github.com/go-acme/lego/v4/platform/wait" ) type byType []acme.Challenge @@ -101,10 +103,11 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error { } initialInterval := time.Duration(ra) * time.Second + ctx := context.Background() + bo := backoff.NewExponentialBackOff() bo.InitialInterval = initialInterval bo.MaxInterval = 10 * initialInterval - bo.MaxElapsedTime = 100 * initialInterval // After the path is sent, the ACME server will access our server. // Repeatedly check the server for an updated status on our request. @@ -127,7 +130,9 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error { return fmt.Errorf("the server didn't respond to our request (status=%s)", authz.Status) } - return backoff.Retry(operation, bo) + return wait.Retry(ctx, operation, + backoff.WithBackOff(bo), + backoff.WithMaxElapsedTime(100*initialInterval)) } func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) { diff --git a/go.mod b/go.mod index 8b13aa1ec..e964ed1d6 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.38.4 github.com/aziontech/azionapi-go-sdk v0.142.0 github.com/baidubce/bce-sdk-go v0.9.243 - github.com/cenkalti/backoff/v4 v4.3.0 + github.com/cenkalti/backoff/v5 v5.0.3 github.com/dnsimple/dnsimple-go/v4 v4.0.0 github.com/exoscale/egoscale/v3 v3.1.26 github.com/go-acme/alidns-20150109/v4 v4.6.0 @@ -130,6 +130,7 @@ require ( github.com/aws/smithy-go v1.23.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect diff --git a/go.sum b/go.sum index 73c319861..9d7e27c76 100644 --- a/go.sum +++ b/go.sum @@ -234,6 +234,8 @@ github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= diff --git a/platform/wait/wait.go b/platform/wait/wait.go index a1fa80903..e6420263d 100644 --- a/platform/wait/wait.go +++ b/platform/wait/wait.go @@ -1,9 +1,11 @@ package wait import ( + "context" "fmt" "time" + "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/log" ) @@ -35,3 +37,12 @@ func For(msg string, timeout, interval time.Duration, f func() (bool, error)) er time.Sleep(interval) } } + +// Retry retries the given operation until it succeeds or the context is canceled. +// Similar to [backoff.Retry] but with a different signature. +func Retry(ctx context.Context, operation func() error, opts ...backoff.RetryOption) error { + _, err := backoff.Retry(ctx, func() (any, error) { + return nil, operation() + }, opts...) + return err +} diff --git a/providers/dns/dynu/internal/client.go b/providers/dns/dynu/internal/client.go index a51556a0b..f7e90c489 100644 --- a/providers/dns/dynu/internal/client.go +++ b/providers/dns/dynu/internal/client.go @@ -12,8 +12,9 @@ import ( "strconv" "time" - "github.com/cenkalti/backoff/v4" + "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/log" + "github.com/go-acme/lego/v4/platform/wait" "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) @@ -123,12 +124,7 @@ func (c *Client) doRetry(ctx context.Context, method, uri string, body []byte, r bo := backoff.NewExponentialBackOff() bo.InitialInterval = 1 * time.Second - err := backoff.RetryNotify(operation, bo, notify) - if err != nil { - return err - } - - return nil + return wait.Retry(ctx, operation, backoff.WithBackOff(bo), backoff.WithNotify(notify)) } func (c *Client) do(ctx context.Context, method, uri string, body []byte, result any) error { diff --git a/providers/dns/internal/hostingde/client.go b/providers/dns/internal/hostingde/client.go index 869d93d3e..43354384f 100644 --- a/providers/dns/internal/hostingde/client.go +++ b/providers/dns/internal/hostingde/client.go @@ -10,7 +10,7 @@ import ( "net/url" "time" - "github.com/cenkalti/backoff/v4" + "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) @@ -40,35 +40,25 @@ func NewClient(apiKey string) *Client { // GetZone gets a zone. func (c *Client) GetZone(ctx context.Context, req ZoneConfigsFindRequest) (*ZoneConfig, error) { - var zoneConfig *ZoneConfig - - operation := func() error { + operation := func() (*ZoneConfig, error) { response, err := c.ListZoneConfigs(ctx, req) if err != nil { - return backoff.Permanent(err) + return nil, backoff.Permanent(err) } if response.Data[0].Status != "active" { - return fmt.Errorf("unexpected status: %q", response.Data[0].Status) + return nil, fmt.Errorf("unexpected status: %q", response.Data[0].Status) } - zoneConfig = &response.Data[0] - - return nil + return &response.Data[0], nil } bo := backoff.NewExponentialBackOff() bo.InitialInterval = 3 * time.Second bo.MaxInterval = 10 * bo.InitialInterval - bo.MaxElapsedTime = 100 * bo.InitialInterval // retry in case the zone was edited recently and is not yet active - err := backoff.Retry(operation, bo) - if err != nil { - return nil, err - } - - return zoneConfig, nil + return backoff.Retry(ctx, operation, backoff.WithBackOff(bo), backoff.WithMaxElapsedTime(100*bo.InitialInterval)) } // ListZoneConfigs lists zone configuration. From 526ca7395c812cef55daa3797ae9277bdd2dacac Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 15 Oct 2025 18:26:51 +0200 Subject: [PATCH 181/298] chore: replace wait.For with backoff inside DNS providers (#2671) --- platform/wait/wait_test.go | 84 +++++++++++++++---- providers/dns/cloudns/cloudns.go | 25 ++++-- providers/dns/f5xc/f5xc.go | 25 ++++-- providers/dns/gcloud/googlecloud.go | 35 ++++---- .../hetzner/internal/hetznerv1/hetznerv1.go | 45 +++++----- .../internal/hetznerv1/hetznerv1_test.go | 8 +- providers/dns/huaweicloud/huaweicloud.go | 32 ++++--- providers/dns/nifcloud/nifcloud.go | 24 ++++-- providers/dns/route53/route53.go | 25 +++--- providers/dns/variomedia/variomedia.go | 25 ++++-- providers/dns/vinyldns/wrapper.go | 18 ++-- 11 files changed, 234 insertions(+), 112 deletions(-) diff --git a/platform/wait/wait_test.go b/platform/wait/wait_test.go index 9d1a4ac34..b401c819f 100644 --- a/platform/wait/wait_test.go +++ b/platform/wait/wait_test.go @@ -2,65 +2,119 @@ package wait import ( "errors" + "sync/atomic" "testing" "time" + + "github.com/stretchr/testify/require" ) +// TODO(ldez): rewrite those tests when upgrading to go1.25 as minimum Go version. + func TestFor_timeout(t *testing.T) { + var io atomic.Int64 + c := make(chan error) + go func() { - c <- For("", 3*time.Second, 1*time.Second, func() (bool, error) { + c <- For("test", 3*time.Second, 1*time.Second, func() (bool, error) { + io.Add(1) + if io.Load() == 1 { + return false, nil + } + return false, nil }) }() timeout := time.After(6 * time.Second) + select { case <-timeout: t.Fatal("timeout exceeded") case err := <-c: - if err == nil { - t.Errorf("expected timeout error; got %v", err) - } - t.Logf("%v", err) + require.EqualError(t, err, "test: time limit exceeded") } + + require.EqualValues(t, 3, io.Load()) +} + +func TestFor_timeout_with_error(t *testing.T) { + var io atomic.Int64 + + c := make(chan error) + + go func() { + c <- For("test", 3*time.Second, 1*time.Second, func() (bool, error) { + io.Add(1) + + // This allows be sure that the latest previous error is returned. + if io.Load() == 1 { + return false, errors.New("oops") + } + + return false, nil + }) + }() + + timeout := time.After(6 * time.Second) + + select { + case <-timeout: + t.Fatal("timeout exceeded") + case err := <-c: + require.EqualError(t, err, "test: time limit exceeded: last error: oops") + } + + require.EqualValues(t, 3, io.Load()) } func TestFor_stop(t *testing.T) { + var io atomic.Int64 + c := make(chan error) + go func() { - c <- For("", 3*time.Second, 1*time.Second, func() (bool, error) { + c <- For("test", 3*time.Second, 1*time.Second, func() (bool, error) { + io.Add(1) + return true, nil }) }() timeout := time.After(6 * time.Second) + select { case <-timeout: t.Fatal("timeout exceeded") case err := <-c: - if err != nil { - t.Errorf("expected no timeout error; got %v", err) - } + require.NoError(t, err) } + + require.EqualValues(t, 1, io.Load()) } -func TestFor_stop_error(t *testing.T) { +func TestFor_stop_with_error(t *testing.T) { + var io atomic.Int64 + c := make(chan error) + go func() { - c <- For("", 3*time.Second, 1*time.Second, func() (bool, error) { + c <- For("test", 3*time.Second, 1*time.Second, func() (bool, error) { + io.Add(1) + return true, errors.New("oops") }) }() timeout := time.After(6 * time.Second) + select { case <-timeout: t.Fatal("timeout exceeded") case err := <-c: - if err == nil { - t.Errorf("expected error; got %v", err) - } - t.Logf("%v", err) + require.EqualError(t, err, "oops") } + + require.EqualValues(t, 1, io.Load()) } diff --git a/providers/dns/cloudns/cloudns.go b/providers/dns/cloudns/cloudns.go index ef6524c4d..f199cd1a1 100644 --- a/providers/dns/cloudns/cloudns.go +++ b/providers/dns/cloudns/cloudns.go @@ -8,6 +8,7 @@ import ( "net/http" "time" + "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" @@ -162,14 +163,22 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // waitNameservers At the time of writing 4 servers are found as authoritative, but 8 are reported during the sync. // If this is not done, the secondary verification done by Let's Encrypt server will fail quire a bit. func (d *DNSProvider) waitNameservers(ctx context.Context, domain string, zone *internal.Zone) error { - return wait.For("Nameserver sync on "+domain, d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) { - syncProgress, err := d.client.GetUpdateStatus(ctx, zone.Name) - if err != nil { - return false, err - } + return wait.Retry(context.Background(), + func() error { + syncProgress, err := d.client.GetUpdateStatus(ctx, zone.Name) + if err != nil { + return fmt.Errorf("nameserver sync on %s: %w", domain, err) + } - log.Infof("[%s] Sync %d/%d complete", domain, syncProgress.Updated, syncProgress.Total) + log.Infof("[%s] Sync %d/%d complete", domain, syncProgress.Updated, syncProgress.Total) - return syncProgress.Complete, nil - }) + if !syncProgress.Complete { + return fmt.Errorf("nameserver sync on %s not complete", domain) + } + + return nil + }, + backoff.WithBackOff(backoff.NewConstantBackOff(d.config.PollingInterval)), + backoff.WithMaxElapsedTime(d.config.PropagationTimeout), + ) } diff --git a/providers/dns/f5xc/f5xc.go b/providers/dns/f5xc/f5xc.go index 2ed1f0c4f..e854bf020 100644 --- a/providers/dns/f5xc/f5xc.go +++ b/providers/dns/f5xc/f5xc.go @@ -8,6 +8,7 @@ import ( "net/http" "time" + "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" @@ -128,29 +129,41 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { }, } - return wait.For("f5xc create", 60*time.Second, 2*time.Second, func() (bool, error) { + return d.waitFor(context.Background(), func() error { _, err = d.client.CreateRRSet(context.Background(), dns01.UnFqdn(authZone), d.config.GroupName, rrSet) if err != nil { - return false, fmt.Errorf("f5xc: create RR set: %w", err) + return fmt.Errorf("create RR set: %w", err) } - return true, nil + return nil }) } // Update RRSet. existingRRSet.RRSet.TXTRecord.Values = append(existingRRSet.RRSet.TXTRecord.Values, info.Value) - return wait.For("f5xc replace", 60*time.Second, 2*time.Second, func() (bool, error) { + return d.waitFor(context.Background(), func() error { _, err = d.client.ReplaceRRSet(context.Background(), dns01.UnFqdn(authZone), d.config.GroupName, subDomain, "TXT", existingRRSet.RRSet) if err != nil { - return false, fmt.Errorf("f5xc: replace RR set: %w", err) + return fmt.Errorf("replace RR set: %w", err) } - return true, nil + return nil }) } +func (d *DNSProvider) waitFor(ctx context.Context, operation func() error) error { + err := wait.Retry(ctx, operation, + backoff.WithBackOff(backoff.NewConstantBackOff(2*time.Second)), + backoff.WithMaxElapsedTime(60*time.Second), + ) + if err != nil { + return fmt.Errorf("f5xc: %w", err) + } + + 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) diff --git a/providers/dns/gcloud/googlecloud.go b/providers/dns/gcloud/googlecloud.go index 94cc3df1e..60f38592d 100644 --- a/providers/dns/gcloud/googlecloud.go +++ b/providers/dns/gcloud/googlecloud.go @@ -11,6 +11,7 @@ import ( "time" "cloud.google.com/go/compute/metadata" + "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" @@ -266,24 +267,28 @@ func (d *DNSProvider) applyChanges(zone string, change *gdns.Change) error { chgID := chg.Id // wait for change to be acknowledged - return wait.For("apply change", 30*time.Second, 3*time.Second, func() (bool, error) { - if d.config.Debug { - data, _ := json.Marshal(change) - log.Printf("change (Get): %s", string(data)) - } + return wait.Retry(context.Background(), + func() error { + if d.config.Debug { + data, _ := json.Marshal(change) + log.Printf("change (Get): %s", string(data)) + } - chg, err = d.client.Changes.Get(d.config.Project, zone, chgID).Do() - if err != nil { - data, _ := json.Marshal(change) - return false, fmt.Errorf("failed to get changes [zone %s, change %s]: %w", zone, string(data), err) - } + chg, err = d.client.Changes.Get(d.config.Project, zone, chgID).Do() + if err != nil { + data, _ := json.Marshal(change) + return fmt.Errorf("failed to get changes [zone %s, change %s]: %w", zone, string(data), err) + } - if chg.Status == changeStatusDone { - return true, nil - } + if chg.Status != changeStatusDone { + return fmt.Errorf("status: %s", chg.Status) + } - return false, fmt.Errorf("status: %s", chg.Status) - }) + return nil + }, + backoff.WithBackOff(backoff.NewConstantBackOff(3*time.Second)), + backoff.WithMaxElapsedTime(30*time.Second), + ) } // CleanUp removes the TXT record matching the specified parameters. diff --git a/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go b/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go index 603177a0d..827eb5457 100644 --- a/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go +++ b/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go @@ -9,6 +9,7 @@ import ( "strconv" "time" + "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" @@ -123,9 +124,9 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("hetzner: add RRSet records: %w", err) } - err = d.waitAction(ctx, "action: add RRSet records", action.ID) + err = d.waitAction(ctx, action.ID) if err != nil { - return fmt.Errorf("hetzner: wait (add): %w", err) + return fmt.Errorf("hetzner: wait (add RRSet records): %w", err) } return nil @@ -164,9 +165,9 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("hetzner: remove RRSet records: %w", err) } - err = d.waitAction(ctx, "action: remove RRSet records", action.ID) + err = d.waitAction(ctx, action.ID) if err != nil { - return fmt.Errorf("hetzner: wait (remove): %w", err) + return fmt.Errorf("hetzner: wait (remove RRSet records): %w", err) } return nil @@ -178,24 +179,26 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -func (d *DNSProvider) waitAction(ctx context.Context, msg string, actionID int) error { - return wait.For(msg, d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) { - result, err := d.client.GetAction(ctx, actionID) - if err != nil { - return false, fmt.Errorf("get action %d: %w", actionID, err) - } +func (d *DNSProvider) waitAction(ctx context.Context, actionID int) error { + return wait.Retry(ctx, + func() error { + result, err := d.client.GetAction(ctx, actionID) + if err != nil { + return backoff.Permanent(fmt.Errorf("get action %d: %w", actionID, err)) + } - switch result.Status { - case internal.StatusRunning: - return false, fmt.Errorf("action %d is %s", actionID, internal.StatusRunning) + switch result.Status { + case internal.StatusRunning: + return fmt.Errorf("action %d is %s", actionID, internal.StatusRunning) - case internal.StatusSuccess: - return true, nil + case internal.StatusError: + return fmt.Errorf("action %d: %s: %w", actionID, internal.StatusError, result.ErrorInfo) - case internal.StatusError: - return true, fmt.Errorf("action %d: %s: %w", actionID, internal.StatusError, result.ErrorInfo) - } - - return true, nil - }) + default: + return nil + } + }, + backoff.WithBackOff(backoff.NewConstantBackOff(d.config.PollingInterval)), + backoff.WithMaxElapsedTime(d.config.PropagationTimeout), + ) } diff --git a/providers/dns/hetzner/internal/hetznerv1/hetznerv1_test.go b/providers/dns/hetzner/internal/hetznerv1/hetznerv1_test.go index 597907e08..e43dce068 100644 --- a/providers/dns/hetzner/internal/hetznerv1/hetznerv1_test.go +++ b/providers/dns/hetzner/internal/hetznerv1/hetznerv1_test.go @@ -164,7 +164,7 @@ func TestDNSProvider_Present_error(t *testing.T) { provider.config.PropagationTimeout = 1 * time.Second err := provider.Present("example.com", "", "foobar") - require.EqualError(t, err, "hetzner: wait (add): action 1: error: action_failed: Action failed") + require.EqualError(t, err, "hetzner: wait (add RRSet records): action 1: error: action_failed: Action failed") } func TestDNSProvider_Present_running(t *testing.T) { @@ -180,7 +180,7 @@ func TestDNSProvider_Present_running(t *testing.T) { provider.config.PropagationTimeout = 1 * time.Second err := provider.Present("example.com", "", "foobar") - require.EqualError(t, err, "hetzner: wait (add): action: add RRSet records: time limit exceeded: last error: action 1 is running") + require.EqualError(t, err, "hetzner: wait (add RRSet records): action 1 is running") } func TestDNSProvider_CleanUp(t *testing.T) { @@ -209,7 +209,7 @@ func TestDNSProvider_CleanUp_error(t *testing.T) { provider.config.PropagationTimeout = 1 * time.Second err := provider.CleanUp("example.com", "", "foobar") - require.EqualError(t, err, "hetzner: wait (remove): action 1: error: action_failed: Action failed") + require.EqualError(t, err, "hetzner: wait (remove RRSet records): action 1: error: action_failed: Action failed") } func TestDNSProvider_CleanUp_running(t *testing.T) { @@ -225,5 +225,5 @@ func TestDNSProvider_CleanUp_running(t *testing.T) { provider.config.PropagationTimeout = 1 * time.Second err := provider.CleanUp("example.com", "", "foobar") - require.EqualError(t, err, "hetzner: wait (remove): action: remove RRSet records: time limit exceeded: last error: action 1 is running") + require.EqualError(t, err, "hetzner: wait (remove RRSet records): action 1 is running") } diff --git a/providers/dns/huaweicloud/huaweicloud.go b/providers/dns/huaweicloud/huaweicloud.go index 32f4d3446..430abce74 100644 --- a/providers/dns/huaweicloud/huaweicloud.go +++ b/providers/dns/huaweicloud/huaweicloud.go @@ -2,6 +2,7 @@ package huaweicloud import ( + "context" "errors" "fmt" "strconv" @@ -9,6 +10,7 @@ import ( "sync" "time" + "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" @@ -148,19 +150,27 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { d.recordIDs[token] = recordSetID d.recordIDsMu.Unlock() - err = wait.For("record set sync on "+domain, d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) { - rs, errShow := d.client.ShowRecordSet(&hwmodel.ShowRecordSetRequest{ - ZoneId: zoneID, - RecordsetId: recordSetID, - }) - if errShow != nil { - return false, fmt.Errorf("show record set: %w", errShow) - } + err = wait.Retry(context.Background(), + func() error { + rs, errShow := d.client.ShowRecordSet(&hwmodel.ShowRecordSetRequest{ + ZoneId: zoneID, + RecordsetId: recordSetID, + }) + if errShow != nil { + return fmt.Errorf("show record set: %w", errShow) + } - return !strings.HasSuffix(ptr.Deref(rs.Status), "PENDING_"), nil - }) + if !strings.HasSuffix(ptr.Deref(rs.Status), "PENDING_") { + return nil + } + + return fmt.Errorf("status: %s", ptr.Deref(rs.Status)) + }, + backoff.WithBackOff(backoff.NewConstantBackOff(d.config.PollingInterval)), + backoff.WithMaxElapsedTime(d.config.PropagationTimeout), + ) if err != nil { - return fmt.Errorf("huaweicloud: %w", err) + return fmt.Errorf("huaweicloud: record set sync on %s: %w", domain, err) } return nil diff --git a/providers/dns/nifcloud/nifcloud.go b/providers/dns/nifcloud/nifcloud.go index e73333c52..ccdadf615 100644 --- a/providers/dns/nifcloud/nifcloud.go +++ b/providers/dns/nifcloud/nifcloud.go @@ -9,6 +9,7 @@ import ( "net/url" "time" + "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" @@ -179,11 +180,20 @@ func (d *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { statusID := resp.ChangeInfo.ID - return wait.For("nifcloud", 120*time.Second, 4*time.Second, func() (bool, error) { - resp, err := d.client.GetChange(ctx, statusID) - if err != nil { - return false, fmt.Errorf("failed to query change status: %w", err) - } - return resp.ChangeInfo.Status == "INSYNC", nil - }) + return wait.Retry(context.Background(), + func() error { + resp, err := d.client.GetChange(ctx, statusID) + if err != nil { + return fmt.Errorf("get change: %w", err) + } + + if resp.ChangeInfo.Status != "INSYNC" { + return fmt.Errorf("change status: %s", resp.ChangeInfo.Status) + } + + return nil + }, + backoff.WithBackOff(backoff.NewConstantBackOff(4*time.Second)), + backoff.WithMaxElapsedTime(120*time.Second), + ) } diff --git a/providers/dns/route53/route53.go b/providers/dns/route53/route53.go index db578eb00..56cb37cb5 100644 --- a/providers/dns/route53/route53.go +++ b/providers/dns/route53/route53.go @@ -17,6 +17,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/route53" awstypes "github.com/aws/aws-sdk-go-v2/service/route53/types" "github.com/aws/aws-sdk-go-v2/service/sts" + "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" @@ -249,18 +250,22 @@ func (d *DNSProvider) changeRecord(ctx context.Context, action awstypes.ChangeAc changeID := resp.ChangeInfo.Id if d.config.WaitForRecordSetsChanged { - return wait.For("route53", d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) { - resp, err := d.client.GetChange(ctx, &route53.GetChangeInput{Id: changeID}) - if err != nil { - return false, fmt.Errorf("failed to query change status: %w", err) - } + return wait.Retry(context.Background(), + func() error { + resp, err := d.client.GetChange(ctx, &route53.GetChangeInput{Id: changeID}) + if err != nil { + return fmt.Errorf("failed to query change status: %w", err) + } - if resp.ChangeInfo.Status == awstypes.ChangeStatusInsync { - return true, nil - } + if resp.ChangeInfo.Status != awstypes.ChangeStatusInsync { + return fmt.Errorf("unable to retrieve change: ID=%s, status=%s", ptr.Deref(changeID), resp.ChangeInfo.Status) + } - return false, fmt.Errorf("unable to retrieve change: ID=%s", ptr.Deref(changeID)) - }) + return nil + }, + backoff.WithBackOff(backoff.NewConstantBackOff(d.config.PollingInterval)), + backoff.WithMaxElapsedTime(d.config.PropagationTimeout), + ) } return nil diff --git a/providers/dns/variomedia/variomedia.go b/providers/dns/variomedia/variomedia.go index 548d8bab8..f8254335c 100644 --- a/providers/dns/variomedia/variomedia.go +++ b/providers/dns/variomedia/variomedia.go @@ -10,6 +10,7 @@ import ( "sync" "time" + "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" @@ -179,14 +180,22 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } func (d *DNSProvider) waitJob(ctx context.Context, domain, id string) error { - return wait.For("variomedia: apply change on "+domain, d.config.PropagationTimeout, d.config.PollingInterval, func() (bool, error) { - result, err := d.client.GetJob(ctx, id) - if err != nil { - return false, err - } + return wait.Retry(context.Background(), + func() error { + result, err := d.client.GetJob(ctx, id) + if err != nil { + return fmt.Errorf("apply change on %s: %w", domain, err) + } - log.Infof("variomedia: [%s] %s: %s %s", domain, result.Data.ID, result.Data.Attributes.JobType, result.Data.Attributes.Status) + log.Infof("variomedia: [%s] %s: %s %s", domain, result.Data.ID, result.Data.Attributes.JobType, result.Data.Attributes.Status) - return result.Data.Attributes.Status == "done", nil - }) + if result.Data.Attributes.Status != "done" { + return fmt.Errorf("apply change on %s: status: %s", domain, result.Data.Attributes.Status) + } + + return nil + }, + backoff.WithBackOff(backoff.NewConstantBackOff(d.config.PollingInterval)), + backoff.WithMaxElapsedTime(d.config.PropagationTimeout), + ) } diff --git a/providers/dns/vinyldns/wrapper.go b/providers/dns/vinyldns/wrapper.go index f17b3de31..84d6e20a6 100644 --- a/providers/dns/vinyldns/wrapper.go +++ b/providers/dns/vinyldns/wrapper.go @@ -1,8 +1,10 @@ package vinyldns import ( + "context" "fmt" + "github.com/cenkalti/backoff/v5" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/wait" "github.com/vinyldns/go-vinyldns/vinyldns" @@ -95,20 +97,22 @@ func (d *DNSProvider) deleteRecordSet(existingRecord *vinyldns.RecordSet) error } func (d *DNSProvider) waitForChanges(operation string, resp *vinyldns.RecordSetUpdateResponse) error { - return wait.For("vinyldns", d.config.PropagationTimeout, d.config.PollingInterval, - func() (bool, error) { + return wait.Retry(context.Background(), + func() error { change, err := d.client.RecordSetChange(resp.Zone.ID, resp.RecordSet.ID, resp.ChangeID) if err != nil { - return false, fmt.Errorf("failed to query change status: %w", err) + return fmt.Errorf("failed to query change status: %w", err) } - if change.Status == "Complete" { - return true, nil + if change.Status != "Complete" { + return fmt.Errorf("waiting operation: %s, zoneID: %s, recordsetID: %s, changeID: %s", + operation, resp.Zone.ID, resp.RecordSet.ID, resp.ChangeID) } - return false, fmt.Errorf("waiting operation: %s, zoneID: %s, recordsetID: %s, changeID: %s", - operation, resp.Zone.ID, resp.RecordSet.ID, resp.ChangeID) + return nil }, + backoff.WithBackOff(backoff.NewConstantBackOff(d.config.PollingInterval)), + backoff.WithMaxElapsedTime(d.config.PropagationTimeout), ) } From 8873a5539c9ebe7baab7350f49aa0f534f0e3381 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 8 Oct 2025 01:19:44 +0200 Subject: [PATCH 182/298] chore: minor changes --- providers/dns/alidns/alidns.go | 24 +++++++------ providers/dns/auroradns/auroradns_test.go | 6 ++-- providers/dns/beget/beget.go | 12 ++++--- providers/dns/cloudflare/cloudflare.go | 10 +++--- providers/dns/cloudns/cloudns.go | 2 +- .../dns/digitalocean/digitalocean_test.go | 2 +- providers/dns/dnsimple/dnsimple.go | 34 +++++++++++-------- providers/dns/dreamhost/dreamhost_test.go | 2 +- providers/dns/exoscale/exoscale.go | 16 ++++----- providers/dns/f5xc/f5xc.go | 12 ++++--- providers/dns/gcloud/googlecloud.go | 10 +++--- .../hetzner/internal/hetznerv1/hetznerv1.go | 2 +- providers/dns/lightsail/lightsail_test.go | 2 +- providers/dns/limacity/limacity.go | 12 ++++--- providers/dns/linode/linode.go | 18 ++++++---- providers/dns/mijnhost/mijnhost.go | 16 +++++---- .../dns/netcup/internal/client_live_test.go | 4 +-- providers/dns/netcup/netcup_test.go | 4 +-- providers/dns/nifcloud/nifcloud.go | 14 ++++---- providers/dns/route53/route53.go | 2 +- providers/dns/route53/route53_test.go | 4 +-- providers/dns/sakuracloud/sakuracloud.go | 5 +-- providers/dns/sakuracloud/wrapper.go | 16 ++++----- providers/dns/sakuracloud/wrapper_test.go | 24 ++++++++----- providers/dns/variomedia/variomedia.go | 2 +- providers/dns/vinyldns/vinyldns.go | 13 ++++--- providers/dns/vinyldns/wrapper.go | 16 ++++----- 27 files changed, 164 insertions(+), 120 deletions(-) diff --git a/providers/dns/alidns/alidns.go b/providers/dns/alidns/alidns.go index 3664dc88e..eefc1162e 100644 --- a/providers/dns/alidns/alidns.go +++ b/providers/dns/alidns/alidns.go @@ -152,9 +152,11 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) - zoneName, err := d.getHostedZone(info.EffectiveFQDN) + zoneName, err := d.getHostedZone(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("alicloud: %w", err) } @@ -164,7 +166,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return err } - _, err = alidns.AddDomainRecordWithContext(context.Background(), d.client, recordRequest, &dara.RuntimeOptions{}) + _, err = alidns.AddDomainRecordWithContext(ctx, d.client, recordRequest, &dara.RuntimeOptions{}) if err != nil { return fmt.Errorf("alicloud: API call failed: %w", err) } @@ -173,14 +175,16 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) - records, err := d.findTxtRecords(info.EffectiveFQDN) + records, err := d.findTxtRecords(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("alicloud: %w", err) } - _, err = d.getHostedZone(info.EffectiveFQDN) + _, err = d.getHostedZone(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("alicloud: %w", err) } @@ -190,7 +194,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { RecordId: rec.RecordId, } - _, err = alidns.DeleteDomainRecordWithContext(context.Background(), d.client, request, &dara.RuntimeOptions{}) + _, err = alidns.DeleteDomainRecordWithContext(ctx, d.client, request, &dara.RuntimeOptions{}) if err != nil { return fmt.Errorf("alicloud: %w", err) } @@ -199,7 +203,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } -func (d *DNSProvider) getHostedZone(domain string) (string, error) { +func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (string, error) { request := new(alidns.DescribeDomainsRequest) var domains []*alidns.DescribeDomainsResponseBodyDomainsDomain @@ -209,7 +213,7 @@ func (d *DNSProvider) getHostedZone(domain string) (string, error) { for { request.SetPageNumber(startPage) - response, err := alidns.DescribeDomainsWithContext(context.Background(), d.client, request, &dara.RuntimeOptions{}) + response, err := alidns.DescribeDomainsWithContext(ctx, d.client, request, &dara.RuntimeOptions{}) if err != nil { return "", fmt.Errorf("API call failed: %w", err) } @@ -256,8 +260,8 @@ func (d *DNSProvider) newTxtRecord(zone, fqdn, value string) (*alidns.AddDomainR SetTTL(int64(d.config.TTL)), nil } -func (d *DNSProvider) findTxtRecords(fqdn string) ([]*alidns.DescribeDomainRecordsResponseBodyDomainRecordsRecord, error) { - zoneName, err := d.getHostedZone(fqdn) +func (d *DNSProvider) findTxtRecords(ctx context.Context, fqdn string) ([]*alidns.DescribeDomainRecordsResponseBodyDomainRecordsRecord, error) { + zoneName, err := d.getHostedZone(ctx, fqdn) if err != nil { return nil, err } @@ -268,7 +272,7 @@ func (d *DNSProvider) findTxtRecords(fqdn string) ([]*alidns.DescribeDomainRecor var records []*alidns.DescribeDomainRecordsResponseBodyDomainRecordsRecord - result, err := alidns.DescribeDomainRecordsWithContext(context.Background(), d.client, request, &dara.RuntimeOptions{}) + result, err := alidns.DescribeDomainRecordsWithContext(ctx, d.client, request, &dara.RuntimeOptions{}) if err != nil { return records, fmt.Errorf("API call has failed: %w", err) } diff --git a/providers/dns/auroradns/auroradns_test.go b/providers/dns/auroradns/auroradns_test.go index 1619ee586..c5a2ca877 100644 --- a/providers/dns/auroradns/auroradns_test.go +++ b/providers/dns/auroradns/auroradns_test.go @@ -160,7 +160,7 @@ func TestDNSProvider_Present(t *testing.T) { Build(t) err := provider.Present("example.com", "", "foobar") - require.NoError(t, err, "fail to create TXT record") + require.NoError(t, err) } func TestDNSProvider_CleanUp(t *testing.T) { @@ -185,8 +185,8 @@ func TestDNSProvider_CleanUp(t *testing.T) { Build(t) err := provider.Present("example.com", "", "foobar") - require.NoError(t, err, "fail to create TXT record") + require.NoError(t, err) err = provider.CleanUp("example.com", "", "foobar") - require.NoError(t, err, "fail to remove TXT record") + require.NoError(t, err) } diff --git a/providers/dns/beget/beget.go b/providers/dns/beget/beget.go index d5354ac86..a8040bc0e 100644 --- a/providers/dns/beget/beget.go +++ b/providers/dns/beget/beget.go @@ -101,9 +101,11 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) - records, err := d.client.GetTXTRecords(context.Background(), dns01.UnFqdn(info.EffectiveFQDN)) + records, err := d.client.GetTXTRecords(ctx, dns01.UnFqdn(info.EffectiveFQDN)) if err != nil { return fmt.Errorf("beget: get TXT records: %w", err) } @@ -115,7 +117,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { TTL: d.config.TTL, }) - err = d.client.ChangeTXTRecord(context.Background(), dns01.UnFqdn(info.EffectiveFQDN), records) + err = d.client.ChangeTXTRecord(ctx, dns01.UnFqdn(info.EffectiveFQDN), records) if err != nil { return fmt.Errorf("beget: failed to create TXT records [domain: %s]: %w", dns01.UnFqdn(info.EffectiveFQDN), err) @@ -126,9 +128,11 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) - records, err := d.client.GetTXTRecords(context.Background(), dns01.UnFqdn(info.EffectiveFQDN)) + records, err := d.client.GetTXTRecords(ctx, dns01.UnFqdn(info.EffectiveFQDN)) if err != nil { return fmt.Errorf("beget: get TXT records: %w", err) } @@ -146,7 +150,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { updatedRecords = append(updatedRecords, record) } - err = d.client.ChangeTXTRecord(context.Background(), dns01.UnFqdn(info.EffectiveFQDN), updatedRecords) + err = d.client.ChangeTXTRecord(ctx, dns01.UnFqdn(info.EffectiveFQDN), updatedRecords) if err != nil { return fmt.Errorf("beget: failed to remove TXT records [domain: %s]: %w", dns01.UnFqdn(info.EffectiveFQDN), err) diff --git a/providers/dns/cloudflare/cloudflare.go b/providers/dns/cloudflare/cloudflare.go index 5fd350925..081025f34 100644 --- a/providers/dns/cloudflare/cloudflare.go +++ b/providers/dns/cloudflare/cloudflare.go @@ -154,10 +154,10 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) if err != nil { return fmt.Errorf("cloudflare: could not find zone for domain %q: %w", domain, err) @@ -191,6 +191,8 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) @@ -198,7 +200,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("cloudflare: could not find zone for domain %q: %w", domain, err) } - zoneID, err := d.client.ZoneIDByName(context.Background(), authZone) + zoneID, err := d.client.ZoneIDByName(ctx, authZone) if err != nil { return fmt.Errorf("cloudflare: failed to find zone %s: %w", authZone, err) } @@ -211,7 +213,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("cloudflare: unknown record ID for '%s'", info.EffectiveFQDN) } - err = d.client.DeleteDNSRecord(context.Background(), zoneID, recordID) + err = d.client.DeleteDNSRecord(ctx, zoneID, recordID) if err != nil { log.Printf("cloudflare: failed to delete TXT record: %v", err) } diff --git a/providers/dns/cloudns/cloudns.go b/providers/dns/cloudns/cloudns.go index f199cd1a1..7fbbe1062 100644 --- a/providers/dns/cloudns/cloudns.go +++ b/providers/dns/cloudns/cloudns.go @@ -163,7 +163,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // waitNameservers At the time of writing 4 servers are found as authoritative, but 8 are reported during the sync. // If this is not done, the secondary verification done by Let's Encrypt server will fail quire a bit. func (d *DNSProvider) waitNameservers(ctx context.Context, domain string, zone *internal.Zone) error { - return wait.Retry(context.Background(), + return wait.Retry(ctx, func() error { syncProgress, err := d.client.GetUpdateStatus(ctx, zone.Name) if err != nil { diff --git a/providers/dns/digitalocean/digitalocean_test.go b/providers/dns/digitalocean/digitalocean_test.go index a01906812..13f8b522d 100644 --- a/providers/dns/digitalocean/digitalocean_test.go +++ b/providers/dns/digitalocean/digitalocean_test.go @@ -138,5 +138,5 @@ func TestDNSProvider_CleanUp(t *testing.T) { provider.recordIDsMu.Unlock() err := provider.CleanUp("example.com", "token", "") - require.NoError(t, err, "fail to remove TXT record") + require.NoError(t, err) } diff --git a/providers/dns/dnsimple/dnsimple.go b/providers/dns/dnsimple/dnsimple.go index 737f214e9..5d1a7ba80 100644 --- a/providers/dns/dnsimple/dnsimple.go +++ b/providers/dns/dnsimple/dnsimple.go @@ -94,14 +94,16 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) - zoneName, err := d.getHostedZone(info.EffectiveFQDN) + zoneName, err := d.getHostedZone(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("dnsimple: %w", err) } - accountID, err := d.getAccountID() + accountID, err := d.getAccountID(ctx) if err != nil { return fmt.Errorf("dnsimple: %w", err) } @@ -111,7 +113,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("dnsimple: %w", err) } - _, err = d.client.Zones.CreateRecord(context.Background(), accountID, zoneName, recordAttributes) + _, err = d.client.Zones.CreateRecord(ctx, accountID, zoneName, recordAttributes) if err != nil { return fmt.Errorf("dnsimple: API call failed: %w", err) } @@ -121,21 +123,23 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) - records, err := d.findTxtRecords(info.EffectiveFQDN) + records, err := d.findTxtRecords(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("dnsimple: %w", err) } - accountID, err := d.getAccountID() + accountID, err := d.getAccountID(ctx) if err != nil { return fmt.Errorf("dnsimple: %w", err) } var lastErr error for _, rec := range records { - _, err := d.client.Zones.DeleteRecord(context.Background(), accountID, rec.ZoneID, rec.ID) + _, err := d.client.Zones.DeleteRecord(ctx, accountID, rec.ZoneID, rec.ID) if err != nil { lastErr = fmt.Errorf("dnsimple: %w", err) } @@ -150,18 +154,18 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -func (d *DNSProvider) getHostedZone(domain string) (string, error) { +func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (string, error) { authZone, err := dns01.FindZoneByFqdn(domain) if err != nil { return "", fmt.Errorf("could not find zone for FQDN %q: %w", domain, err) } - accountID, err := d.getAccountID() + accountID, err := d.getAccountID(ctx) if err != nil { return "", err } - hostedZone, err := d.client.Zones.GetZone(context.Background(), accountID, dns01.UnFqdn(authZone)) + hostedZone, err := d.client.Zones.GetZone(ctx, accountID, dns01.UnFqdn(authZone)) if err != nil { return "", fmt.Errorf("get zone: %w", err) } @@ -173,13 +177,13 @@ func (d *DNSProvider) getHostedZone(domain string) (string, error) { return hostedZone.Data.Name, nil } -func (d *DNSProvider) findTxtRecords(fqdn string) ([]dnsimple.ZoneRecord, error) { - zoneName, err := d.getHostedZone(fqdn) +func (d *DNSProvider) findTxtRecords(ctx context.Context, fqdn string) ([]dnsimple.ZoneRecord, error) { + zoneName, err := d.getHostedZone(ctx, fqdn) if err != nil { return nil, err } - accountID, err := d.getAccountID() + accountID, err := d.getAccountID(ctx) if err != nil { return nil, err } @@ -189,7 +193,7 @@ func (d *DNSProvider) findTxtRecords(fqdn string) ([]dnsimple.ZoneRecord, error) return nil, err } - result, err := d.client.Zones.ListRecords(context.Background(), accountID, zoneName, &dnsimple.ZoneRecordListOptions{Name: &subDomain, Type: dnsimple.String("TXT"), ListOptions: dnsimple.ListOptions{}}) + result, err := d.client.Zones.ListRecords(ctx, accountID, zoneName, &dnsimple.ZoneRecordListOptions{Name: &subDomain, Type: dnsimple.String("TXT"), ListOptions: dnsimple.ListOptions{}}) if err != nil { return nil, fmt.Errorf("API call has failed: %w", err) } @@ -211,8 +215,8 @@ func newTxtRecord(zoneName, fqdn, value string, ttl int) (dnsimple.ZoneRecordAtt }, nil } -func (d *DNSProvider) getAccountID() (string, error) { - whoamiResponse, err := d.client.Identity.Whoami(context.Background()) +func (d *DNSProvider) getAccountID(ctx context.Context) (string, error) { + whoamiResponse, err := d.client.Identity.Whoami(ctx) if err != nil { return "", err } diff --git a/providers/dns/dreamhost/dreamhost_test.go b/providers/dns/dreamhost/dreamhost_test.go index f85e00da4..39b092c6f 100644 --- a/providers/dns/dreamhost/dreamhost_test.go +++ b/providers/dns/dreamhost/dreamhost_test.go @@ -151,7 +151,7 @@ func TestDNSProvider_Cleanup(t *testing.T) { Build(t) err := provider.CleanUp("example.com", "", fakeChallengeToken) - require.NoError(t, err, "failed to remove TXT record") + require.NoError(t, err) } func TestLivePresentAndCleanUp(t *testing.T) { diff --git a/providers/dns/exoscale/exoscale.go b/providers/dns/exoscale/exoscale.go index 1a5f358f5..fa76949d9 100644 --- a/providers/dns/exoscale/exoscale.go +++ b/providers/dns/exoscale/exoscale.go @@ -105,6 +105,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) zoneName, recordName, err := d.findZoneAndRecordName(info.EffectiveFQDN) @@ -112,7 +113,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("exoscale: %w", err) } - zone, err := d.findExistingZone(zoneName) + zone, err := d.findExistingZone(ctx, zoneName) if err != nil { return fmt.Errorf("exoscale: %w", err) } @@ -143,6 +144,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) zoneName, recordName, err := d.findZoneAndRecordName(info.EffectiveFQDN) @@ -150,7 +152,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("exoscale: %w", err) } - zone, err := d.findExistingZone(zoneName) + zone, err := d.findExistingZone(ctx, zoneName) if err != nil { return fmt.Errorf("exoscale: %w", err) } @@ -158,7 +160,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("exoscale: zone %q not found", zoneName) } - recordID, err := d.findExistingRecordID(zone.ID, recordName, info.Value) + recordID, err := d.findExistingRecordID(ctx, zone.ID, recordName, info.Value) if err != nil { return err } @@ -188,9 +190,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // findExistingZone Query Exoscale to find an existing zone for this name. // Returns nil result if no zone could be found. -func (d *DNSProvider) findExistingZone(zoneName string) (*egoscale.DNSDomain, error) { - ctx := context.Background() - +func (d *DNSProvider) findExistingZone(ctx context.Context, zoneName string) (*egoscale.DNSDomain, error) { zones, err := d.client.ListDNSDomains(ctx) if err != nil { return nil, fmt.Errorf("error while retrieving DNS zones: %w", err) @@ -207,9 +207,7 @@ func (d *DNSProvider) findExistingZone(zoneName string) (*egoscale.DNSDomain, er // findExistingRecordID Query Exoscale to find an existing record for this name. // Returns empty result if no record could be found. -func (d *DNSProvider) findExistingRecordID(zoneID egoscale.UUID, recordName, value string) (egoscale.UUID, error) { - ctx := context.Background() - +func (d *DNSProvider) findExistingRecordID(ctx context.Context, zoneID egoscale.UUID, recordName, value string) (egoscale.UUID, error) { records, err := d.client.ListDNSDomainRecords(ctx, zoneID) if err != nil { return "", fmt.Errorf("error while retrieving DNS records: %w", err) diff --git a/providers/dns/f5xc/f5xc.go b/providers/dns/f5xc/f5xc.go index e854bf020..9d74d1538 100644 --- a/providers/dns/f5xc/f5xc.go +++ b/providers/dns/f5xc/f5xc.go @@ -101,6 +101,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) @@ -113,7 +115,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("f5xc: %w", err) } - existingRRSet, err := d.client.GetRRSet(context.Background(), dns01.UnFqdn(authZone), d.config.GroupName, subDomain, "TXT") + existingRRSet, err := d.client.GetRRSet(ctx, dns01.UnFqdn(authZone), d.config.GroupName, subDomain, "TXT") if err != nil { return fmt.Errorf("f5xc: get RR Set: %w", err) } @@ -129,8 +131,8 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { }, } - return d.waitFor(context.Background(), func() error { - _, err = d.client.CreateRRSet(context.Background(), dns01.UnFqdn(authZone), d.config.GroupName, rrSet) + return d.waitFor(ctx, func() error { + _, err = d.client.CreateRRSet(ctx, dns01.UnFqdn(authZone), d.config.GroupName, rrSet) if err != nil { return fmt.Errorf("create RR set: %w", err) } @@ -142,8 +144,8 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // Update RRSet. existingRRSet.RRSet.TXTRecord.Values = append(existingRRSet.RRSet.TXTRecord.Values, info.Value) - return d.waitFor(context.Background(), func() error { - _, err = d.client.ReplaceRRSet(context.Background(), dns01.UnFqdn(authZone), d.config.GroupName, subDomain, "TXT", existingRRSet.RRSet) + return d.waitFor(ctx, func() error { + _, err = d.client.ReplaceRRSet(ctx, dns01.UnFqdn(authZone), d.config.GroupName, subDomain, "TXT", existingRRSet.RRSet) if err != nil { return fmt.Errorf("replace RR set: %w", err) } diff --git a/providers/dns/gcloud/googlecloud.go b/providers/dns/gcloud/googlecloud.go index 60f38592d..7c17abd4a 100644 --- a/providers/dns/gcloud/googlecloud.go +++ b/providers/dns/gcloud/googlecloud.go @@ -182,6 +182,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) zone, err := d.getHostedZone(info.EffectiveFQDN) @@ -211,7 +213,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // Attempt to delete the existing records before adding the new one. if len(existingRrSet) > 0 { - if err = d.applyChanges(zone, &gdns.Change{Deletions: existingRrSet}); err != nil { + if err = d.applyChanges(ctx, zone, &gdns.Change{Deletions: existingRrSet}); err != nil { return fmt.Errorf("googlecloud: %w", err) } } @@ -236,14 +238,14 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Additions: []*gdns.ResourceRecordSet{rec}, } - if err = d.applyChanges(zone, change); err != nil { + if err = d.applyChanges(ctx, zone, change); err != nil { return fmt.Errorf("googlecloud: %w", err) } return nil } -func (d *DNSProvider) applyChanges(zone string, change *gdns.Change) error { +func (d *DNSProvider) applyChanges(ctx context.Context, zone string, change *gdns.Change) error { if d.config.Debug { data, _ := json.Marshal(change) log.Printf("change (Create): %s", string(data)) @@ -267,7 +269,7 @@ func (d *DNSProvider) applyChanges(zone string, change *gdns.Change) error { chgID := chg.Id // wait for change to be acknowledged - return wait.Retry(context.Background(), + return wait.Retry(ctx, func() error { if d.config.Debug { data, _ := json.Marshal(change) diff --git a/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go b/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go index 827eb5457..63724712c 100644 --- a/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go +++ b/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go @@ -192,7 +192,7 @@ func (d *DNSProvider) waitAction(ctx context.Context, actionID int) error { return fmt.Errorf("action %d is %s", actionID, internal.StatusRunning) case internal.StatusError: - return fmt.Errorf("action %d: %s: %w", actionID, internal.StatusError, result.ErrorInfo) + return backoff.Permanent(fmt.Errorf("action %d: %s: %w", actionID, internal.StatusError, result.ErrorInfo)) default: return nil diff --git a/providers/dns/lightsail/lightsail_test.go b/providers/dns/lightsail/lightsail_test.go index adac03d5d..db69738fd 100644 --- a/providers/dns/lightsail/lightsail_test.go +++ b/providers/dns/lightsail/lightsail_test.go @@ -76,5 +76,5 @@ func TestDNSProvider_Present(t *testing.T) { keyAuth := "123456d==" err := provider.Present(domain, "", keyAuth) - require.NoError(t, err, "Expected Present to return no error") + require.NoError(t, err) } diff --git a/providers/dns/limacity/limacity.go b/providers/dns/limacity/limacity.go index 58755aabe..b403ecedf 100644 --- a/providers/dns/limacity/limacity.go +++ b/providers/dns/limacity/limacity.go @@ -110,9 +110,11 @@ func (d *DNSProvider) Sequential() time.Duration { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) - domains, err := d.client.GetDomains(context.Background()) + domains, err := d.client.GetDomains(ctx) if err != nil { return fmt.Errorf("limacity: get domains: %w", err) } @@ -134,7 +136,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Type: "TXT", } - err = d.client.AddRecord(context.Background(), dom.ID, record) + err = d.client.AddRecord(ctx, dom.ID, record) if err != nil { return fmt.Errorf("limacity: add record: %w", err) } @@ -148,6 +150,8 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) // gets the domain's unique ID @@ -158,7 +162,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("limacity: unknown domain ID for '%s' '%s'", info.EffectiveFQDN, token) } - records, err := d.client.GetRecords(context.Background(), domainID) + records, err := d.client.GetRecords(ctx, domainID) if err != nil { return fmt.Errorf("limacity: get records: %w", err) } @@ -175,7 +179,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return errors.New("limacity: TXT record not found") } - err = d.client.DeleteRecord(context.Background(), domainID, recordID) + err = d.client.DeleteRecord(ctx, domainID, recordID) if err != nil { return fmt.Errorf("limacity: delete record (domain ID=%d, record ID=%d): %w", domainID, recordID, err) } diff --git a/providers/dns/linode/linode.go b/providers/dns/linode/linode.go index 25e1b07d1..6e5951d71 100644 --- a/providers/dns/linode/linode.go +++ b/providers/dns/linode/linode.go @@ -130,9 +130,11 @@ func (d *DNSProvider) Timeout() (time.Duration, time.Duration) { // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) - zone, err := d.getHostedZoneInfo(info.EffectiveFQDN) + zone, err := d.getHostedZoneInfo(ctx, info.EffectiveFQDN) if err != nil { return err } @@ -144,22 +146,24 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Type: linodego.RecordTypeTXT, } - _, err = d.client.CreateDomainRecord(context.Background(), zone.domainID, createOpts) + _, err = d.client.CreateDomainRecord(ctx, zone.domainID, createOpts) return err } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) - zone, err := d.getHostedZoneInfo(info.EffectiveFQDN) + zone, err := d.getHostedZoneInfo(ctx, info.EffectiveFQDN) if err != nil { return err } // Get all TXT records for the specified domain. listOpts := linodego.NewListOptions(0, `{"type":"TXT"}`) - resources, err := d.client.ListDomainRecords(context.Background(), zone.domainID, listOpts) + resources, err := d.client.ListDomainRecords(ctx, zone.domainID, listOpts) if err != nil { return err } @@ -168,7 +172,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { for _, resource := range resources { if (resource.Name == dns01.UnFqdn(info.EffectiveFQDN) || resource.Name == zone.resourceName) && resource.Target == info.Value { - if err := d.client.DeleteDomainRecord(context.Background(), zone.domainID, resource.ID); err != nil { + if err := d.client.DeleteDomainRecord(ctx, zone.domainID, resource.ID); err != nil { return err } } @@ -177,7 +181,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } -func (d *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) { +func (d *DNSProvider) getHostedZoneInfo(ctx context.Context, fqdn string) (*hostedZoneInfo, error) { // Lookup the zone that handles the specified FQDN. authZone, err := dns01.FindZoneByFqdn(fqdn) if err != nil { @@ -191,7 +195,7 @@ func (d *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) { } listOpts := linodego.NewListOptions(0, string(filter)) - domains, err := d.client.ListDomains(context.Background(), listOpts) + domains, err := d.client.ListDomains(ctx, listOpts) if err != nil { return nil, err } diff --git a/providers/dns/mijnhost/mijnhost.go b/providers/dns/mijnhost/mijnhost.go index 21aca3c9e..515caa2f6 100644 --- a/providers/dns/mijnhost/mijnhost.go +++ b/providers/dns/mijnhost/mijnhost.go @@ -106,9 +106,11 @@ func (d *DNSProvider) Sequential() time.Duration { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) - domains, err := d.client.ListDomains(context.Background()) + domains, err := d.client.ListDomains(ctx) if err != nil { return fmt.Errorf("mijnhost: list domains: %w", err) } @@ -118,7 +120,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("mijnhost: find domain: %w", err) } - records, err := d.client.GetRecords(context.Background(), dom.Domain) + records, err := d.client.GetRecords(ctx, dom.Domain) if err != nil { return fmt.Errorf("mijnhost: get records: %w", err) } @@ -143,7 +145,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { cleanedRecords = append(cleanedRecords, record) - err = d.client.UpdateRecords(context.Background(), dom.Domain, cleanedRecords) + err = d.client.UpdateRecords(ctx, dom.Domain, cleanedRecords) if err != nil { return fmt.Errorf("mijnhost: update records: %w", err) } @@ -153,9 +155,11 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) - domains, err := d.client.ListDomains(context.Background()) + domains, err := d.client.ListDomains(ctx) if err != nil { return fmt.Errorf("mijnhost: list domains: %w", err) } @@ -165,7 +169,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("mijnhost: find domain: %w", err) } - records, err := d.client.GetRecords(context.Background(), dom.Domain) + records, err := d.client.GetRecords(ctx, dom.Domain) if err != nil { return fmt.Errorf("mijnhost: get records: %w", err) } @@ -174,7 +178,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return record.Type == txtType && record.Value == info.Value }) - err = d.client.UpdateRecords(context.Background(), dom.Domain, cleanedRecords) + err = d.client.UpdateRecords(ctx, dom.Domain, cleanedRecords) if err != nil { return fmt.Errorf("mijnhost: update records: %w", err) } diff --git a/providers/dns/netcup/internal/client_live_test.go b/providers/dns/netcup/internal/client_live_test.go index 3cf6c8c0b..68621882e 100644 --- a/providers/dns/netcup/internal/client_live_test.go +++ b/providers/dns/netcup/internal/client_live_test.go @@ -38,7 +38,7 @@ func TestClient_GetDNSRecords_Live(t *testing.T) { info := dns01.GetChallengeInfo(envTest.GetDomain(), "123d==") zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - require.NoError(t, err, "error finding DNSZone") + require.NoError(t, err) zone = dns01.UnFqdn(zone) @@ -103,7 +103,7 @@ func TestClient_UpdateDNSRecord_Live(t *testing.T) { // Tear down err = client.UpdateDNSRecord(ctx, envTest.GetDomain(), []DNSRecord{records[recordIdx]}) - require.NoError(t, err, "Did not remove record! Please do so yourself.") + require.NoError(t, err) err = client.Logout(ctx) require.NoError(t, err) diff --git a/providers/dns/netcup/netcup_test.go b/providers/dns/netcup/netcup_test.go index f9cc43ab9..a0c631f46 100644 --- a/providers/dns/netcup/netcup_test.go +++ b/providers/dns/netcup/netcup_test.go @@ -164,7 +164,7 @@ func TestLivePresentAndCleanup(t *testing.T) { info := dns01.GetChallengeInfo(envTest.GetDomain(), "123d==") zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - require.NoError(t, err, "error finding DNSZone") + require.NoError(t, err) zone = dns01.UnFqdn(zone) @@ -181,7 +181,7 @@ func TestLivePresentAndCleanup(t *testing.T) { require.NoError(t, err) err = p.CleanUp(test, "987d", "123d==") - require.NoError(t, err, "Did not clean up! Please remove record yourself.") + require.NoError(t, err) }) } } diff --git a/providers/dns/nifcloud/nifcloud.go b/providers/dns/nifcloud/nifcloud.go index ccdadf615..415921b52 100644 --- a/providers/dns/nifcloud/nifcloud.go +++ b/providers/dns/nifcloud/nifcloud.go @@ -108,9 +108,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) - err := d.changeRecord("CREATE", info.EffectiveFQDN, info.Value, d.config.TTL) + err := d.changeRecord(ctx, "CREATE", info.EffectiveFQDN, info.Value, d.config.TTL) if err != nil { return fmt.Errorf("nifcloud: %w", err) } @@ -119,9 +121,11 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) - err := d.changeRecord("DELETE", info.EffectiveFQDN, info.Value, d.config.TTL) + err := d.changeRecord(ctx, "DELETE", info.EffectiveFQDN, info.Value, d.config.TTL) if err != nil { return fmt.Errorf("nifcloud: %w", err) } @@ -134,7 +138,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -func (d *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { +func (d *DNSProvider) changeRecord(ctx context.Context, action, fqdn, value string, ttl int) error { authZone, err := dns01.FindZoneByFqdn(fqdn) if err != nil { return fmt.Errorf("could not find zone: %w", err) @@ -171,8 +175,6 @@ func (d *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { }, } - ctx := context.Background() - resp, err := d.client.ChangeResourceRecordSets(ctx, dns01.UnFqdn(authZone), reqParams) if err != nil { return fmt.Errorf("failed to change record set: %w", err) @@ -180,7 +182,7 @@ func (d *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { statusID := resp.ChangeInfo.ID - return wait.Retry(context.Background(), + return wait.Retry(ctx, func() error { resp, err := d.client.GetChange(ctx, statusID) if err != nil { diff --git a/providers/dns/route53/route53.go b/providers/dns/route53/route53.go index 56cb37cb5..6e66149da 100644 --- a/providers/dns/route53/route53.go +++ b/providers/dns/route53/route53.go @@ -250,7 +250,7 @@ func (d *DNSProvider) changeRecord(ctx context.Context, action awstypes.ChangeAc changeID := resp.ChangeInfo.Id if d.config.WaitForRecordSetsChanged { - return wait.Retry(context.Background(), + return wait.Retry(ctx, func() error { resp, err := d.client.GetChange(ctx, &route53.GetChangeInput{Id: changeID}) if err != nil { diff --git a/providers/dns/route53/route53_test.go b/providers/dns/route53/route53_test.go index a0fac49e8..f9f3b6293 100644 --- a/providers/dns/route53/route53_test.go +++ b/providers/dns/route53/route53_test.go @@ -82,7 +82,7 @@ func Test_getHostedZoneID_FromEnv(t *testing.T) { require.NoError(t, err) hostedZoneID, err := provider.getHostedZoneID(t.Context(), "whatever") - require.NoError(t, err, "HostedZoneID") + require.NoError(t, err) assert.Equal(t, expectedZoneID, hostedZoneID) } @@ -182,7 +182,7 @@ func TestDNSProvider_Present(t *testing.T) { keyAuth := "123456d==" err := provider.Present(domain, "", keyAuth) - require.NoError(t, err, "Expected Present to return no error") + require.NoError(t, err) } func Test_createAWSConfig(t *testing.T) { diff --git a/providers/dns/sakuracloud/sakuracloud.go b/providers/dns/sakuracloud/sakuracloud.go index f12248d42..940b6ac5c 100644 --- a/providers/dns/sakuracloud/sakuracloud.go +++ b/providers/dns/sakuracloud/sakuracloud.go @@ -2,6 +2,7 @@ package sakuracloud import ( + "context" "errors" "fmt" "net/http" @@ -115,7 +116,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - err := d.addTXTRecord(info.EffectiveFQDN, info.Value, d.config.TTL) + err := d.addTXTRecord(context.Background(), info.EffectiveFQDN, info.Value, d.config.TTL) if err != nil { return fmt.Errorf("sakuracloud: %w", err) } @@ -127,7 +128,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - err := d.cleanupTXTRecord(info.EffectiveFQDN, info.Value) + err := d.cleanupTXTRecord(context.Background(), info.EffectiveFQDN, info.Value) if err != nil { return fmt.Errorf("sakuracloud: %w", err) } diff --git a/providers/dns/sakuracloud/wrapper.go b/providers/dns/sakuracloud/wrapper.go index 0898ccd3b..c23a8b590 100644 --- a/providers/dns/sakuracloud/wrapper.go +++ b/providers/dns/sakuracloud/wrapper.go @@ -14,11 +14,11 @@ import ( // see: https://github.com/go-acme/lego/pull/850 var mu sync.Mutex -func (d *DNSProvider) addTXTRecord(fqdn, value string, ttl int) error { +func (d *DNSProvider) addTXTRecord(ctx context.Context, fqdn, value string, ttl int) error { mu.Lock() defer mu.Unlock() - zone, err := d.getHostedZone(fqdn) + zone, err := d.getHostedZone(ctx, fqdn) if err != nil { return err } @@ -35,7 +35,7 @@ func (d *DNSProvider) addTXTRecord(fqdn, value string, ttl int) error { TTL: ttl, }) - _, err = d.client.UpdateSettings(context.Background(), zone.ID, &iaas.DNSUpdateSettingsRequest{ + _, err = d.client.UpdateSettings(ctx, zone.ID, &iaas.DNSUpdateSettingsRequest{ Records: records, SettingsHash: zone.SettingsHash, }) @@ -46,11 +46,11 @@ func (d *DNSProvider) addTXTRecord(fqdn, value string, ttl int) error { return nil } -func (d *DNSProvider) cleanupTXTRecord(fqdn, value string) error { +func (d *DNSProvider) cleanupTXTRecord(ctx context.Context, fqdn, value string) error { mu.Lock() defer mu.Unlock() - zone, err := d.getHostedZone(fqdn) + zone, err := d.getHostedZone(ctx, fqdn) if err != nil { return err } @@ -71,7 +71,7 @@ func (d *DNSProvider) cleanupTXTRecord(fqdn, value string) error { Records: updRecords, SettingsHash: zone.SettingsHash, } - _, err = d.client.UpdateSettings(context.Background(), zone.ID, settings) + _, err = d.client.UpdateSettings(ctx, zone.ID, settings) if err != nil { return fmt.Errorf("API call failed: %w", err) } @@ -79,7 +79,7 @@ func (d *DNSProvider) cleanupTXTRecord(fqdn, value string) error { return nil } -func (d *DNSProvider) getHostedZone(domain string) (*iaas.DNS, error) { +func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*iaas.DNS, error) { authZone, err := dns01.FindZoneByFqdn(domain) if err != nil { return nil, fmt.Errorf("could not find zone: %w", err) @@ -93,7 +93,7 @@ func (d *DNSProvider) getHostedZone(domain string) (*iaas.DNS, error) { }, } - res, err := d.client.Find(context.Background(), conditions) + res, err := d.client.Find(ctx, conditions) if err != nil { if iaas.IsNotFoundError(err) { return nil, fmt.Errorf("zone %s not found on SakuraCloud DNS: %w", zoneName, err) diff --git a/providers/dns/sakuracloud/wrapper_test.go b/providers/dns/sakuracloud/wrapper_test.go index 15eb19618..958ef1b88 100644 --- a/providers/dns/sakuracloud/wrapper_test.go +++ b/providers/dns/sakuracloud/wrapper_test.go @@ -64,10 +64,12 @@ func TestDNSProvider_addAndCleanupRecords(t *testing.T) { require.NoError(t, err) t.Run("addTXTRecord", func(t *testing.T) { - err = p.addTXTRecord("test.example.com.", "dummyValue", 10) + ctx := t.Context() + + err = p.addTXTRecord(ctx, "test.example.com.", "dummyValue", 10) require.NoError(t, err) - updZone, e := p.getHostedZone("test.example.com.") + updZone, e := p.getHostedZone(ctx, "test.example.com.") require.NoError(t, e) require.NotNil(t, updZone) @@ -75,10 +77,12 @@ func TestDNSProvider_addAndCleanupRecords(t *testing.T) { }) t.Run("cleanupTXTRecord", func(t *testing.T) { - err = p.cleanupTXTRecord("test.example.com.", "dummyValue") + ctx := t.Context() + + err = p.cleanupTXTRecord(ctx, "test.example.com.", "dummyValue") require.NoError(t, err) - updZone, e := p.getHostedZone("test.example.com.") + updZone, e := p.getHostedZone(ctx, "test.example.com.") require.NoError(t, e) require.NotNil(t, updZone) @@ -108,9 +112,11 @@ func TestDNSProvider_concurrentAddAndCleanupRecords(t *testing.T) { t.Run("addTXTRecord", func(t *testing.T) { wg.Add(len(providers)) + ctx := t.Context() + for i, p := range providers { go func(j int, client *DNSProvider) { - err := client.addTXTRecord(fmt.Sprintf("test%d.example.com.", j), "dummyValue", 10) + err := client.addTXTRecord(ctx, fmt.Sprintf("test%d.example.com.", j), "dummyValue", 10) require.NoError(t, err) wg.Done() }(i, p) @@ -118,7 +124,7 @@ func TestDNSProvider_concurrentAddAndCleanupRecords(t *testing.T) { wg.Wait() - updZone, err := providers[0].getHostedZone("example.com.") + updZone, err := providers[0].getHostedZone(ctx, "example.com.") require.NoError(t, err) require.NotNil(t, updZone) @@ -128,9 +134,11 @@ func TestDNSProvider_concurrentAddAndCleanupRecords(t *testing.T) { t.Run("cleanupTXTRecord", func(t *testing.T) { wg.Add(len(providers)) + ctx := t.Context() + for i, p := range providers { go func(i int, client *DNSProvider) { - err := client.cleanupTXTRecord(fmt.Sprintf("test%d.example.com.", i), "dummyValue") + err := client.cleanupTXTRecord(ctx, fmt.Sprintf("test%d.example.com.", i), "dummyValue") require.NoError(t, err) wg.Done() }(i, p) @@ -138,7 +146,7 @@ func TestDNSProvider_concurrentAddAndCleanupRecords(t *testing.T) { wg.Wait() - updZone, err := providers[0].getHostedZone("example.com.") + updZone, err := providers[0].getHostedZone(ctx, "example.com.") require.NoError(t, err) require.NotNil(t, updZone) diff --git a/providers/dns/variomedia/variomedia.go b/providers/dns/variomedia/variomedia.go index f8254335c..67d7b9a50 100644 --- a/providers/dns/variomedia/variomedia.go +++ b/providers/dns/variomedia/variomedia.go @@ -180,7 +180,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } func (d *DNSProvider) waitJob(ctx context.Context, domain, id string) error { - return wait.Retry(context.Background(), + return wait.Retry(ctx, func() error { result, err := d.client.GetJob(ctx, id) if err != nil { diff --git a/providers/dns/vinyldns/vinyldns.go b/providers/dns/vinyldns/vinyldns.go index 9e36ccc51..098347af4 100644 --- a/providers/dns/vinyldns/vinyldns.go +++ b/providers/dns/vinyldns/vinyldns.go @@ -2,6 +2,7 @@ package vinyldns import ( + "context" "errors" "fmt" "strconv" @@ -103,6 +104,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) existingRecord, err := d.getRecordSet(info.EffectiveFQDN) @@ -115,7 +118,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { record := vinyldns.Record{Text: value} if existingRecord == nil || existingRecord.ID == "" { - err = d.createRecordSet(info.EffectiveFQDN, []vinyldns.Record{record}) + err = d.createRecordSet(ctx, info.EffectiveFQDN, []vinyldns.Record{record}) if err != nil { return fmt.Errorf("vinyldns: %w", err) } @@ -132,7 +135,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { records := existingRecord.Records records = append(records, record) - err = d.updateRecordSet(existingRecord, records) + err = d.updateRecordSet(ctx, existingRecord, records) if err != nil { return fmt.Errorf("vinyldns: %w", err) } @@ -142,6 +145,8 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) existingRecord, err := d.getRecordSet(info.EffectiveFQDN) @@ -163,7 +168,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } if len(records) == 0 { - err = d.deleteRecordSet(existingRecord) + err = d.deleteRecordSet(ctx, existingRecord) if err != nil { return fmt.Errorf("vinyldns: %w", err) } @@ -171,7 +176,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } - err = d.updateRecordSet(existingRecord, records) + err = d.updateRecordSet(ctx, existingRecord, records) if err != nil { return fmt.Errorf("vinyldns: %w", err) } diff --git a/providers/dns/vinyldns/wrapper.go b/providers/dns/vinyldns/wrapper.go index 84d6e20a6..e247fd265 100644 --- a/providers/dns/vinyldns/wrapper.go +++ b/providers/dns/vinyldns/wrapper.go @@ -43,7 +43,7 @@ func (d *DNSProvider) getRecordSet(fqdn string) (*vinyldns.RecordSet, error) { } } -func (d *DNSProvider) createRecordSet(fqdn string, records []vinyldns.Record) error { +func (d *DNSProvider) createRecordSet(ctx context.Context, fqdn string, records []vinyldns.Record) error { zoneName, hostName, err := splitDomain(fqdn) if err != nil { return err @@ -67,10 +67,10 @@ func (d *DNSProvider) createRecordSet(fqdn string, records []vinyldns.Record) er return err } - return d.waitForChanges("CreateRS", resp) + return d.waitForChanges(ctx, "CreateRS", resp) } -func (d *DNSProvider) updateRecordSet(recordSet *vinyldns.RecordSet, newRecords []vinyldns.Record) error { +func (d *DNSProvider) updateRecordSet(ctx context.Context, recordSet *vinyldns.RecordSet, newRecords []vinyldns.Record) error { operation := "delete" if len(recordSet.Records) < len(newRecords) { operation = "add" @@ -84,20 +84,20 @@ func (d *DNSProvider) updateRecordSet(recordSet *vinyldns.RecordSet, newRecords return err } - return d.waitForChanges("UpdateRS - "+operation, resp) + return d.waitForChanges(ctx, "UpdateRS - "+operation, resp) } -func (d *DNSProvider) deleteRecordSet(existingRecord *vinyldns.RecordSet) error { +func (d *DNSProvider) deleteRecordSet(ctx context.Context, existingRecord *vinyldns.RecordSet) error { resp, err := d.client.RecordSetDelete(existingRecord.ZoneID, existingRecord.ID) if err != nil { return err } - return d.waitForChanges("DeleteRS", resp) + return d.waitForChanges(ctx, "DeleteRS", resp) } -func (d *DNSProvider) waitForChanges(operation string, resp *vinyldns.RecordSetUpdateResponse) error { - return wait.Retry(context.Background(), +func (d *DNSProvider) waitForChanges(ctx context.Context, operation string, resp *vinyldns.RecordSetUpdateResponse) error { + return wait.Retry(ctx, func() error { change, err := d.client.RecordSetChange(resp.Zone.ID, resp.RecordSet.ID, resp.ChangeID) if err != nil { From 2eb76de5a0cf98342f00e0d74d222321f2bbce8c Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 16 Oct 2025 20:49:25 +0200 Subject: [PATCH 183/298] chore: update dependencies (#2673) --- go.mod | 104 +++++++++++++-------------- go.sum | 216 ++++++++++++++++++++++++++++----------------------------- 2 files changed, 160 insertions(+), 160 deletions(-) diff --git a/go.mod b/go.mod index e964ed1d6..d99e9ff70 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/go-acme/lego/v4 go 1.24.0 require ( - cloud.google.com/go/compute/metadata v0.8.0 + cloud.google.com/go/compute/metadata v0.9.0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 @@ -16,25 +16,25 @@ require ( github.com/BurntSushi/toml v1.5.0 github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 - github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.12 - github.com/alibabacloud-go/tea v1.3.12 + github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 + github.com/alibabacloud-go/tea v1.3.13 github.com/aliyun/credentials-go v1.4.7 - github.com/aws/aws-sdk-go-v2 v1.39.0 - github.com/aws/aws-sdk-go-v2/config v1.31.8 - github.com/aws/aws-sdk-go-v2/credentials v1.18.12 - github.com/aws/aws-sdk-go-v2/service/lightsail v1.48.4 - github.com/aws/aws-sdk-go-v2/service/route53 v1.58.2 - github.com/aws/aws-sdk-go-v2/service/s3 v1.88.1 - github.com/aws/aws-sdk-go-v2/service/sts v1.38.4 - github.com/aziontech/azionapi-go-sdk v0.142.0 - github.com/baidubce/bce-sdk-go v0.9.243 + github.com/aws/aws-sdk-go-v2 v1.39.2 + github.com/aws/aws-sdk-go-v2/config v1.31.12 + github.com/aws/aws-sdk-go-v2/credentials v1.18.16 + github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.0 + github.com/aws/aws-sdk-go-v2/service/route53 v1.58.4 + github.com/aws/aws-sdk-go-v2/service/s3 v1.88.4 + github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 + github.com/aziontech/azionapi-go-sdk v0.143.0 + github.com/baidubce/bce-sdk-go v0.9.248 github.com/cenkalti/backoff/v5 v5.0.3 github.com/dnsimple/dnsimple-go/v4 v4.0.0 - github.com/exoscale/egoscale/v3 v3.1.26 - github.com/go-acme/alidns-20150109/v4 v4.6.0 + github.com/exoscale/egoscale/v3 v3.1.27 + github.com/go-acme/alidns-20150109/v4 v4.6.1 github.com/go-acme/tencentclouddnspod v1.1.10 github.com/go-acme/tencentedgdeone v1.1.19 - github.com/go-jose/go-jose/v4 v4.1.2 + github.com/go-jose/go-jose/v4 v4.1.3 github.com/go-viper/mapstructure/v2 v2.4.0 github.com/google/go-cmp v0.7.0 github.com/google/go-querystring v1.1.0 @@ -42,12 +42,12 @@ require ( github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/go-version v1.7.0 - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.168 + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.172 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 github.com/labbsr0x/bindman-dns-webhook v1.0.2 - github.com/ldez/grignotin v0.10.0 - github.com/linode/linodego v1.57.0 + github.com/ldez/grignotin v0.10.1 + github.com/linode/linodego v1.60.0 github.com/liquidweb/liquidweb-go v1.6.4 github.com/mattn/go-isatty v0.0.20 github.com/miekg/dns v1.1.68 @@ -63,8 +63,8 @@ require ( github.com/nrdcg/mailinabox v0.2.0 github.com/nrdcg/namesilo v0.5.0 github.com/nrdcg/nodion v0.1.0 - github.com/nrdcg/oci-go-sdk/common/v1065 v1065.100.0 - github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.100.0 + github.com/nrdcg/oci-go-sdk/common/v1065 v1065.102.0 + github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.102.0 github.com/nrdcg/porkbun v0.4.0 github.com/nzdjb/go-metaname v1.0.0 github.com/ovh/go-ovh v1.9.0 @@ -72,35 +72,35 @@ require ( 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.3.3 - github.com/sacloud/iaas-api-go v1.17.1 - github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34 + github.com/sacloud/iaas-api-go v1.19.0 + github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35 github.com/selectel/domains-go v1.1.0 github.com/selectel/go-selvpcclient/v4 v4.1.0 github.com/softlayer/softlayer-go v1.2.1 github.com/stretchr/testify v1.11.1 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.26 - github.com/transip/gotransip/v6 v6.26.0 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.41 + github.com/transip/gotransip/v6 v6.26.1 github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 github.com/urfave/cli/v2 v2.27.7 github.com/vinyldns/go-vinyldns v0.9.16 - github.com/volcengine/volc-sdk-golang v1.0.219 - github.com/vultr/govultr/v3 v3.23.0 - github.com/yandex-cloud/go-genproto v0.23.0 + github.com/volcengine/volc-sdk-golang v1.0.223 + github.com/vultr/govultr/v3 v3.24.0 + github.com/yandex-cloud/go-genproto v0.31.0 github.com/yandex-cloud/go-sdk/services/dns v0.0.12 - github.com/yandex-cloud/go-sdk/v2 v2.11.0 - golang.org/x/crypto v0.42.0 - golang.org/x/net v0.44.0 - golang.org/x/oauth2 v0.31.0 - golang.org/x/text v0.29.0 - golang.org/x/time v0.13.0 - google.golang.org/api v0.249.0 + github.com/yandex-cloud/go-sdk/v2 v2.19.0 + golang.org/x/crypto v0.43.0 + golang.org/x/net v0.46.0 + golang.org/x/oauth2 v0.32.0 + golang.org/x/text v0.30.0 + golang.org/x/time v0.14.0 + google.golang.org/api v0.252.0 gopkg.in/ns1/ns1-go.v2 v2.15.0 gopkg.in/yaml.v2 v2.4.0 software.sslmate.com/src/go-pkcs12 v0.6.0 ) require ( - cloud.google.com/go/auth v0.16.5 // indirect + cloud.google.com/go/auth v0.17.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect @@ -110,23 +110,23 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect github.com/alibabacloud-go/debug v1.0.1 // indirect github.com/alibabacloud-go/openapi-util v0.1.1 // indirect github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.29.3 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 // indirect github.com/aws/smithy-go v1.23.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect @@ -209,15 +209,15 @@ require ( go.uber.org/ratelimit v0.3.1 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect - golang.org/x/mod v0.27.0 // indirect + golang.org/x/mod v0.28.0 // indirect golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/tools v0.37.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect - google.golang.org/grpc v1.75.0 // indirect - google.golang.org/protobuf v1.36.8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // indirect + google.golang.org/grpc v1.75.1 // indirect + google.golang.org/protobuf v1.36.10 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9d7e27c76..606cecb72 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,8 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI= -cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ= +cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= +cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -23,8 +23,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA= -cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -44,8 +44,8 @@ github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0 h1:MhRfI58HblXzCtWEZCO0feHs8LweePB3s90r7WaR1KU= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.0/go.mod h1:okZ+ZURbArNdlJ+ptXoyHNuOETzOl1Oww19rm8I2WLA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 h1:KpMC6LFL7mqpExyMC9jVOYRiVhLmamjeZfRsUpB7l4s= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= @@ -85,8 +85,8 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 h1:XkkQbfMyuH2jTSjQjSoihryI8GINRcs4xp8lNawg0FI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= @@ -123,9 +123,9 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= -github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.11/go.mod h1:ue0+WkdPxpCB2JP3iaG4Iawayxp72kyT5uDbozQKaW8= -github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.12 h1:e2yCrhtWd6Qcsy4he2OL+jIAU+93Lx9OcLlPRoFLT1w= github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.12/go.mod h1:f2wDpbM7hK9SvLIH09zSKVU1TsyemUNOqErMscMMl7c= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 h1:Q00FU3H94Ts0ZIHDmY+fYGgB7dV9D/YX6FGsgorQPgw= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE= github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= @@ -146,9 +146,9 @@ github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/Ke github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= -github.com/alibabacloud-go/tea v1.3.11/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= -github.com/alibabacloud-go/tea v1.3.12 h1:ir2Io80UlBy1JHf7t+uCTxmaGQtiEta1WpV29NGJTkE= github.com/alibabacloud-go/tea v1.3.12/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= +github.com/alibabacloud-go/tea v1.3.13 h1:WhGy6LIXaMbBM6VBYcsDCz6K/TPsT1Ri2hPmmZffZ94= +github.com/alibabacloud-go/tea v1.3.13/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= @@ -172,52 +172,52 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.39.0 h1:xm5WV/2L4emMRmMjHFykqiA4M/ra0DJVSWUkDyBjbg4= -github.com/aws/aws-sdk-go-v2 v1.39.0/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= +github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I= +github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00= -github.com/aws/aws-sdk-go-v2/config v1.31.8 h1:kQjtOLlTU4m4A64TsRcqwNChhGCwaPBt+zCQt/oWsHU= -github.com/aws/aws-sdk-go-v2/config v1.31.8/go.mod h1:QPpc7IgljrKwH0+E6/KolCgr4WPLerURiU592AYzfSY= -github.com/aws/aws-sdk-go-v2/credentials v1.18.12 h1:zmc9e1q90wMn8wQbjryy8IwA6Q4XlaL9Bx2zIqdNNbk= -github.com/aws/aws-sdk-go-v2/credentials v1.18.12/go.mod h1:3VzdRDR5u3sSJRI4kYcOSIBbeYsgtVk7dG5R/U6qLWY= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 h1:Is2tPmieqGS2edBnmOJIbdvOA6Op+rRpaYR60iBAwXM= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7/go.mod h1:F1i5V5421EGci570yABvpIXgRIBPb5JM+lSkHF6Dq5w= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 h1:UCxq0X9O3xrlENdKf1r9eRJoKz/b0AfGkpp3a7FPlhg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7/go.mod h1:rHRoJUNUASj5Z/0eqI4w32vKvC7atoWR0jC+IkmVH8k= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 h1:Y6DTZUn7ZUC4th9FMBbo8LVE+1fyq3ofw+tRwkUd3PY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7/go.mod h1:x3XE6vMnU9QvHN/Wrx2s44kwzV2o2g5x/siw4ZUJ9g8= +github.com/aws/aws-sdk-go-v2/config v1.31.12 h1:pYM1Qgy0dKZLHX2cXslNacbcEFMkDMl+Bcj5ROuS6p8= +github.com/aws/aws-sdk-go-v2/config v1.31.12/go.mod h1:/MM0dyD7KSDPR+39p9ZNVKaHDLb9qnfDurvVS2KAhN8= +github.com/aws/aws-sdk-go-v2/credentials v1.18.16 h1:4JHirI4zp958zC026Sm+V4pSDwW4pwLefKrc0bF2lwI= +github.com/aws/aws-sdk-go-v2/credentials v1.18.16/go.mod h1:qQMtGx9OSw7ty1yLclzLxXCRbrkjWAM7JnObZjmCB7I= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 h1:Mv4Bc0mWmv6oDuSWTKnk+wgeqPL5DRFu5bQL9BGPQ8Y= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9/go.mod h1:IKlKfRppK2a1y0gy1yH6zD+yX5uplJ6UuPlgd48dJiQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 h1:se2vOWGD3dWQUtfn4wEjRQJb1HK1XsNIt825gskZ970= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9/go.mod h1:hijCGH2VfbZQxqCDN7bwz/4dzxV+hkyhjawAtdPWKZA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 h1:6RBnKZLkJM4hQ+kN6E7yWFveOTg8NLPHAkqrs4ZPlTU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9/go.mod h1:V9rQKRmK7AWuEsOMnHzKj8WyrIir1yUJbZxDuZLFvXI= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7 h1:BszAktdUo2xlzmYHjWMq70DqJ7cROM8iBd3f6hrpuMQ= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7/go.mod h1:XJ1yHki/P7ZPuG4fd3f0Pg/dSGA2cTQBCLw82MH2H48= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9 h1:w9LnHqTq8MEdlnyhV4Bwfizd65lfNCNgdlNC6mM5paE= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9/go.mod h1:LGEP6EK4nj+bwWNdrvX/FnDTFowdBNwcSPuZu/ouFys= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7 h1:zmZ8qvtE9chfhBPuKB2aQFxW5F/rpwXUgmcVCgQzqRw= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7/go.mod h1:vVYfbpd2l+pKqlSIDIOgouxNsGu5il9uDp0ooWb0jys= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 h1:mLgc5QIgOy26qyh5bvW+nDoAppxgn3J2WV3m9ewq7+8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7/go.mod h1:wXb/eQnqt8mDQIQTTmcw58B5mYGxzLGZGK8PWNFZ0BA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7 h1:u3VbDKUCWarWiU+aIUK4gjTr/wQFXV17y3hgNno9fcA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7/go.mod h1:/OuMQwhSyRapYxq6ZNpPer8juGNrB4P5Oz8bZ2cgjQE= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.48.4 h1:rJjEP5CSJw3Xsoe5Lvhbvr5P8q+rdt8/5IL2MDCc5n0= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.48.4/go.mod h1:O5Ew7rQ2iERj/HtA0AxBWymP0UVcG4iuMoIQzbRhcZU= -github.com/aws/aws-sdk-go-v2/service/route53 v1.58.2 h1:uqxTxY0i8b1ZFHxIf6pZYpUCOuYV/xxcgTv0vDz8Iig= -github.com/aws/aws-sdk-go-v2/service/route53 v1.58.2/go.mod h1:py/7C8W37SHqyHk6tkvZKiFDvMA/WkfPv5Qd8dUXYQw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.88.1 h1:+RpGuaQ72qnU83qBKVwxkznewEdAGhIWo/PQCmkhhog= -github.com/aws/aws-sdk-go-v2/service/s3 v1.88.1/go.mod h1:xajPTguLoeQMAOE44AAP2RQoUhF8ey1g5IFHARv71po= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.3 h1:7PKX3VYsZ8LUWceVRuv0+PU+E7OtQb1lgmi5vmUE9CM= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.3/go.mod h1:Ql6jE9kyyWI5JHn+61UT/Y5Z0oyVJGmgmJbZD5g4unY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.4 h1:e0XBRn3AptQotkyBFrHAxFB8mDhAIOfsG+7KyJ0dg98= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.4/go.mod h1:XclEty74bsGBCr1s0VSaA11hQ4ZidK4viWK7rRfO88I= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.4 h1:PR00NXRYgY4FWHqOGx3fC3lhVKjsp1GdloDv2ynMSd8= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.4/go.mod h1:Z+Gd23v97pX9zK97+tX4ppAgqCt3Z2dIXB02CtBncK8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.0 h1:X0FveUndcZ3lKbSpIC6rMYGRiQTcUVRNH6X4yYtIrlU= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.0/go.mod h1:IWjQYlqw4EX9jw2g3qnEPPWvCE6bS8fKzhMed1OK7c8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 h1:5r34CgVOD4WZudeEKZ9/iKpiT6cM1JyEROpXjOcdWv8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9/go.mod h1:dB12CEbNWPbzO2uC6QSWHteqOg4JfBVJOojbAoAUb5I= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9 h1:wuZ5uW2uhJR63zwNlqWH2W4aL4ZjeJP3o92/W+odDY4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9/go.mod h1:/G58M2fGszCrOzvJUkDdY8O9kycodunH4VdT5oBAqls= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.0 h1:JOLRYFWMMKUABCp94HHfo0JBVQDVTLXOvWWphjpBBiQ= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.0/go.mod h1:WEOSRNyfIfvgrD9MuSIGrogKyuFahaVMziVq1pHI0NQ= +github.com/aws/aws-sdk-go-v2/service/route53 v1.58.4 h1:KycXrohD5OxAZ5h02YechO2gevvoHfAPAaJM5l8zqb0= +github.com/aws/aws-sdk-go-v2/service/route53 v1.58.4/go.mod h1:xNLZLn4SusktBQ5moqUOgiDKGz3a7vHwF4W0KD+WBPc= +github.com/aws/aws-sdk-go-v2/service/s3 v1.88.4 h1:mUI3b885qJgfqKDUSj6RgbRqLdX0wGmg8ruM03zNfQA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.88.4/go.mod h1:6v8ukAxc7z4x4oBjGUsLnH7KGLY9Uhcgij19UJNkiMg= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 h1:A1oRkiSQOWstGh61y4Wc/yQ04sqrQZr1Si/oAXj20/s= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.6/go.mod h1:5PfYspyCU5Vw1wNPsxi15LZovOnULudOQuVxphSflQA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 h1:5fm5RTONng73/QA73LhCNR7UT9RpFH3hR6HWL6bIgVY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1/go.mod h1:xBEjWD13h+6nq+z4AkqSfSvqRKFgDIQeaMguAJndOWo= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 h1:p3jIvqYwUZgu/XYeI48bJxOhvm47hZb5HUQ0tn6Q9kA= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.6/go.mod h1:WtKK+ppze5yKPkZ0XwqIVWD4beCwv056ZbPQNoeHqM8= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= -github.com/aziontech/azionapi-go-sdk v0.142.0 h1:1NOHXlC0/7VgbfbTIGVpsYn1THCugM4/SCOXVdUHQ+A= -github.com/aziontech/azionapi-go-sdk v0.142.0/go.mod h1:cA5DY/VP4X5Eu11LpQNzNn83ziKjja7QVMIl4J45feA= -github.com/baidubce/bce-sdk-go v0.9.243 h1:6/yb519gFiABE6U1qbVZzBmEonfEGj5qcXrmIkbkFyQ= -github.com/baidubce/bce-sdk-go v0.9.243/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/aziontech/azionapi-go-sdk v0.143.0 h1:4eEBlYT10prgeCVTNR9FIc7f59Crbl2zrH1a4D1BUqU= +github.com/aziontech/azionapi-go-sdk v0.143.0/go.mod h1:cA5DY/VP4X5Eu11LpQNzNn83ziKjja7QVMIl4J45feA= +github.com/baidubce/bce-sdk-go v0.9.248 h1:vB5OMuEC2xnO197M6OWUi24C8mYOZHKXT/8HuKQJUhU= +github.com/baidubce/bce-sdk-go v0.9.248/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -286,8 +286,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/exoscale/egoscale/v3 v3.1.26 h1:bXXT0zVLbE4QFm6tmt0bg6ZPk9pQgUA3Z8SJrctQ7b0= -github.com/exoscale/egoscale/v3 v3.1.26/go.mod h1:0iY8OxgHJCS5TKqDNhwOW95JBKCnBZl3YGU4Yt+NqkU= +github.com/exoscale/egoscale/v3 v3.1.27 h1:vKdWZG8QFDc7rY7lCfcuudO+ovyp5psYjFwKVqmkhCE= +github.com/exoscale/egoscale/v3 v3.1.27/go.mod h1:0iY8OxgHJCS5TKqDNhwOW95JBKCnBZl3YGU4Yt+NqkU= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= @@ -314,8 +314,8 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-acme/alidns-20150109/v4 v4.6.0 h1:1w0AZXlroykumr83vfxdYcM8L8bFWkV6SbqhxElzT4w= -github.com/go-acme/alidns-20150109/v4 v4.6.0/go.mod h1:qCK/gDRcJZrr5D8OIWuUugECsrdrJZnLXjWF7Mqi91g= +github.com/go-acme/alidns-20150109/v4 v4.6.1 h1:Dch3aWRcw4U62+jKPjPQN3iW3TPvgIywATbvHzojXeo= +github.com/go-acme/alidns-20150109/v4 v4.6.1/go.mod h1:RBcqBA5IvUWtlpjx6dC6EkPVyBNLQ+mR18XoaP38BFY= github.com/go-acme/tencentclouddnspod v1.1.10 h1:ERVJ4mc3cT4Nb3+n6H/c1AwZnChGBqLoymE0NVYscKI= github.com/go-acme/tencentclouddnspod v1.1.10/go.mod h1:Bo/0YQJ/99FM+44HmCQkByuptX1tJsJ9V14MGV/2Qco= github.com/go-acme/tencentedgdeone v1.1.19 h1:1jdEpMITrDuXHnu7QLOy2hpLW0BlDof70/KuRT+EiTo= @@ -326,8 +326,8 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= -github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= @@ -531,8 +531,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.168 h1:CQcnff1kIag7rq12IcdTsF0xIW6WJoUx4MqNnDib420= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.168/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.172 h1:68VUVbLKwBxPh8tjCXwnLO/d8/thdZ+ExpxdFMEdK5A= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.172/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -601,13 +601,13 @@ github.com/labbsr0x/bindman-dns-webhook v1.0.2 h1:I7ITbmQPAVwrDdhd6dHKi+MYJTJqPC github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA= github.com/labbsr0x/goh v1.0.1 h1:97aBJkDjpyBZGPbQuOK5/gHcSFbcr5aRsq3RSRJFpPk= github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w= -github.com/ldez/grignotin v0.10.0 h1:NQPeh1E/Eza4F0exCeC1WkpnLvgUcQDT8MQ1vOLML0E= -github.com/ldez/grignotin v0.10.0/go.mod h1:oR4iCKUP9fwoeO6vCQeD7M5SMxCT6xdVas4vg0h1LaI= +github.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o= +github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/linode/linodego v1.57.0 h1:B5cl2gRNtaY1TIQ7B4uAhDa8NjtiWdEnWUO8nkHaU0A= -github.com/linode/linodego v1.57.0/go.mod h1:7zol1dqpLRdAT9s04mIaUhA+rXaRERFnKBERcQZiEeg= +github.com/linode/linodego v1.60.0 h1:SgsebJFRCi+lSmYy+C40wmKZeJllGGm+W12Qw4+yVdI= +github.com/linode/linodego v1.60.0/go.mod h1:1+Bt0oTz5rBnDOJbGhccxn7LYVytXTIIfAy7QYmijDs= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= @@ -705,10 +705,10 @@ github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE= github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.100.0 h1:QvgOjQ7QW8M4f+FeRr43looh/hiQP2y6RRVHsuPu5YI= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.100.0/go.mod h1:sOWH1Rqtipy3kyrIER0JLge8O7n8pIr7G8UVEs6xaDY= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.100.0 h1:TvZNfl2qbBNxtLWbdNMLM02Ygukj7VW3IVsay0FxwRo= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.100.0/go.mod h1:wVoSAX7BxmHUJX4FZ1E9hyxtoupsCUfE4nQeoZs+GSQ= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.102.0 h1:W28ZizQSS2aRWkFA3iAP9eiZS4OLFaiv35nXtq2lW/s= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.102.0/go.mod h1:cVbzGjRhtXgrduaQbR1GR1x+VDU60NcXPMZ3+eQuiiY= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.102.0 h1:gAOs1dkE7LFoWflzqrDqAhOprc0kF1a0fyV8C4HUPj4= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.102.0/go.mod h1:EUBSYwop1K40VpcKy1haIK6kFK/gPT1atEk89OkY0Kg= github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -804,8 +804,8 @@ github.com/regfish/regfish-dnsapi-go v0.1.1/go.mod h1:ubIgXSfqarSnl3XHSn8hIFwFF3 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= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -814,16 +814,16 @@ github.com/sacloud/api-client-go v0.3.3 h1:ZpSAyGpITA8UFO3Hq4qMHZLGuNI1FgxAxo4sq github.com/sacloud/api-client-go v0.3.3/go.mod h1:0p3ukcWYXRCc2AUWTl1aA+3sXLvurvvDqhRaLZRLBwo= github.com/sacloud/go-http v0.1.9 h1:Xa5PY8/pb7XWhwG9nAeXSrYXPbtfBWqawgzxD5co3VE= github.com/sacloud/go-http v0.1.9/go.mod h1:DpDG+MSyxYaBwPJ7l3aKLMzwYdTVtC5Bo63HActcgoE= -github.com/sacloud/iaas-api-go v1.17.1 h1:DeNbmyjNITHZUo2ZLWhxALUKjoZ7zih7M0BvwYbwHWo= -github.com/sacloud/iaas-api-go v1.17.1/go.mod h1:T3SG9JXPH7XPfxhNA+G81WLeujmCdlh5dgTD8OsAj1g= +github.com/sacloud/iaas-api-go v1.19.0 h1:Bw4uygqukcvlblhWrITp94nikXqy2fnKoAC6929LkIA= +github.com/sacloud/iaas-api-go v1.19.0/go.mod h1:XV995RM1I7k5AHb7UZrCVyDF/8bZXDxa+uk1EXoj/Zs= github.com/sacloud/packages-go v0.0.11 h1:hrRWLmfPM9w7GBs6xb5/ue6pEMl8t1UuDKyR/KfteHo= github.com/sacloud/packages-go v0.0.11/go.mod h1:XNF5MCTWcHo9NiqWnYctVbASSSZR3ZOmmQORIzcurJ8= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34 h1:48+VFHsyVcAHIN2v1Ao9v1/RkjJS5AwctFucBrfYNIA= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34/go.mod h1:zFWiHphneiey3s8HOtAEnGrRlWivNaxW5T6d5Xfco7g= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35 h1:8xfn1RzeI9yoCUuEwDy08F+No6PcKZGEDOQ6hrRyLts= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35/go.mod h1:47B1d/YXmSAxlJxUJxClzHR6b3T4M1WyCvwENPQNBWc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/selectel/domains-go v1.1.0 h1:futG50J43ALLKQAnZk9H9yOtLGnSUh7c5hSvuC5gSHo= github.com/selectel/domains-go v1.1.0/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA= @@ -903,14 +903,14 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.10/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.19/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.26 h1:h8/jVLNbsLFQczGX6NDp1GcJahA81ytDj9R/wrq0i8k= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.26/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.41 h1:XQDGrLX6v4McMP+2myhgQcy5JaPqSgwpLM1qa7ngUII= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.41/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/transip/gotransip/v6 v6.26.0 h1:Aejfvh8rSp8Mj2GX/RpdBjMCv+Iy/DmgfNgczPDP550= -github.com/transip/gotransip/v6 v6.26.0/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s= +github.com/transip/gotransip/v6 v6.26.1 h1:MeqIjkTBBsZwWAK6giZyMkqLmKMclVHEuTNmoBdx4MA= +github.com/transip/gotransip/v6 v6.26.1/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 h1:/VaznPrb/b68e3iMvkr27fU7JqPKU4j7tIITZnjQX1k= github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419/go.mod h1:QN0/PdenvYWB0GRMz6JJbPeZz2Lph2iys1p8AFVHm2c= @@ -919,10 +919,10 @@ github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= -github.com/volcengine/volc-sdk-golang v1.0.219 h1:IqMCdpJ6uuqS2ZZQYUVHKVd+2H1au0NDsSt0wx6hv9k= -github.com/volcengine/volc-sdk-golang v1.0.219/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= -github.com/vultr/govultr/v3 v3.23.0 h1:0jZo4FI+oMkPXFez1bvhsb5Ql0EZUFbe3SNLq3d8IIY= -github.com/vultr/govultr/v3 v3.23.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= +github.com/volcengine/volc-sdk-golang v1.0.223 h1:1EEK6VOUaA2Tu0VBD4VC5iSTTFag+KuNo+Vix469Tz4= +github.com/volcengine/volc-sdk-golang v1.0.223/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= +github.com/vultr/govultr/v3 v3.24.0 h1:fTTTj0VBve+Miy+wGhlb90M2NMDfpGFi6Frlj3HVy6M= +github.com/vultr/govultr/v3 v3.24.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= @@ -931,12 +931,12 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yandex-cloud/go-genproto v0.23.0 h1:7nnb/o//eK4ZDd3wCuhWn2DoY1U2+dcUTJosoY49h0M= -github.com/yandex-cloud/go-genproto v0.23.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-genproto v0.31.0 h1:mFMS5SD1Lt8qErwefR8ChK3d0jg0tvbDLq57IqenpTg= +github.com/yandex-cloud/go-genproto v0.31.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= github.com/yandex-cloud/go-sdk/services/dns v0.0.12 h1:c5TNaX7r3DqY37YJFbr7HyQFRcSe1WzCbR81LVwxXyk= github.com/yandex-cloud/go-sdk/services/dns v0.0.12/go.mod h1:6CRtIkxq6iTSZIOT42EFns54CEr35ncECy4ix9lXUd4= -github.com/yandex-cloud/go-sdk/v2 v2.11.0 h1:K3Z0IyKkhEnvcDa0NUKm9ArB0yDL7Nr1rgxTz8W2VH0= -github.com/yandex-cloud/go-sdk/v2 v2.11.0/go.mod h1:aiib58mIEY7YiKbpViYZJtBvMjcskAvZ5w/TFshPRCg= +github.com/yandex-cloud/go-sdk/v2 v2.19.0 h1:Cuzjn6kkOD/KrBF/QyDbKS7b5GAu8fC2ZUjdBjit60A= +github.com/yandex-cloud/go-sdk/v2 v2.19.0/go.mod h1:4SwghU8RB4v2OQzhESgq5SF8XmCXIP80WhgrrNpetJ8= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= @@ -1028,8 +1028,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1073,8 +1073,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1132,8 +1132,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1141,8 +1141,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= -golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1246,8 +1246,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1262,8 +1262,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1282,16 +1282,16 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= -golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1349,8 +1349,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1379,8 +1379,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.249.0 h1:0VrsWAKzIZi058aeq+I86uIXbNhm9GxSHpbmZ92a38w= -google.golang.org/api v0.249.0/go.mod h1:dGk9qyI0UYPwO/cjt2q06LG/EhUpwZGdAbYF14wHHrQ= +google.golang.org/api v0.252.0 h1:xfKJeAJaMwb8OC9fesr369rjciQ704AjU/psjkKURSI= +google.golang.org/api v0.252.0/go.mod h1:dnHOv81x5RAmumZ7BWLShB/u7JZNeyalImxHmtTHxqw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1423,8 +1423,8 @@ google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuO google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 h1:CirRxTOwnRWVLKzDNrs0CXAaVozJoR4G9xvdRecrdpk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1442,8 +1442,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= -google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1458,8 +1458,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 0fcac851b38f1742ca4a401627b7ac63cb306fca Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 16 Oct 2025 16:50:56 +0200 Subject: [PATCH 184/298] docs: improve changelog headings --- CHANGELOG.md | 469 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 371 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d531b3034..50ed3cc1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog -## [v4.26.0](https://github.com/go-acme/lego/releases/tag/v4.26.0) (2025-09-13) +## v4.26.0 + +- Release date: 2025-09-13 +- Tag: [v4.26.0](https://github.com/go-acme/lego/releases/tag/v4.26.0) ### Added @@ -20,10 +23,13 @@ - **[dnsprovider]** selectelv2: add missing options -## [v4.25.2](https://github.com/go-acme/lego/releases/tag/v4.25.2) (2025-08-06) +## v4.25.2 + +- Release date: 2025-08-06 +- Tag: [v4.25.2](https://github.com/go-acme/lego/releases/tag/v4.25.2) ### Changed -- + - **[cli,log]** log when dynamic renew date not yet reached ### Fixed @@ -31,13 +37,19 @@ - **[cli]** fix: remove wrong env var - **[lib,cli]** fix: enforce HTTPS to the ACME server -## [v4.25.1](https://github.com/go-acme/lego/releases/tag/v4.25.1) (2025-07-21) +## v4.25.1 + +- Release date: 2025-07-21 +- Tag: [v4.25.1](https://github.com/go-acme/lego/releases/tag/v4.25.1) ### Fixed - **[cli]** fix: wrong CLI flag type -## [v4.25.0](https://github.com/go-acme/lego/releases/tag/v4.25.0) (2025-07-21) +## v4.25.0 + +- Release date: 2025-07-21 +- Tag: [v4.25.0](https://github.com/go-acme/lego/releases/tag/v4.25.0) The binary size of this release is about ~50% smaller compared to previous releases. @@ -50,7 +62,7 @@ This will also reduce the module cache usage by 320 MB (this will only affect us - **[lib,cli]** Add an option to disable common name in CSR ### Changed -- + - **[dnsprovider]** vinyldns: add an option to add quotes around the TXT record value - **[dnsprovider]** ionos: increase default propagation timeout @@ -58,7 +70,10 @@ This will also reduce the module cache usage by 320 MB (this will only affect us - **[cli]** fix: enforce domain into renewal command -## [v4.24.0](https://github.com/go-acme/lego/releases/tag/v4.24.0) (2025-07-07) +## v4.24.0 + +- Release date: 2025-07-07 +- Tag: [v4.24.0](https://github.com/go-acme/lego/releases/tag/v4.24.0) ### Added @@ -81,13 +96,19 @@ This will also reduce the module cache usage by 320 MB (this will only affect us - **[dnsprovider]** nicmanager: fix mode env var name and value - **[lib,cli]** Check order identifiers difference between client and server -## [v4.23.1](https://github.com/go-acme/lego/releases/tag/v4.23.1) (2025-04-16) +## v4.23.1 + +- Release date: 2025-04-16 +- Tag: [v4.23.1](https://github.com/go-acme/lego/releases/tag/v4.23.1) Due to an error related to Snapcraft, some artifacts of the v4.23.0 release have not been published. This release contains the same things as v4.23.0. -## [v4.23.0](https://github.com/go-acme/lego/releases/tag/v4.23.0) (2025-04-16) +## v4.23.0 + +- Release date: 2025-04-16 +- Tag: [v4.23.0](https://github.com/go-acme/lego/releases/tag/v4.23.0) ### Added @@ -118,13 +139,19 @@ This release contains the same things as v4.23.0. - **[dnsprovider]** pdns: fix TXT record cleanup for wildcard domains - **[dnsprovider]** allinkl: remove `ReturnInfo` -## [v4.22.2](https://github.com/go-acme/lego/releases/tag/v4.22.2) (2025-02-17) +## v4.22.2 + +- Release date: 2025-02-17 +- Tag: [v4.22.2](https://github.com/go-acme/lego/releases/tag/v4.22.2) ### Fixed - **[dnsprovider]** acme-dns: use new registred account -## [v4.22.1](https://github.com/go-acme/lego/releases/tag/v4.22.1) (2025-02-17) +## v4.22.1 + +- Release date: 2025-02-17 +- Tag: [v4.22.1](https://github.com/go-acme/lego/releases/tag/v4.22.1) ### Fixed @@ -132,7 +159,10 @@ This release contains the same things as v4.23.0. ### Added -## [v4.22.0](https://github.com/go-acme/lego/releases/tag/v4.22.0) (2025-02-17) +## v4.22.0 + +- Release date: 2025-02-17 +- Tag: [v4.22.0](https://github.com/go-acme/lego/releases/tag/v4.22.0) ### Added @@ -160,7 +190,10 @@ This release contains the same things as v4.23.0. - **[cli,log]** remove extra debug logs -## [v4.21.0](https://github.com/go-acme/lego/releases/tag/v4.21.0) (2024-12-20) +## v4.21.0 + +- Release date: 2024-12-20 +- Tag: [v4.21.0](https://github.com/go-acme/lego/releases/tag/v4.21.0) ### Added @@ -181,11 +214,17 @@ This release contains the same things as v4.23.0. - **[dnsprovider]** netcup: increase default propagation values - **[dnsprovider]** otc: use default transport -## [v4.20.4](https://github.com/go-acme/lego/releases/tag/v4.20.4) (2024-11-21) +## v4.20.4 + +- Release date: 2024-11-21 +- Tag: [v4.20.4](https://github.com/go-acme/lego/releases/tag/v4.20.4) Publish the Snap to the Snapcraft stable channel. -## [v4.20.3](https://github.com/go-acme/lego/releases/tag/v4.20.3) (2024-11-21) +## v4.20.3 + +- Release date: 2024-11-21 +- Tag: [v4.20.3](https://github.com/go-acme/lego/releases/tag/v4.20.3) ### Fixed @@ -193,7 +232,10 @@ Publish the Snap to the Snapcraft stable channel. - **[dnsprovider]** directadmin: fix timeout configuration - **[httpprovider]** fix: HTTP server IPv6 matching -## [v4.20.2](https://github.com/go-acme/lego/releases/tag/v4.20.2) (2024-11-11) +## v4.20.2 + +- Release date: 2024-11-11 +- Tag: [v4.20.2](https://github.com/go-acme/lego/releases/tag/v4.20.2) ### Added @@ -221,28 +263,41 @@ Publish the Snap to the Snapcraft stable channel. - **[dnsprovider]** volcengine: set API information within the default configuration - **[log]** Parse printf verbs in log line output -## v4.20.1 (2024-11-11) +## v4.20.1 + +- Release date: 2024-11-11 Cancelled due to CI failure. -## v4.20.0 (2024-11-11) +## v4.20.0 + +- Release date: 2024-11-11 Cancelled due to CI failure. -## [v4.19.2](https://github.com/go-acme/lego/releases/tag/v4.19.2) (2024-10-06) +## v4.19.2 + +- Release date: 2024-10-06 +- Tag: [v4.19.2](https://github.com/go-acme/lego/releases/tag/v4.19.2) ### Fixed - **[lib]** go1.22 compatibility -## [v4.19.1](https://github.com/go-acme/lego/releases/tag/v4.19.1) (2024-10-06) +## v4.19.1 + +- Release date: 2024-10-06 +- Tag: [v4.19.1](https://github.com/go-acme/lego/releases/tag/v4.19.1) ### Fixed - **[dnsprovider]** selectelv2: use baseURL from configuration - **[dnsprovider]** epik: add User-Agent -## [v4.19.0](https://github.com/go-acme/lego/releases/tag/v4.19.0) (2024-10-03) +## v4.19.0 + +- Release date: 2024-10-03 +- Tag: [v4.19.0](https://github.com/go-acme/lego/releases/tag/v4.19.0) ### Added @@ -264,7 +319,10 @@ Cancelled due to CI failure. - **[dnsprovider]** namesilo: restrict CleanUp - **[dnsprovider]** godaddy: fix cleanup -## [v4.18.0](https://github.com/go-acme/lego/releases/tag/v4.18.0) (2024-08-30) +## v4.18.0 + +- Release date: 2024-08-30 +- Tag: [v4.18.0](https://github.com/go-acme/lego/releases/tag/v4.18.0) ### Added @@ -286,13 +344,19 @@ Cancelled due to CI failure. - **[ari]** fix: avoid Int63n panic in ShouldRenewAt() -## [v4.17.4](https://github.com/go-acme/lego/releases/tag/v4.17.4) (2024-06-12) +## v4.17.4 + +- Release date: 2024-06-12 +- Tag: [v4.17.4](https://github.com/go-acme/lego/releases/tag/v4.17.4) ### Fixed - **[dnsprovider]** Update dependencies -## [v4.17.3](https://github.com/go-acme/lego/releases/tag/v4.17.3) (2024-05-28) +## v4.17.3 + +- Release date: 2024-05-28 +- Tag: [v4.17.3](https://github.com/go-acme/lego/releases/tag/v4.17.3) ### Added @@ -320,13 +384,17 @@ Cancelled due to CI failure. - **[dnsprovider]** pdns: reconstruct zone URLs to enable non-root folder API endpoints - **[dnsprovider]** alidns: fix link to API documentation -## v4.17.2 (2024-05-28) +## v4.17.2 + +- Release date: 2024-05-28 Canceled due to a release failure related to Snapcraft. The Snapcraft release are disabled for now. -## v4.17.1 (2024-05-28) +## v4.17.1 + +- Release date: 2024-05-28 Canceled due to a release failure related to oci-go-sdk. @@ -335,17 +403,25 @@ The module `github.com/oracle/oci-go-sdk/v65` uses `github.com/gofrs/flock` but Due to that we will remove the Solaris build. -## v4.17.0 (2024-05-28) +## v4.17.0 + +- Release date: 2024-05-28 Canceled due to a release failure related to Snapcraft. -## [v4.16.1](https://github.com/go-acme/lego/releases/tag/v4.16.1) (2024-03-10) +## v4.16.1 + +- Release date: 2024-03-10 +- Tag: [v4.16.1](https://github.com/go-acme/lego/releases/tag/v4.16.1) ### Fixed - **[cli,ari]** fix: don't generate ARI cert ID if ARI is not enable -## [v4.16.0](https://github.com/go-acme/lego/releases/tag/v4.16.0) (2024-03-09) +## v4.16.0 + +- Release date: 2024-03-09 +- Tag: [v4.16.0](https://github.com/go-acme/lego/releases/tag/v4.16.0) ### Added @@ -366,7 +442,10 @@ Canceled due to a release failure related to Snapcraft. - **[dnsprovider]** easydns: fix zone detection - **[dnsprovider]** ns1: fix record creation -## [v4.15.0](https://github.com/go-acme/lego/releases/tag/v4.15.0) (2024-01-28) +## v4.15.0 + +- Release date: 2024-01-28 +- Tag: [v4.15.0](https://github.com/go-acme/lego/releases/tag/v4.15.0) ### Added @@ -404,7 +483,10 @@ Canceled due to a release failure related to Snapcraft. - **[dnsprovider]** nifcloud: fix API requests - **[dnsprovider]** otc: sequential challenge -## [v4.14.1](https://github.com/go-acme/lego/releases/tag/v4.14.1) (2023-09-20) +## v4.14.1 + +- Release date: 2023-09-20 +- Tag: [v4.14.1](https://github.com/go-acme/lego/releases/tag/v4.14.1) ### Fixed @@ -412,11 +494,16 @@ Canceled due to a release failure related to Snapcraft. - **[dnsprovider]** bunny: use NRDCG fork - **[dnsprovider]** ovh: update client to v1.4.2 -## v4.14.1 (2023-09-19) +## v4.14.1 + +- Release date: 2023-09-19 Cancelled due to CI failure. -## [v4.14.0](https://github.com/go-acme/lego/releases/tag/v4.14.0) (2023-08-20) +## v4.14.0 + +- Release date: 2023-08-20 +- Tag: [v4.14.0](https://github.com/go-acme/lego/releases/tag/v4.14.0) ### Added @@ -435,20 +522,29 @@ Cancelled due to CI failure. - **[dnsprovider]** pdns: fix notify - **[dnsprovider]** route53: avoid unexpected records deletion -## [v4.13.3](https://github.com/go-acme/lego/releases/tag/v4.13.3) (2023-07-25) +## v4.13.3 + +- Release date: 2023-07-25 +- Tag: [v4.13.3](https://github.com/go-acme/lego/releases/tag/v4.13.3) ### Fixed - **[dnsprovider]** azuredns: fix configuration from env vars - **[dnsprovider]** gcore: change API domain -## [v4.13.2](https://github.com/go-acme/lego/releases/tag/v4.13.2) (2023-07-21) +## v4.13.2 + +- Release date: 2023-07-21 +- Tag: [v4.13.2](https://github.com/go-acme/lego/releases/tag/v4.13.2) ### Fixed - **[dnsprovider]** servercow: fix regression -## [v4.13.1](https://github.com/go-acme/lego/releases/tag/v4.13.1) (2023-07-20) +## v4.13.1 + +- Release date: 2023-07-20 +- Tag: [v4.13.1](https://github.com/go-acme/lego/releases/tag/v4.13.1) ### Added @@ -469,24 +565,35 @@ Cancelled due to CI failure. - **[cli]** fix: list command - **[lib]** fix: ARI explanationURL -## v4.13.0 (2023-07-20) +## v4.13.0 + +- Release date: 2023-07-20 Cancelled due to a CI issue (no space left on device). -## [v4.12.2](https://github.com/go-acme/lego/releases/tag/v4.12.2) (2023-06-19) +## v4.12.2 + +- Release date: 2023-06-19 +- Tag: [v4.12.2](https://github.com/go-acme/lego/releases/tag/v4.12.2) ### Fixed - **[dnsprovider]** dnsmadeeasy: fix DeleteRecord - **[lib]** fix: read status code from response -## [v4.12.1](https://github.com/go-acme/lego/releases/tag/v4.12.1) (2023-06-06) +## v4.12.1 + +- Release date: 2023-06-06 +- Tag: [v4.12.1](https://github.com/go-acme/lego/releases/tag/v4.12.1) ### Fixed - **[dnsprovider]** pdns: fix record value -## [v4.12.0](https://github.com/go-acme/lego/releases/tag/v4.12.0) (2023-05-28) +## v4.12.0 + +- Release date: 2023-05-28 +- Tag: [v4.12.0](https://github.com/go-acme/lego/releases/tag/v4.12.0) ### Added @@ -504,7 +611,10 @@ Cancelled due to a CI issue (no space left on device). - **[dnsprovider]** autodns: fixes wrong zone in api call if CNAME is used - **[cli]** fix: archive only domain-related files on revoke -## [v4.11.0](https://github.com/go-acme/lego/releases/tag/v4.11.0) (2023-05-02) +## v4.11.0 + +- Release date: 2023-05-02 +- Tag: [v4.11.0](https://github.com/go-acme/lego/releases/tag/v4.11.0) ### Added @@ -526,18 +636,27 @@ Cancelled due to a CI issue (no space left on device). - **[dnsprovider]** rimuhosting: fix API base URL -## [v4.10.2](https://github.com/go-acme/lego/releases/tag/v4.10.2) (2023-02-26) +## v4.10.2 + +- Release date: 2023-02-26 +- Tag: [v4.10.2](https://github.com/go-acme/lego/releases/tag/v4.10.2) Fix Docker image builds. -## [v4.10.1](https://github.com/go-acme/lego/releases/tag/v4.10.1) (2023-02-25) +## v4.10.1 + +- Release date: 2023-02-25 +- Tag: [v4.10.1](https://github.com/go-acme/lego/releases/tag/v4.10.1) ### Fixed - **[dnsprovider,cname]** acmedns: fix CNAME support - **[dnsprovider]** dynu: fix subdomain support -## [v4.10.0](https://github.com/go-acme/lego/releases/tag/v4.10.0) (2023-02-10) +## v4.10.0 + +- Release date: 2023-02-10 +- Tag: [v4.10.0](https://github.com/go-acme/lego/releases/tag/v4.10.0) ### Added @@ -563,7 +682,10 @@ Fix Docker image builds. - **[dnsprovider]** pdns: fix usage of notify only when zone kind is Master or Slave - **[dnsprovider]** return an error when extracting record name -## [v4.9.1](https://github.com/go-acme/lego/releases/tag/v4.9.1) (2022-11-25) +## v4.9.1 + +- Release date: 2022-11-25 +- Tag: [v4.9.1](https://github.com/go-acme/lego/releases/tag/v4.9.1) ### Changed @@ -578,7 +700,10 @@ Fix Docker image builds. - **[dnsprovider]** hurricane: fix CNAME support - **[lib,cname]** cname: stop trying to traverse cname if none have been found -## [v4.9.0](https://github.com/go-acme/lego/releases/tag/v4.9.0) (2022-10-03) +## v4.9.0 + +- Release date: 2022-10-03 +- Tag: [v4.9.0](https://github.com/go-acme/lego/releases/tag/v4.9.0) ### Added @@ -608,7 +733,10 @@ Fix Docker image builds. - **[dnsprovider]** njalla: fix record id unmarshal error - **[dnsprovider]** tencentcloud: fix subdomain error -## [v4.8.0](https://github.com/go-acme/lego/releases/tag/v4.8.0) (2022-06-30) +## v4.8.0 + +- Release date: 2022-06-30 +- Tag: [v4.8.0](https://github.com/go-acme/lego/releases/tag/v4.8.0) ### Added @@ -624,7 +752,10 @@ Fix Docker image builds. - **[dnsprovider]** hetzner: set min TTL to 60s - **[docs]** refactoring and cleanup -## [v4.7.0](https://github.com/go-acme/lego/releases/tag/v4.7.0) (2022-05-27) +## v4.7.0 + +- Release date: 2022-05-27 +- Tag: [v4.7.0](https://github.com/go-acme/lego/releases/tag/v4.7.0) ### Added @@ -646,7 +777,10 @@ Fix Docker image builds. - **[dnsprovider]** tencentcloud: fix InvalidParameter.DomainInvalid error when using DNS challenges - **[lib]** fix: panic in certcrypto.ParsePEMPrivateKey -## [v4.6.0](https://github.com/go-acme/lego/releases/tag/v4.6.0) (2022-01-18) +## v4.6.0 + +- Release date: 2022-01-18 +- Tag: [v4.6.0](https://github.com/go-acme/lego/releases/tag/v4.6.0) ### Added @@ -668,13 +802,19 @@ Fix Docker image builds. - **[dnsprovider]** mythicbeasts: fix token expiration - **[dnsprovider]** rackspace: change zone ID to string -## [v4.5.3](https://github.com/go-acme/lego/releases/tag/v4.5.3) (2021-09-06) +## v4.5.3 + +- Release date: 2021-09-06 +- Tag: [v4.5.3](https://github.com/go-acme/lego/releases/tag/v4.5.3) ### Fixed - **[lib,cli]** fix: missing preferred chain param for renew request -## [v4.5.2](https://github.com/go-acme/lego/releases/tag/v4.5.2) (2021-09-01) +## v4.5.2 + +- Release date: 2021-09-01 +- Tag: [v4.5.2](https://github.com/go-acme/lego/releases/tag/v4.5.2) ### Added @@ -704,15 +844,22 @@ Fix Docker image builds. - **[lib]** lib: use permanent error instead of context cancellation - **[dnsprovider]** desec: bump to v0.6.0 -## v4.5.1 (2021-09-01) +## v4.5.1 + +- Release date: 2021-10-01 Cancelled due to a CI issue, replaced by v4.5.2. -## v4.5.0 (2021-09-30) +## v4.5.0 + +- Release date: 2021-09-30 Cancelled due to a CI issue, replaced by v4.5.2. -## [v4.4.0](https://github.com/go-acme/lego/releases/tag/v4.4.0) (2021-06-08) +## v4.4.0 + +- Release date: 2021-06-08 +- Tag: [v4.4.0](https://github.com/go-acme/lego/releases/tag/v4.4.0) ### Added @@ -740,13 +887,19 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** nifcloud: Get zone info from dns01.FindZoneByFqdn - **[cli,lib]** csr: Support the type `NEW CERTIFICATE REQUEST` -## [v4.3.1](https://github.com/go-acme/lego/releases/tag/v4.3.1) (2021-03-12) +## v4.3.1 + +- Release date: 2021-03-12 +- Tag: [v4.3.1](https://github.com/go-acme/lego/releases/tag/v4.3.1) ### Fixed - **[dnsprovider]** exoscale: fix dependency version. -## [v4.3.0](https://github.com/go-acme/lego/releases/tag/v4.3.0) (2021-03-10) +## v4.3.0 + +- Release date: 2021-03-10 +- Tag: [v4.3.0](https://github.com/go-acme/lego/releases/tag/v4.3.0) ### Added @@ -770,7 +923,10 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[lib]** Increase HTTP client timeouts - **[lib]** preferred chain only match root name -## [v4.2.0](https://github.com/go-acme/lego/releases/tag/v4.2.0) (2021-01-24) +## v4.2.0 + +- Release date: 2021-01-24 +- Tag: [v4.2.0](https://github.com/go-acme/lego/releases/tag/v4.2.0) ### Added @@ -790,26 +946,38 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** pdns: URL request creation. - **[lib]** errors: Fix instance not being printed -## [v4.1.3](https://github.com/go-acme/lego/releases/tag/v4.1.3) (2020-11-25) +## v4.1.3 + +- Release date: 2020-11-25 +- Tag: [v4.1.3](https://github.com/go-acme/lego/releases/tag/v4.1.3) ### Fixed - **[dnsprovider]** azure: fix error handling. -## [v4.1.2](https://github.com/go-acme/lego/releases/tag/v4.1.2) (2020-11-21) +## v4.1.2 + +- Release date: 2020-11-21 +- Tag: [v4.1.2](https://github.com/go-acme/lego/releases/tag/v4.1.2) ### Fixed - **[lib]** fix: preferred chain support. -## [v4.1.1](https://github.com/go-acme/lego/releases/tag/v4.1.1) (2020-11-19) +## v4.1.1 + +- Release date: 2020-11-19 +- Tag: [v4.1.1](https://github.com/go-acme/lego/releases/tag/v4.1.1) ### Fixed - **[dnsprovider]** otc: select correct zone if multiple returned - **[dnsprovider]** azure: fix target must be a non-nil pointer -## [v4.1.0](https://github.com/go-acme/lego/releases/tag/v4.1.0) (2020-11-06) +## v4.1.0 + +- Release date: 2020-11-06 +- Tag: [v4.1.0](https://github.com/go-acme/lego/releases/tag/v4.1.0) ### Added @@ -827,13 +995,19 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[lib]** acme/api: use postAsGet instead of post for AccountService.Get - **[lib]** fix: use http.Header.Set method instead of Add. -## [v4.0.1](https://github.com/go-acme/lego/releases/tag/v4.0.1) (2020-09-03) +## v4.0.1 + +- Release date: 2020-09-03 +- Tag: [v4.0.1](https://github.com/go-acme/lego/releases/tag/v4.0.1) ### Fixed - **[dnsprovider]** exoscale: change dependency version. -## [v4.0.0](https://github.com/go-acme/lego/releases/tag/v4.0.0) (2020-09-02) +## v4.0.0 + +- Release date: 2020-09-02 +- Tag: [v4.0.0](https://github.com/go-acme/lego/releases/tag/v4.0.0) ### Added @@ -850,7 +1024,10 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** Removes old Linode provider - **[lib]** Removes `AddPreCheck` function -## [v3.9.0](https://github.com/go-acme/lego/releases/tag/v3.9.0) (2020-09-01) +## v3.9.0 + +- Release date: 2020-09-01 +- Tag: [v3.9.0](https://github.com/go-acme/lego/releases/tag/v3.9.0) ### Added @@ -867,7 +1044,10 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** namesilo: fix cleanup. -## [v3.8.0](https://github.com/go-acme/lego/releases/tag/v3.8.0) (2020-07-02) +## v3.8.0 + +- Release date: 2020-07-02 +- Tag: [v3.8.0](https://github.com/go-acme/lego/releases/tag/v3.8.0) ### Added @@ -891,7 +1071,10 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** hetzner: fix record name. - **[lib]** Registrar.ResolveAccountByKey: Fix malformed request -## [v3.7.0](https://github.com/go-acme/lego/releases/tag/v3.7.0) (2020-05-11) +## v3.7.0 + +- Release date: 2020-05-11 +- Tag: [v3.7.0](https://github.com/go-acme/lego/releases/tag/v3.7.0) ### Added @@ -914,7 +1097,10 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[cli]** fix: renew path information. - **[cli]** Fix account storage location warning message -## [v3.6.0](https://github.com/go-acme/lego/releases/tag/v3.6.0) (2020-04-24) +## v3.6.0 + +- Release date: 2020-04-24 +- Tag: [v3.6.0](https://github.com/go-acme/lego/releases/tag/v3.6.0) ### Added @@ -938,7 +1124,10 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** ns1: fix missing domain in log - **[dnsprovider]** rimuhosting: use HTTP client from config. -## [v3.5.0](https://github.com/go-acme/lego/releases/tag/v3.5.0) (2020-03-15) +## v3.5.0 + +- Release date: 2020-03-15 +- Tag: [v3.5.0](https://github.com/go-acme/lego/releases/tag/v3.5.0) ### Added @@ -961,7 +1150,10 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** gcloud: fixes issues when used with GKE Workload Identity - **[dnsprovider]** oraclecloud: fix subdomain support -## [v3.4.0](https://github.com/go-acme/lego/releases/tag/v3.4.0) (2020-02-25) +## v3.4.0 + +- Release date: 2020-02-25 +- Tag: [v3.4.0](https://github.com/go-acme/lego/releases/tag/v3.4.0) ### Added @@ -986,7 +1178,10 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[lib]** crypto: Treat CommonName as optional - **[lib]** chore: update cenkalti/backoff to v4. -## [v3.3.0](https://github.com/go-acme/lego/releases/tag/v3.3.0) (2020-01-08) +## v3.3.0 + +- Release date: 2020-01-08 +- Tag: [v3.3.0](https://github.com/go-acme/lego/releases/tag/v3.3.0) ### Added @@ -1002,7 +1197,10 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** Update dnspod, because of API breaking changes. -## [v3.2.0](https://github.com/go-acme/lego/releases/tag/v3.2.0) (2019-11-10) +## v3.2.0 + +- Release date: 2019-11-10 +- Tag: [v3.2.0](https://github.com/go-acme/lego/releases/tag/v3.2.0) ### Added @@ -1018,7 +1216,10 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** use token as unique ID. -## [v3.1.0](https://github.com/go-acme/lego/releases/tag/v3.1.0) (2019-10-07) +## v3.1.0 + +- Release date: 2019-10-07 +- Tag: [v3.1.0](https://github.com/go-acme/lego/releases/tag/v3.1.0) ### Added @@ -1036,36 +1237,54 @@ Cancelled due to a CI issue, replaced by v4.5.2. - **[dnsprovider]** ovh: fix int overflow. - **[dnsprovider]** bindman: fix client version. -## [v3.0.2](https://github.com/go-acme/lego/releases/tag/v3.0.2) (2019-08-15) +## v3.0.2 + +- Release date: 2019-08-15 +- Tag: [v3.0.2](https://github.com/go-acme/lego/releases/tag/v3.0.2) ### Fixed - Invalid pseudo version (related to Cloudflare client). -## [v3.0.1](https://github.com/go-acme/lego/releases/tag/v3.0.1) (2019-08-14) +## v3.0.1 + +- Release date: 2019-08-14 +- Tag: [v3.0.1](https://github.com/go-acme/lego/releases/tag/v3.0.1) There was a problem when creating the tag v3.0.1, this tag has been invalidated. -## [v3.0.0](https://github.com/go-acme/lego/releases/tag/v3.0.0) (2019-08-05) +## v3.0.0 + +- Release date: 2019-08-05 +- Tag: [v3.0.0](https://github.com/go-acme/lego/releases/tag/v3.0.0) ### Changed - migrate to go module (new import github.com/go-acme/lego/v3/) - update DNS clients -## [v2.7.2](https://github.com/go-acme/lego/releases/tag/v2.7.2) (2019-07-30) +## v2.7.2 + +- Release date: 2019-07-30 +- Tag: [v2.7.2](https://github.com/go-acme/lego/releases/tag/v2.7.2) ### Fixed - **[dnsprovider]** vultr: quote TXT record -## [v2.7.1](https://github.com/go-acme/lego/releases/tag/v2.7.1) (2019-07-22) +## v2.7.1 + +- Release date: 2019-07-22 +- Tag: [v2.7.1](https://github.com/go-acme/lego/releases/tag/v2.7.1) ### Fixed - **[dnsprovider]** vultr: invalid record type. -## [v2.7.0](https://github.com/go-acme/lego/releases/tag/v2.7.0) (2019-07-17) +## v2.7.0 + +- Release date: 2019-07-17 +- Tag: [v2.7.0](https://github.com/go-acme/lego/releases/tag/v2.7.0) ### Added @@ -1082,7 +1301,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - **[dnsprovider]** otc: Prevent sending empty body. -## [v2.6.0](https://github.com/go-acme/lego/releases/tag/v2.6.0) (2019-05-27) +## v2.6.0 + +- Release date: 2019-05-27 +- Tag: [v2.6.0](https://github.com/go-acme/lego/releases/tag/v2.6.0) ### Added @@ -1104,7 +1326,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - **[cli]** fix: cli disable-cp option. - **[dnsprovider]** gcloud: fix zone visibility. -## [v2.5.0](https://github.com/go-acme/lego/releases/tag/v2.5.0) (2019-04-17) +## v2.5.0 + +- Release date: 2019-04-17 +- Tag: [v2.5.0](https://github.com/go-acme/lego/releases/tag/v2.5.0) ### Added @@ -1123,9 +1348,12 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - **[dnsprovider]** Disable authz when solve fail. - Add tzdata to the Docker image. -## [v2.4.0](https://github.com/go-acme/lego/releases/tag/v2.4.0) (2019-03-25) +## v2.4.0 -- Migrate from xenolf/lego to go-acme/lego. +- Release date: 2019-03-25 +- Tag: [v2.4.0](https://github.com/go-acme/lego/releases/tag/v2.4.0) + +Migrate from xenolf/lego to go-acme/lego. ### Added @@ -1138,7 +1366,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - **[dnsprovider]** hostingde: Use provided ZoneName instead of domain - **[dnsprovider]** pdns: fix wildcard with SANs -## [v2.3.0](https://github.com/go-acme/lego/releases/tag/v2.3.0) (2019-03-11) +## v2.3.0 + +- Release date: 2019-03-11 +- Tag: [v2.3.0](https://github.com/go-acme/lego/releases/tag/v2.3.0) ### Added @@ -1162,7 +1393,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - **[dnsprovider]** vscale: fix TXT records clean up - **[dnsprovider]** selectel: fix TXT records clean up -## [v2.2.0](https://github.com/go-acme/lego/releases/tag/v2.2.0) (2019-02-08) +## v2.2.0 + +- Release date: 2019-02-08 +- Tag: [v2.2.0](https://github.com/go-acme/lego/releases/tag/v2.2.0) ### Added @@ -1182,7 +1416,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - **[dnsprovider]** fastdns: Do not overwrite existing TXT records - Log wildcard domain correctly in validation -## [v2.1.0](https://github.com/go-acme/lego/releases/tag/v2.1.0) (2019-01-24) +## v2.1.0 + +- Release date: 2019-01-24 +- Tag: [v2.1.0](https://github.com/go-acme/lego/releases/tag/v2.1.0) ### Added @@ -1199,7 +1436,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - **[dnsprovider]** alicloud: fix pagination. - **[dnsprovider]** namecheap: fix panic. -## [v2.0.0](https://github.com/go-acme/lego/releases/tag/v2.0.0) (2019-01-09) +## v2.0.0 + +- Release date: 2019-01-09 +- Tag: [v2.0.0](https://github.com/go-acme/lego/releases/tag/v2.0.0) ### Added @@ -1251,7 +1491,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - **[dnsprovider]** Azure: Do not overwrite existing TXT records - **[dnsprovider]** fix: Cloudflare error. -## [v1.2.0](https://github.com/go-acme/lego/releases/tag/v1.2.0) (2018-11-04) +## v1.2.0 + +- Release date: 2018-11-04 +- Tag: [v1.2.0](https://github.com/go-acme/lego/releases/tag/v1.2.0) ### Added @@ -1272,7 +1515,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - **[lib]** Do not send a JWS body when POSTing challenges. - **[lib]** Support POST-as-GET. -## [v1.1.0](https://github.com/go-acme/lego/releases/tag/v1.1.0) (2018-10-16) +## v1.1.0 + +- Release date: 2018-10-16 +- Tag: [v1.1.0](https://github.com/go-acme/lego/releases/tag/v1.1.0) ### Added @@ -1308,7 +1554,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - **[lib]** Submit all dns records up front, then validate serially -## [v1.0.0](https://github.com/go-acme/lego/releases/tag/v1.0.0) (2018-05-30) +## v1.0.0 + +- Release date: 2018-05-30 +- Tag: [v1.0.0](https://github.com/go-acme/lego/releases/tag/v1.0.0) ### Changed @@ -1317,7 +1566,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - **[dnsprovider]** Modified Google Cloud provider `gcloud.NewDNSProviderServiceAccount` function to extract the project id directly from the service account file. - **[dnsprovider]** Made errors more verbose for the Cloudflare provider. -## [v0.5.0](https://github.com/go-acme/lego/releases/tag/v0.5.0) (2018-05-29) +## v0.5.0 + +- Release date: 2018-05-29 +- Tag: [v0.5.0](https://github.com/go-acme/lego/releases/tag/v0.5.0) ### Added @@ -1351,7 +1603,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - **[dnsprovider]** Exoscale: update to latest egoscale version. - **[dnsprovider]** Route53: Use NewSessionWithOptions instead of deprecated New. -## [0.4.1](https://github.com/go-acme/lego/releases/tag/0.4.1) (2017-09-26) +## 0.4.1 + +- Release date: 2017-09-26 +- Tag: [0.4.1](https://github.com/go-acme/lego/releases/tag/0.4.1) ### Added @@ -1364,7 +1619,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - lib: Fixed an authentication issue with the latest Azure SDK. -## [0.4.0](https://github.com/go-acme/lego/releases/tag/0.4.0) (2017-07-13) +## 0.4.0 + +- Release date: 2017-07-13 +- Tag: [0.4.0](https://github.com/go-acme/lego/releases/tag/0.4.0) ### Added @@ -1417,7 +1675,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - lib: Fixed a condition where we could stall due to an early error condition. - lib: Fixed an issue where Authz object could end up in an active state after an error condition. -## [0.3.1](https://github.com/go-acme/lego/releases/tag/0.3.1) (2016-04-19) +## 0.3.1 + +- Release date: 2016-04-19 +- Tag: [0.3.1](https://github.com/go-acme/lego/releases/tag/0.3.1) ### Added @@ -1429,7 +1690,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - lib: handleHTTPError should only try to JSON decode error messages with the right content type. - lib: The propagation checker for the DNS challenge would not retry on send errors. -## [0.3.0](https://github.com/go-acme/lego/releases/tag/0.3.0) (2016-03-19) +## 0.3.0 + +- Release date: 2016-03-19 +- Tag: [0.3.0](https://github.com/go-acme/lego/releases/tag/0.3.0) ### Added @@ -1464,7 +1728,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - lib: Fixed an issue where status codes on ACME challenge responses could lead to no action being taken. - lib: Fixed a regression when calling the Renew function with a SAN certificate. -## [0.2.0](https://github.com/go-acme/lego/releases/tag/0.2.0) (2016-01-09) +## 0.2.0 + +- Release date: 2016-01-09 +- Tag: [0.2.0](https://github.com/go-acme/lego/releases/tag/0.2.0) ### Added @@ -1494,7 +1761,10 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - CLI: Fix logic using the `--days` parameter for renew -## [0.1.1](https://github.com/go-acme/lego/releases/tag/0.1.1) (2015-12-18) +## 0.1.1 + +- Release date: 2015-12-18 +- Tag: [0.1.1](https://github.com/go-acme/lego/releases/tag/0.1.1) ### Added @@ -1514,6 +1784,9 @@ There was a problem when creating the tag v3.0.1, this tag has been invalidated. - lib: Fix possible DOS on GetOCSPForCert -## [0.1.0](https://github.com/go-acme/lego/releases/tag/0.1.0) (2015-12-03) +## 0.1.0 -- Initial release +- Release date: 2015-12-03 +- Tag: [0.1.0](https://github.com/go-acme/lego/releases/tag/0.1.0) + +Initial release From acfb5ea9386b4b54c3efc602f18d4f16612e5a72 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 16 Oct 2025 20:54:49 +0200 Subject: [PATCH 185/298] docs: sponsor incentives --- .goreleaser.yml | 14 ++++++++++++++ CHANGELOG.md | 6 ++++++ README.md | 8 +++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 25252850b..073997209 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -55,6 +55,20 @@ changelog: - '(?i)^Detach v[\d|.]+' - '(?i)^Prepare release v[\d|.]+' +release: + skip_upload: false + github: + owner: 'go-acme' + name: 'lego' + header: | + lego is an independent, free, and open-source project, if you value it, consider [supporting it](https://donate.ldez.dev)! ❤️ + + Everybody thinks that the others will donate, but in the end, nobody does. + + So if you think that lego is worth it, please consider [donating](https://donate.ldez.dev). + + For key updates, see the [changelog](https://github.com/go-acme/lego/blob/HEAD/CHANGELOG.md#v{{ .Major }}{{ .Minor }}{{ .Patch }}). + archives: - id: lego name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}' diff --git a/CHANGELOG.md b/CHANGELOG.md index 50ed3cc1b..0bf16e7e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +lego is an independent, free, and open-source project, if you value it, consider [supporting it](https://donate.ldez.dev)! ❤️ + +Everybody thinks that the others will donate, but in the end, nobody does. + +So if you think that lego is worth it, please consider [donating](https://donate.ldez.dev). + ## v4.26.0 - Release date: 2025-09-13 diff --git a/README.md b/README.md index 303f8824b..5448102ed 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,12 @@ Let's Encrypt client and ACME library written in Go. [![Build Status](https://github.com//go-acme/lego/workflows/Main/badge.svg?branch=master)](https://github.com//go-acme/lego/actions) [![Docker Pulls](https://img.shields.io/docker/pulls/goacme/lego.svg)](https://hub.docker.com/r/goacme/lego/) +lego is an independent, free, and open-source project, if you value it, consider [supporting it](https://donate.ldez.dev)! ❤️ + +Everybody thinks that the others will donate, but in the end, nobody does. + +So if you think that lego is worth it, please consider [donating](https://donate.ldez.dev). + ## Features - ACME v2 [RFC 8555](https://www.rfc-editor.org/rfc/rfc8555.html) @@ -18,7 +24,7 @@ Let's Encrypt client and ACME library written in Go. - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): certificates for IP addresses - Support [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html): Renewal Information (ARI) Extension - Support [draft-aaron-acme-profiles-00](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/): Profiles Extension -- Comes with about [150 DNS providers](https://go-acme.github.io/lego/dns) +- Comes with about [170 DNS providers](https://go-acme.github.io/lego/dns) - Register with CA - Obtain certificates, both from scratch or with an existing CSR - Renew certificates From a58c45ee4fc63ff17fb63898764791df88eab65a Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 16 Oct 2025 22:27:21 +0200 Subject: [PATCH 186/298] Prepare release v4.27.0 --- CHANGELOG.md | 21 +++++++++++++++++++ acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bf16e7e5..b60c38a70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,27 @@ Everybody thinks that the others will donate, but in the end, nobody does. So if you think that lego is worth it, please consider [donating](https://donate.ldez.dev). +## v4.27.0 + +- Release date: 2025-10-17 +- Tag: [v4.27.0](https://github.com/go-acme/lego/releases/tag/v4.27.0) + +### Added + +- **[dnsprovider]** Add DNS provider for Octenium +- **[dnsprovider]** Add DNS provider for Hostinger +- **[dnsprovider]** Add DNS provider for Beget.com + +### Changed + +- **[cli]** support `--private-key` with a PKCS#8 keypair +- **[dnsprovider]** hetzner: update to new API +- **[dnsprovider]** otc: adds option to use private zone + +### Fixed + +- **[lib]** fix: deduplicate order identifiers + ## v4.26.0 - Release date: 2025-09-13 diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 6cf5cb7df..3f138107f 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.26.0" + ourUserAgent = "xenolf-acme/4.27.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index a97cd3c5c..e8fc408a2 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.26.0+dev-detach" +const defaultVersion = "v4.27.0+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 7cbe439ef..b2f3357a6 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.26.0" + ourUserAgent = "goacme-lego/4.27.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From 753d31f25427f54f2812e7afafa3cb869259f85a Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 16 Oct 2025 22:27:40 +0200 Subject: [PATCH 187/298] Detach v4.27.0 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 3f138107f..f7aee3fa5 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index e8fc408a2..b2ef87912 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.27.0+dev-release" +const defaultVersion = "v4.27.0+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index b2f3357a6..b0d6f3437 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From b44293d8b1b38353d7da01d793138f54f2968b57 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 17 Oct 2025 17:24:51 +0200 Subject: [PATCH 188/298] chore: fix attest-build-provenance checksums subject --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2d946694..a83c85909 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -80,7 +80,7 @@ jobs: - uses: actions/attest-build-provenance@v3 with: - subject-checksums: ./dist/checksums.txt + subject-checksums: ./dist/lego_*_checksums.txt - uses: actions/attest-build-provenance@v3 with: subject-checksums: ./dist/digests.txt From 4022827c6e3daa5727057212978f83f8c930b5af Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 18 Oct 2025 16:46:55 +0200 Subject: [PATCH 189/298] chore: remove existing files before generating new files (#2676) --- .gitattributes | 1 + docs/content/dns/zz_gen_hetznerv1.md | 67 ---------------------------- internal/dns/docs/generator.go | 21 +++++++++ 3 files changed, 22 insertions(+), 67 deletions(-) delete mode 100644 docs/content/dns/zz_gen_hetznerv1.md diff --git a/.gitattributes b/.gitattributes index a91e62484..ae17ee40c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ **/zz_gen_*.* linguist-generated +docs/data/zz_cli_help.toml linguist-generated diff --git a/docs/content/dns/zz_gen_hetznerv1.md b/docs/content/dns/zz_gen_hetznerv1.md deleted file mode 100644 index 737004fe4..000000000 --- a/docs/content/dns/zz_gen_hetznerv1.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: "Hetzner" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: hetznerv1 -dnsprovider: - since: "v4.27.0" - code: "hetznerv1" - url: "https://hetzner.com" ---- - - - - - - -Configuration for [Hetzner](https://hetzner.com). - - - - -- Code: `hetznerv1` -- Since: v4.27.0 - - -Here is an example bash command using the Hetzner provider: - -```bash -HETZNER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns hetznerv1 -d '*.example.com' -d example.com run -``` - - - - -## Credentials - -| Environment Variable Name | Description | -|-----------------------|-------------| -| `HETZNER_API_TOKEN` | API token | - -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 | -|--------------------------------|-------------| -| `HETZNER_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `HETZNER_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `HETZNER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `HETZNER_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | - -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://docs.hetzner.cloud/reference/cloud#dns) - - - - diff --git a/internal/dns/docs/generator.go b/internal/dns/docs/generator.go index d618ce568..676f65f5b 100644 --- a/internal/dns/docs/generator.go +++ b/internal/dns/docs/generator.go @@ -48,6 +48,11 @@ func main() { log.Fatal(err) } + err = cleanDocumentation() + if err != nil { + log.Fatal(err) + } + for _, m := range models.Providers { // generate documentation err = generateDocumentation(m) @@ -71,6 +76,22 @@ func main() { fmt.Printf("Documentation for %d DNS providers has been generated.\n", len(models.Providers)+1) } +func cleanDocumentation() error { + paths, err := filepath.Glob(filepath.Join(docOutput, "zz_gen_*.md")) + if err != nil { + return err + } + + for _, p := range paths { + err = os.RemoveAll(p) + if err != nil { + return err + } + } + + return nil +} + func generateDocumentation(m descriptors.Provider) error { filename := filepath.Join(docOutput, "zz_gen_"+m.Code+".md") From 9e7572e1edd4e853379146fbea8abdc443fb50e5 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 20 Oct 2025 22:34:12 +0200 Subject: [PATCH 190/298] chore: fix typos inside issue templates --- .github/ISSUE_TEMPLATE/bug_report.yml | 4 ++-- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/ISSUE_TEMPLATE/new_dns_provider.yml | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f8591e2a6..ecd9cb6a5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -7,9 +7,9 @@ body: attributes: label: Welcome options: - - label: Yes, I'm using a binary release within 2 latest releases. + - label: Yes, I'm using a binary release within the two latest releases. required: true - - label: Yes, I've searched similar issues on GitHub and didn't find any. + - label: Yes, I've searched for similar issues on GitHub and didn't find any. required: true - label: Yes, I've included all information below (version, config, etc). required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index b29c0d9f5..33f7be155 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -6,7 +6,7 @@ body: attributes: label: Welcome options: - - label: Yes, I've searched similar issues on GitHub and didn't find any. + - label: Yes, I've searched for similar issues on GitHub and didn't find any. required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/new_dns_provider.yml b/.github/ISSUE_TEMPLATE/new_dns_provider.yml index f310e9815..9e9fe3c03 100644 --- a/.github/ISSUE_TEMPLATE/new_dns_provider.yml +++ b/.github/ISSUE_TEMPLATE/new_dns_provider.yml @@ -8,7 +8,7 @@ body: attributes: label: Welcome options: - - label: Yes, I've searched similar issues on GitHub and didn't find any. + - label: Yes, I've searched for similar issues on GitHub and didn't find any. required: true - label: Yes, the DNS provider exposes a public API. required: true @@ -34,6 +34,7 @@ body: - Through Bitnami - Through 1Panel - Through Zoraxy + - go install - Other validations: required: true From 95953b45b54e4edd834bf425f1c0bd28f5e69b52 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 22 Oct 2025 20:22:12 +0200 Subject: [PATCH 191/298] chore: improve retryable HTTP client error handling (#2682) --- cmd/setup.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/cmd/setup.go b/cmd/setup.go index fd8038464..72f881755 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -1,14 +1,18 @@ package cmd import ( + "context" "crypto/x509" + "encoding/json" "encoding/pem" "fmt" + "io" "net/http" "os" "strings" "time" + "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/log" @@ -70,6 +74,7 @@ func newClient(ctx *cli.Context, acc registration.User, keyType certcrypto.KeyTy retryClient := retryablehttp.NewClient() retryClient.RetryMax = 5 retryClient.HTTPClient = config.HTTPClient + retryClient.CheckRetry = checkRetry retryClient.Logger = nil if _, v := os.LookupEnv("LEGO_DEBUG_ACME_HTTP_CLIENT"); v { @@ -163,3 +168,45 @@ func readCSRFile(filename string) (*x509.CertificateRequest, error) { // (if this assumption is wrong, parsing these bytes will fail) return x509.ParseCertificateRequest(raw) } + +func checkRetry(ctx context.Context, resp *http.Response, err error) (bool, error) { + rt, err := retryablehttp.ErrorPropagatedRetryPolicy(ctx, resp, err) + if err != nil { + return rt, err + } + + if resp.StatusCode/100 == 2 { + return rt, nil + } + + all, err := io.ReadAll(resp.Body) + if err == nil { + var errorDetails *acme.ProblemDetails + + err = json.Unmarshal(all, &errorDetails) + if err != nil { + return rt, fmt.Errorf("%s %s: %s", resp.Request.Method, resp.Request.URL.Redacted(), string(all)) + } + + switch errorDetails.Type { + case acme.BadNonceErr: + return false, &acme.NonceError{ + ProblemDetails: errorDetails, + } + + case acme.AlreadyReplacedErr: + if errorDetails.HTTPStatus == http.StatusConflict { + return false, &acme.AlreadyReplacedError{ + ProblemDetails: errorDetails, + } + } + + default: + log.Warnf("retry: %v", errorDetails) + + return rt, errorDetails + } + } + + return rt, nil +} From e6c98a195e7dd14fc0a6d90e4cbe1f2da661e203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Thu, 23 Oct 2025 11:57:29 +0200 Subject: [PATCH 192/298] Add DNS provider for Anexia (#2675) Co-authored-by: Fernandez Ludovic --- README.md | 83 ++++--- cmd/zz_gen_cmd_dnshelp.go | 22 ++ docs/content/dns/zz_gen_anexia.md | 73 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/anexia/anexia.go | 233 ++++++++++++++++++ providers/dns/anexia/anexia.toml | 31 +++ providers/dns/anexia/anexia_test.go | 165 +++++++++++++ providers/dns/anexia/internal/client.go | 156 ++++++++++++ providers/dns/anexia/internal/client_test.go | 133 ++++++++++ .../fixtures/create_record-request.json | 7 + .../internal/fixtures/create_record.json | 38 +++ .../fixtures/create_record_incomplete.json | 37 +++ .../dns/anexia/internal/fixtures/error.json | 6 + .../anexia/internal/fixtures/get_zone.json | 82 ++++++ providers/dns/anexia/internal/types.go | 38 +++ providers/dns/zz_gen_dns_providers.go | 3 + 16 files changed, 1069 insertions(+), 40 deletions(-) create mode 100644 docs/content/dns/zz_gen_anexia.md create mode 100644 providers/dns/anexia/anexia.go create mode 100644 providers/dns/anexia/anexia.toml create mode 100644 providers/dns/anexia/anexia_test.go create mode 100644 providers/dns/anexia/internal/client.go create mode 100644 providers/dns/anexia/internal/client_test.go create mode 100644 providers/dns/anexia/internal/fixtures/create_record-request.json create mode 100644 providers/dns/anexia/internal/fixtures/create_record.json create mode 100644 providers/dns/anexia/internal/fixtures/create_record_incomplete.json create mode 100644 providers/dns/anexia/internal/fixtures/error.json create mode 100644 providers/dns/anexia/internal/fixtures/get_zone.json create mode 100644 providers/dns/anexia/internal/types.go diff --git a/README.md b/README.md index 5448102ed..e02eff0c7 100644 --- a/README.md +++ b/README.md @@ -66,203 +66,208 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Amazon Lightsail Amazon Route 53 + Anexia CloudDNS ArvanCloud - Aurora DNS + Aurora DNS Autodns Axelname Azion - Azure (deprecated) + Azure (deprecated) Azure DNS Baidu Cloud Beget.com - Binary Lane + Binary Lane Bindman Bluecat BookMyName - Brandit (deprecated) + Brandit (deprecated) Bunny Checkdomain Civo - Cloud.ru + Cloud.ru CloudDNS Cloudflare ClouDNS - CloudXNS (Deprecated) + CloudXNS (Deprecated) ConoHa v2 ConoHa v3 Constellix - Core-Networks + Core-Networks CPanel/WHM Derak Cloud deSEC.io - Designate DNSaaS for Openstack + Designate DNSaaS for Openstack Digital Ocean DirectAdmin DNS Made Easy - dnsHome.de + dnsHome.de DNSimple DNSPod (deprecated) Domain Offensive (do.de) - Domeneshop + Domeneshop DreamHost Duck DNS Dyn - DynDnsFree.de + DynDnsFree.de Dynu EasyDNS Efficient IP - Epik + Epik Exoscale External program F5 XC - freemyip.com + freemyip.com G-Core Gandi Gandi Live DNS (v5) - Glesys + Glesys Go Daddy Google Cloud Google Domains - Hetzner + Hetzner Hosting.de Hostinger Hosttech - HTTP request + HTTP request http.net Huawei Cloud Hurricane Electric DNS - HyperOne + HyperOne IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox - Infomaniak + Infomaniak Internet Initiative Japan Internet.bs INWX - Ionos + Ionos IPv64 iwantmyname Joker - Joohoi's ACME-DNS + Joohoi's ACME-DNS KeyHelp Liara Lima-City - Linode (v4) + Linode (v4) Liquid Web Loopia LuaDNS - Mail-in-a-Box + Mail-in-a-Box ManageEngine CloudDNS Manual Metaname - Metaregistrar + Metaregistrar mijn.host Mittwald myaddr.{tools,dev,io} - MyDNS.jp + MyDNS.jp MythicBeasts Name.com Namecheap - Namesilo + Namesilo NearlyFreeSpeech.NET Netcup Netlify - Nicmanager + Nicmanager NIFCloud Njalla Nodion - NS1 + NS1 Octenium Open Telekom Cloud Oracle Cloud - OVH + OVH plesk.com Porkbun PowerDNS - Rackspace + Rackspace Rain Yun/雨云 RcodeZero reg.ru - Regfish + Regfish RFC2136 RimuHosting RU CENTER - Sakura Cloud + Sakura Cloud Scaleway Selectel Selectel v2 - SelfHost.(de|eu) + SelfHost.(de|eu) Servercow Shellrent Simply.com - Sonic + Sonic Spaceship Stackpath Technitium - Tencent Cloud DNS + Tencent Cloud DNS Tencent EdgeOne Timeweb Cloud TransIP - UKFast SafeDNS + UKFast SafeDNS Ultradns Variomedia VegaDNS - Vercel + Vercel Versio.[nl|eu|uk] VinylDNS VK Cloud - Volcano Engine/火山引擎 + Volcano Engine/火山引擎 Vscale Vultr Webnames - Websupport + Websupport WEDOS West.cn/西部数码 Yandex 360 - Yandex Cloud + Yandex Cloud Yandex PDD Zone.ee ZoneEdit + Zonomi + + + diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 2c7c1acc0..4c76a0f51 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -17,6 +17,7 @@ func allDNSCodes() string { "active24", "alidns", "allinkl", + "anexia", "arvancloud", "auroradns", "autodns", @@ -269,6 +270,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/allinkl`) + case "anexia": + // generated from: providers/dns/anexia/anexia.toml + ew.writeln(`Configuration for Anexia CloudDNS.`) + ew.writeln(`Code: 'anexia'`) + ew.writeln(`Since: 'v4.28.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "ANEXIA_TOKEN": API token for Anexia Engine`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "ANEXIA_API_URL": API endpoint URL (default: https://engine.anexia-it.com)`) + ew.writeln(` - "ANEXIA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "ANEXIA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "ANEXIA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) + ew.writeln(` - "ANEXIA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/anexia`) + case "arvancloud": // generated from: providers/dns/arvancloud/arvancloud.toml ew.writeln(`Configuration for ArvanCloud.`) diff --git a/docs/content/dns/zz_gen_anexia.md b/docs/content/dns/zz_gen_anexia.md new file mode 100644 index 000000000..4256d957c --- /dev/null +++ b/docs/content/dns/zz_gen_anexia.md @@ -0,0 +1,73 @@ +--- +title: "Anexia CloudDNS" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: anexia +dnsprovider: + since: "v4.28.0" + code: "anexia" + url: "https://www.anexia-it.com/" +--- + + + + + + +Configuration for [Anexia CloudDNS](https://www.anexia-it.com/). + + + + +- Code: `anexia` +- Since: v4.28.0 + + +Here is an example bash command using the Anexia CloudDNS provider: + +```bash +ANEXIA_TOKEN=xxx \ +lego --email you@example.com --dns anexia -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `ANEXIA_TOKEN` | API token for Anexia Engine | + +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 | +|--------------------------------|-------------| +| `ANEXIA_API_URL` | API endpoint URL (default: https://engine.anexia-it.com) | +| `ANEXIA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `ANEXIA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `ANEXIA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | +| `ANEXIA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | + +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" %}}). + +## Description + +You need to create an API token in the [Anexia Engine](https://engine.anexia-it.com/). + +The token must have permissions to manage DNS zones and records. + + + +## More information + +- [API documentation](https://engine.anexia-it.com/docs/en/module/clouddns/api) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 8943057d0..bebc8b763 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/anexia/anexia.go b/providers/dns/anexia/anexia.go new file mode 100644 index 000000000..9328c14c5 --- /dev/null +++ b/providers/dns/anexia/anexia.go @@ -0,0 +1,233 @@ +// Package anexia implements a DNS provider for solving the DNS-01 challenge using Anexia CloudDNS. +package anexia + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/cenkalti/backoff/v5" + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/anexia/internal" +) + +// Environment variables names. +const ( + envNamespace = "ANEXIA_" + + EnvToken = envNamespace + "TOKEN" + EnvAPIURL = envNamespace + "API_URL" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +const defaultTTL = 300 + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + Token string + APIURL string + + TTL int + PropagationTimeout time.Duration + PollingInterval time.Duration + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + TTL: env.GetOrDefaultInt(EnvTTL, defaultTTL), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute), + 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for Anexia CloudDNS. +// Credentials must be passed in the environment variable: ANEXIA_TOKEN. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvToken) + if err != nil { + return nil, fmt.Errorf("anexia: %w", err) + } + + config := NewDefaultConfig() + config.Token = values[EnvToken] + config.APIURL = env.GetOrFile(EnvAPIURL) + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Anexia CloudDNS. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("anexia: the configuration of the DNS provider is nil") + } + + if config.Token == "" { + return nil, errors.New("anexia: incomplete credentials, missing token") + } + + client, err := internal.NewClient(config.Token) + if err != nil { + return nil, fmt.Errorf("anexia: %w", err) + } + + if config.APIURL != "" { + var err error + client.BaseURL, err = url.Parse(config.APIURL) + if err != nil { + return nil, fmt.Errorf("anexia: %w", err) + } + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + return &DNSProvider{ + config: config, + client: client, + }, 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 +} + +// Present creates a TXT record to fulfill the dns-01 challenge. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("anexia: could not find zone for domain %q: %w", domain, err) + } + + recordName, err := extractRecordName(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("anexia: %w", err) + } + + zoneName := dns01.UnFqdn(authZone) + + recordReq := internal.Record{ + Name: recordName, + Type: "TXT", + RData: info.Value, + TTL: d.config.TTL, + } + + // Ignores returned zone, because of UUID unstability. + // https://github.com/go-acme/lego/pull/2675#issuecomment-3418678194 + _, err = d.client.CreateRecord(ctx, zoneName, recordReq) + if err != nil { + return fmt.Errorf("anexia: new record: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("anexia: could not find zone for domain %q: %w", domain, err) + } + + recordName, err := extractRecordName(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("anexia: %w", err) + } + + recordID, err := d.findRecordID(ctx, dns01.UnFqdn(authZone), recordName, info.Value) + if err != nil { + return fmt.Errorf("anexia: %w", err) + } + + err = d.client.DeleteRecord(ctx, dns01.UnFqdn(authZone), recordID) + if err != nil { + return fmt.Errorf("anexia: delete TXT record: %w", err) + } + + return nil +} + +// findRecordID attempts to find the record ID from the zone response. +// If the record is not immediately available in the response, it retries by querying the zone. +func (d *DNSProvider) findRecordID(ctx context.Context, zoneName, recordName, rdata string) (string, error) { + return backoff.Retry(ctx, + func() (string, error) { + currentZone, err := d.client.GetZone(ctx, zoneName) + if err != nil { + return "", backoff.Permanent(fmt.Errorf("get zone: %w", err)) + } + + recordID := findRecordIdentifier(currentZone, recordName, rdata) + if recordID == "" { + return "", fmt.Errorf("get record identifier: %w", err) + } + + return recordID, nil + }, + backoff.WithBackOff(backoff.NewConstantBackOff(5*time.Second)), + backoff.WithMaxElapsedTime(300*time.Second), + ) +} + +func findRecordIdentifier(zone *internal.Zone, recordName, rdata string) string { + if len(zone.Revisions) == 0 { + return "" + } + + // Check the first revision (index 0) which should be the current one + + for _, record := range zone.Revisions[0].Records { + if record.Name != recordName || record.Type != "TXT" { + continue + } + + if record.RData == rdata || record.RData == strconv.Quote(rdata) { + return record.Identifier + } + } + + return "" +} + +func extractRecordName(fqdn, authZone string) (string, error) { + if dns01.UnFqdn(fqdn) == dns01.UnFqdn(authZone) { + // "@" for the root domain instead of an empty string. + return "@", nil + } + + return dns01.ExtractSubDomain(fqdn, authZone) +} diff --git a/providers/dns/anexia/anexia.toml b/providers/dns/anexia/anexia.toml new file mode 100644 index 000000000..4fad8ea48 --- /dev/null +++ b/providers/dns/anexia/anexia.toml @@ -0,0 +1,31 @@ +Name = "Anexia CloudDNS" +Description = '''''' +URL = "https://www.anexia-it.com/" +Code = "anexia" +Since = "v4.28.0" + +Example = ''' +ANEXIA_TOKEN=xxx \ +lego --email you@example.com --dns anexia -d '*.example.com' -d example.com run +''' + +Additional = ''' +## Description + +You need to create an API token in the [Anexia Engine](https://engine.anexia-it.com/). + +The token must have permissions to manage DNS zones and records. +''' + +[Configuration] + [Configuration.Credentials] + ANEXIA_TOKEN = "API token for Anexia Engine" + [Configuration.Additional] + ANEXIA_API_URL = "API endpoint URL (default: https://engine.anexia-it.com)" + ANEXIA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + ANEXIA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" + ANEXIA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + ANEXIA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://engine.anexia-it.com/docs/en/module/clouddns/api" diff --git a/providers/dns/anexia/anexia_test.go b/providers/dns/anexia/anexia_test.go new file mode 100644 index 000000000..f38bfd7f7 --- /dev/null +++ b/providers/dns/anexia/anexia_test.go @@ -0,0 +1,165 @@ +package anexia + +import ( + "net/http/httptest" + "testing" + "time" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest( + EnvToken, + EnvAPIURL). + WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success with token", + envVars: map[string]string{ + EnvToken: "secret", + }, + }, + { + desc: "missing token", + envVars: map[string]string{ + EnvToken: "", + }, + expected: "anexia: some credentials information are missing: ANEXIA_TOKEN", + }, + } + + 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) + assert.NotNil(t, p.config) + assert.NotNil(t, p.client) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + token string + expected string + }{ + { + desc: "success with token", + token: "secret", + }, + { + desc: "missing token", + token: "", + expected: "anexia: incomplete credentials, missing token", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Token = test.token + + p, err := NewDNSProviderConfig(config) + + if test.expected == "" { + require.NoError(t, err) + require.NotNil(t, p) + assert.NotNil(t, p.config) + assert.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) + + time.Sleep(2 * time.Second) + + err = provider.CleanUp(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.Token = "secret" + config.APIURL = server.URL + config.HTTPClient = server.Client() + + return NewDNSProviderConfig(config) + }, + servermock.CheckHeader(). + WithAuthorization("Token secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("POST /api/clouddns/v1/zone.json/example.com/records", + servermock.ResponseFromInternal("create_record.json"), + servermock.CheckHeader(). + WithContentType("application/json; charset=utf-8"), + servermock.CheckRequestJSONBodyFromInternal("create_record-request.json")). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("GET /api/clouddns/v1/zone.json/example.com", + servermock.ResponseFromInternal("get_zone.json")). + Route("DELETE /api/clouddns/v1/zone.json/example.com/records/12345678-1234-1234-1234-123456789abc", + servermock.Noop()). + Build(t) + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/anexia/internal/client.go b/providers/dns/anexia/internal/client.go new file mode 100644 index 000000000..86f3b2697 --- /dev/null +++ b/providers/dns/anexia/internal/client.go @@ -0,0 +1,156 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" +) + +const defaultBaseURL = "https://engine.anexia-it.com" + +// Client the Anexia CloudDNS API client. +type Client struct { + token string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(token string) (*Client, error) { + if token == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + token: token, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) CreateRecord(ctx context.Context, zoneName string, record Record) (*Zone, error) { + endpoint := c.BaseURL.JoinPath("api", "clouddns", "v1", "zone.json", zoneName, "records") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) + if err != nil { + return nil, err + } + + var zone Zone + err = c.do(req, &zone) + if err != nil { + return nil, err + } + + return &zone, nil +} + +func (c *Client) DeleteRecord(ctx context.Context, zoneName, recordID string) error { + endpoint := c.BaseURL.JoinPath("api", "clouddns", "v1", "zone.json", zoneName, "records", recordID) + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) GetZone(ctx context.Context, zoneName string) (*Zone, error) { + endpoint := c.BaseURL.JoinPath("api", "clouddns", "v1", "zone.json", zoneName) + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var zone Zone + + err = c.do(req, &zone) + if err != nil { + return nil, err + } + + return &zone, nil +} + +func (c *Client) do(req *http.Request, result any) error { + useragent.SetHeader(req.Header) + + req.Header.Add("Authorization", fmt.Sprintf("Token %s", c.token)) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json; charset=utf-8") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/anexia/internal/client_test.go b/providers/dns/anexia/internal/client_test.go new file mode 100644 index 000000000..be33d6f88 --- /dev/null +++ b/providers/dns/anexia/internal/client_test.go @@ -0,0 +1,133 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("secret") + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + WithAuthorization("Token secret"), + ) +} + +func TestClient_CreateRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /api/clouddns/v1/zone.json/example.com/records", + servermock.ResponseFromFixture("create_record.json"), + servermock.CheckHeader(). + WithContentType("application/json; charset=utf-8"), + servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). + Build(t) + + record := Record{ + Name: "_acme-challenge", + RData: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 300, + Type: "TXT", + } + + zone, err := client.CreateRecord(t.Context(), "example.com", record) + require.NoError(t, err) + + expected := &Zone{ + Name: "example.com", + TTL: 86400, + ZoneName: "example.com", + Revisions: []Revision{{ + Identifier: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + Records: []Record{{ + Identifier: "12345678-1234-1234-1234-123456789abc", + Name: "_acme-challenge", + RData: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 300, + Type: "TXT", + }}, + State: "deployed", + }}, + } + + assert.Equal(t, expected, zone) +} + +func TestClient_DeleteRecord(t *testing.T) { + client := mockBuilder(). + Route("DELETE /api/clouddns/v1/zone.json/example.com/records/12345678-1234-1234-1234-123456789abc", + servermock.Noop()). + Build(t) + + err := client.DeleteRecord(t.Context(), "example.com", "12345678-1234-1234-1234-123456789abc") + require.NoError(t, err) +} + +func TestClient_DeleteRecord_error(t *testing.T) { + client := mockBuilder(). + Route("DELETE /api/clouddns/v1/zone.json/example.com/records/12345678-1234-1234-1234-123456789abc", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + err := client.DeleteRecord(t.Context(), "example.com", "12345678-1234-1234-1234-123456789abc") + require.EqualError(t, err, "401: Unauthorized") +} + +func TestClient_GetZone(t *testing.T) { + client := mockBuilder(). + Route("GET /api/clouddns/v1/zone.json/example.com", + servermock.ResponseFromFixture("get_zone.json")). + Build(t) + + zone, err := client.GetZone(t.Context(), "example.com") + require.NoError(t, err) + + expected := &Zone{ + Identifier: "fdb355ffd07c48aba3d4f6bf6a116296", + Name: "example.com", + TTL: 3600, + ZoneName: "", + Revisions: []Revision{{ + Identifier: "eeed7e08-f1ad-442b-9e75-369a0958c7d8", + Records: []Record{ + { + Identifier: "5ced498b-c89d-4487-824d-c03ded84f849", + Immutable: true, + Name: "@", + RData: "acns02.xaas.systems.", + Region: "9a1609af9dae4ce1a4ef63f51d305321", + TTL: 3600, + Type: "NS", + }, + { + Identifier: "12345678-1234-1234-1234-123456789abc", + Immutable: false, + Name: "_acme-challenge", + RData: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + Region: "", + TTL: 300, + Type: "TXT", + }, + }, + State: "active", + }}, + } + + assert.Equal(t, expected, zone) +} diff --git a/providers/dns/anexia/internal/fixtures/create_record-request.json b/providers/dns/anexia/internal/fixtures/create_record-request.json new file mode 100644 index 000000000..e82add260 --- /dev/null +++ b/providers/dns/anexia/internal/fixtures/create_record-request.json @@ -0,0 +1,7 @@ +{ + "name": "_acme-challenge", + "type": "TXT", + "rdata": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "region": "", + "ttl": 300 +} diff --git a/providers/dns/anexia/internal/fixtures/create_record.json b/providers/dns/anexia/internal/fixtures/create_record.json new file mode 100644 index 000000000..8c4f2c149 --- /dev/null +++ b/providers/dns/anexia/internal/fixtures/create_record.json @@ -0,0 +1,38 @@ +{ + "name": "example.com", + "zone_name": "example.com", + "master": true, + "dnssec_mode": "managed", + "admin_email": "admin@example.com", + "refresh": 10800, + "retry": 3600, + "expire": 604800, + "ttl": 86400, + "customer": "ANX12345", + "created_at": "0001-01-01T00:00:00Z", + "updated_at": "0001-01-01T00:00:00Z", + "published_at": "0001-01-01T00:00:00Z", + "is_editable": true, + "validation_level": 0, + "deployment_level": 0, + "revisions": [ + { + "created_at": "0001-01-01T00:00:00Z", + "identifier": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "modified_at": "0001-01-01T00:00:00Z", + "records": [ + { + "identifier": "12345678-1234-1234-1234-123456789abc", + "immutable": false, + "name": "_acme-challenge", + "rdata": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "region": "", + "ttl": 300, + "type": "TXT" + } + ], + "serial": 1, + "state": "deployed" + } + ] +} diff --git a/providers/dns/anexia/internal/fixtures/create_record_incomplete.json b/providers/dns/anexia/internal/fixtures/create_record_incomplete.json new file mode 100644 index 000000000..0515fcde3 --- /dev/null +++ b/providers/dns/anexia/internal/fixtures/create_record_incomplete.json @@ -0,0 +1,37 @@ +{ + "name": "example.com", + "zone_name": "example.com", + "master": true, + "dnssec_mode": "managed", + "admin_email": "admin@example.com", + "refresh": 10800, + "retry": 3600, + "expire": 604800, + "ttl": 86400, + "customer": "ANX12345", + "created_at": "0001-01-01T00:00:00Z", + "updated_at": "0001-01-01T00:00:00Z", + "published_at": "0001-01-01T00:00:00Z", + "is_editable": true, + "validation_level": 0, + "deployment_level": 0, + "revisions": [ + { + "created_at": "0001-01-01T00:00:00Z", + "identifier": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "modified_at": "0001-01-01T00:00:00Z", + "records": [ + { + "immutable": false, + "name": "_acme-challenge", + "rdata": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "region": "", + "ttl": 300, + "type": "TXT" + } + ], + "serial": 1, + "state": "deployed" + } + ] +} diff --git a/providers/dns/anexia/internal/fixtures/error.json b/providers/dns/anexia/internal/fixtures/error.json new file mode 100644 index 000000000..afed571fa --- /dev/null +++ b/providers/dns/anexia/internal/fixtures/error.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": 401, + "message": "Unauthorized" + } +} diff --git a/providers/dns/anexia/internal/fixtures/get_zone.json b/providers/dns/anexia/internal/fixtures/get_zone.json new file mode 100644 index 000000000..6e54594ff --- /dev/null +++ b/providers/dns/anexia/internal/fixtures/get_zone.json @@ -0,0 +1,82 @@ +{ + "identifier": "fdb355ffd07c48aba3d4f6bf6a116296", + "admin_email": "admin@example.com", + "created_at": "2019-02-06T10:02:07.000Z", + "current_revision": "eeed7e08-f1ad-442b-9e75-369a0958c7d8", + "deployment_level": 100, + "dns_servers": [ + { + "server": "acns01.xaas.systems", + "alias": null + }, + { + "server": "acns04.xaas.systems", + "alias": null + }, + { + "server": "acns02.xaas.systems", + "alias": null + }, + { + "server": "acns03.xaas.systems", + "alias": null + }, + { + "server": "acns05.xaas.systems", + "alias": null + } + ], + "dnsCluster": null, + "dnssec_ksk": null, + "dnssec_mode": "unvalidated", + "dnssec_sig_expires_at": null, + "dnssec_zsk": null, + "expire": 604800, + "inherit_ns_from": null, + "nameserver_set": null, + "master": true, + "master_ns": "acns02.xaas.systems.", + "name": "example.com", + "notify_allowed_ips": [ + "127.0.0.1" + ], + "published_at": "2023-06-20T08:41:06.000Z", + "refresh": 14400, + "revisions": [ + { + "created_at": "2023-06-20T08:41:06.000000Z", + "identifier": "eeed7e08-f1ad-442b-9e75-369a0958c7d8", + "modified_at": "2023-06-20T08:41:06.000000Z", + "records": [ + { + "identifier": "5ced498b-c89d-4487-824d-c03ded84f849", + "immutable": true, + "name": "@", + "rdata": "acns02.xaas.systems.", + "region": "9a1609af9dae4ce1a4ef63f51d305321", + "ttl": 3600, + "type": "NS", + "options": null + }, + { + "identifier": "12345678-1234-1234-1234-123456789abc", + "immutable": false, + "name": "_acme-challenge", + "rdata": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "region": "", + "ttl": 300, + "Type": "TXT" + } + ], + "serial": 14, + "state": "active" + } + ], + "retry": 3600, + "ttl": 3600, + "updated_at": "2020-06-04T18:34:22.000Z", + "validation_level": 100, + "whitelabel_config": null, + "is_editable": true, + "deploy_zone": "49459f420f614eb2a979fc7e961f83e6" +} diff --git a/providers/dns/anexia/internal/types.go b/providers/dns/anexia/internal/types.go new file mode 100644 index 000000000..f5546ca98 --- /dev/null +++ b/providers/dns/anexia/internal/types.go @@ -0,0 +1,38 @@ +package internal + +import "fmt" + +type APIError struct { + Details struct { + Code int `json:"code"` + Message string `json:"message"` + } `json:"error"` +} + +func (a *APIError) Error() string { + return fmt.Sprintf("%d: %s", a.Details.Code, a.Details.Message) +} + +type Zone struct { + Identifier string `json:"identifier,omitempty"` + Name string `json:"name,omitempty"` + TTL int `json:"ttl,omitempty"` + ZoneName string `json:"zone_name,omitempty"` + Revisions []Revision `json:"revisions,omitempty"` +} + +type Revision struct { + Identifier string `json:"identifier,omitempty"` + Records []Record `json:"records,omitempty"` + State string `json:"state,omitempty"` +} + +type Record struct { + Identifier string `json:"identifier,omitempty"` + Immutable bool `json:"immutable,omitempty"` + Name string `json:"name,omitempty"` + RData string `json:"rdata,omitempty"` + Region string `json:"region"` + TTL int `json:"ttl,omitempty"` + Type string `json:"type,omitempty"` +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 4dcf5317c..5cb428eb1 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/active24" "github.com/go-acme/lego/v4/providers/dns/alidns" "github.com/go-acme/lego/v4/providers/dns/allinkl" + "github.com/go-acme/lego/v4/providers/dns/anexia" "github.com/go-acme/lego/v4/providers/dns/arvancloud" "github.com/go-acme/lego/v4/providers/dns/auroradns" "github.com/go-acme/lego/v4/providers/dns/autodns" @@ -185,6 +186,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return alidns.NewDNSProvider() case "allinkl": return allinkl.NewDNSProvider() + case "anexia": + return anexia.NewDNSProvider() case "arvancloud": return arvancloud.NewDNSProvider() case "auroradns": From fe0a1f86686cae0cb6daab814be1d7e5019e7fd1 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 23 Oct 2025 21:06:17 +0200 Subject: [PATCH 193/298] hetzner: add deprecation logs (#2683) Co-authored-by: Dominik Menke --- providers/dns/hetzner/hetzner.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/providers/dns/hetzner/hetzner.go b/providers/dns/hetzner/hetzner.go index 4d74a29cd..1b02590d6 100644 --- a/providers/dns/hetzner/hetzner.go +++ b/providers/dns/hetzner/hetzner.go @@ -9,6 +9,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/hetznerv1" "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/legacy" @@ -76,6 +77,8 @@ func NewDNSProvider() (*DNSProvider, error) { return &DNSProvider{provider: provider}, nil case foundAPIKey: + log.Warnf("APIKey (legacy Hetzner DNS API) is deprecated, please use APIToken (Hetzner Cloud API) instead.") + provider, err := legacy.NewDNSProvider() if err != nil { return nil, err @@ -117,6 +120,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return &DNSProvider{provider: provider}, nil case config.APIKey != "": + log.Warnf("%s (legacy Hetzner DNS API) is deprecated, please use %s (Hetzner Cloud API) instead.", EnvAPIKey, EnvAPIToken) + cfg := &legacy.Config{ APIKey: config.APIKey, PropagationTimeout: config.PropagationTimeout, From 4bb17b02349d21bd9ca5a718632ce98b9772589f Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 27 Oct 2025 11:42:02 +0100 Subject: [PATCH 194/298] hostinger: fix record update (#2690) --- providers/dns/hostinger/hostinger.go | 100 +++++++++--------- providers/dns/hostinger/hostinger_test.go | 25 +---- providers/dns/hostinger/internal/client.go | 13 +++ .../dns/hostinger/internal/client_test.go | 44 ++++++-- .../internal/fixtures/delete_dns_records.json | 3 + .../fixtures/get_dns_records_empty.json | 10 ++ .../fixtures/update_dns_records-request.json | 15 +-- .../update_dns_records_base-request.json | 12 +-- .../update_dns_records_empty-request.json | 25 ----- providers/dns/hostinger/internal/types.go | 9 ++ 10 files changed, 124 insertions(+), 132 deletions(-) create mode 100644 providers/dns/hostinger/internal/fixtures/delete_dns_records.json delete mode 100644 providers/dns/hostinger/internal/fixtures/update_dns_records_empty-request.json diff --git a/providers/dns/hostinger/hostinger.go b/providers/dns/hostinger/hostinger.go index 14c8f1605..469e227f2 100644 --- a/providers/dns/hostinger/hostinger.go +++ b/providers/dns/hostinger/hostinger.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "time" "github.com/go-acme/lego/v4/challenge/dns01" @@ -103,38 +104,16 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { ctx := context.Background() - recordSets, err := d.client.GetDNSRecords(ctx, dns01.UnFqdn(authZone)) - if err != nil { - return fmt.Errorf("hostinger: get DNS records: %w", err) - } - - var newRecordSet []internal.RecordSet - - var added bool - - for _, recordSet := range recordSets { - if recordSet.Name == subDomain && recordSet.Type == "TXT" { - recordSet.Records = append(recordSet.Records, internal.Record{Content: info.Value}) - added = true - } - - newRecordSet = append(newRecordSet, recordSet) - } - - if !added { - newRecordSet = append(newRecordSet, internal.RecordSet{ + request := internal.ZoneRequest{ + Overwrite: false, + Zone: []internal.RecordSet{{ Name: subDomain, Type: "TXT", TTL: d.config.TTL, Records: []internal.Record{ {Content: info.Value}, }, - }) - } - - request := internal.ZoneRequest{ - Overwrite: false, - Zone: newRecordSet, + }}, } err = d.client.UpdateDNSRecords(ctx, dns01.UnFqdn(authZone), request) @@ -161,45 +140,45 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { ctx := context.Background() - recordSets, err := d.client.GetDNSRecords(ctx, dns01.UnFqdn(authZone)) + recordSet, err := d.findRecordSet(ctx, authZone, subDomain) if err != nil { - return fmt.Errorf("hostinger: get DNS records: %w", err) + return fmt.Errorf("hostinger: %w", err) } - var changed bool + var newRecords []internal.Record - var newRecordSet []internal.RecordSet - - for _, recordSet := range recordSets { - if recordSet.Name == subDomain && recordSet.Type == "TXT" { - var rs []internal.Record - - for _, record := range recordSet.Records { - if record.Content == info.Value { - changed = true - } else { - rs = append(rs, record) - } - } - - recordSet.Records = rs + for _, record := range recordSet.Records { + if record.Content == info.Value || record.Content == strconv.Quote(info.Value) { + continue } - newRecordSet = append(newRecordSet, recordSet) + newRecords = append(newRecords, record) } - if !changed { + recordSet.Records = newRecords + + if len(recordSet.Records) > 0 { + request := internal.ZoneRequest{ + Overwrite: true, + Zone: []internal.RecordSet{recordSet}, + } + + err = d.client.UpdateDNSRecords(ctx, dns01.UnFqdn(authZone), request) + if err != nil { + return fmt.Errorf("hostinger: update DNS records (delete): %w", err) + } + return nil } - request := internal.ZoneRequest{ - Overwrite: false, - Zone: newRecordSet, - } + filters := []internal.Filter{{ + Name: subDomain, + Type: "TXT", + }} - err = d.client.UpdateDNSRecords(ctx, dns01.UnFqdn(authZone), request) + err = d.client.DeleteDNSRecords(ctx, dns01.UnFqdn(authZone), filters) if err != nil { - return fmt.Errorf("hostinger: update DNS records (delete): %w", err) + return fmt.Errorf("hostinger: delete DNS records: %w", err) } return nil @@ -210,3 +189,20 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } + +func (d *DNSProvider) findRecordSet(ctx context.Context, authZone, subDomain string) (internal.RecordSet, error) { + recordSets, err := d.client.GetDNSRecords(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return internal.RecordSet{}, fmt.Errorf("get DNS records: %w", err) + } + + for _, recordSet := range recordSets { + if recordSet.Name != subDomain || recordSet.Type != "TXT" { + continue + } + + return recordSet, nil + } + + return internal.RecordSet{}, fmt.Errorf("no record found for domain %q and subdomain %q", authZone, subDomain) +} diff --git a/providers/dns/hostinger/hostinger_test.go b/providers/dns/hostinger/hostinger_test.go index 305a7a99c..967674618 100644 --- a/providers/dns/hostinger/hostinger_test.go +++ b/providers/dns/hostinger/hostinger_test.go @@ -1,7 +1,6 @@ package hostinger import ( - "net/http" "net/http/httptest" "net/url" "testing" @@ -116,8 +115,6 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { func TestDNSProvider_Present(t *testing.T) { provider := mockBuilder(). - Route("GET /api/dns/v1/zones/example.com", - servermock.ResponseFromInternal("get_dns_records.json")). Route("PUT /api/dns/v1/zones/example.com", servermock.ResponseFromInternal("update_dns_records.json"), servermock.CheckRequestJSONBodyFromInternal("update_dns_records-request.json")). @@ -127,20 +124,7 @@ func TestDNSProvider_Present(t *testing.T) { require.NoError(t, err) } -func TestDNSProvider_Present_empty(t *testing.T) { - provider := mockBuilder(). - Route("GET /api/dns/v1/zones/example.com", - servermock.ResponseFromInternal("get_dns_records_empty.json")). - Route("PUT /api/dns/v1/zones/example.com", - servermock.ResponseFromInternal("update_dns_records.json"), - servermock.CheckRequestJSONBodyFromInternal("update_dns_records_empty-request.json")). - Build(t) - - err := provider.Present("example.com", "", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp(t *testing.T) { +func TestDNSProvider_CleanUp_update(t *testing.T) { provider := mockBuilder(). Route("GET /api/dns/v1/zones/example.com", servermock.ResponseFromInternal("get_dns_records_acme.json")). @@ -153,12 +137,13 @@ func TestDNSProvider_CleanUp(t *testing.T) { require.NoError(t, err) } -func TestDNSProvider_CleanUp_empty(t *testing.T) { +func TestDNSProvider_CleanUp_delete(t *testing.T) { provider := mockBuilder(). Route("GET /api/dns/v1/zones/example.com", servermock.ResponseFromInternal("get_dns_records_empty.json")). - Route("PUT /api/dns/v1/zones/example.com", - servermock.Noop().WithStatusCode(http.StatusServiceUnavailable)). + Route("DELETE /api/dns/v1/zones/example.com", + servermock.ResponseFromInternal("delete_dns_records.json"), + servermock.CheckRequestJSONBody(`{"filters":[{"name":"_acme-challenge","type":"TXT"}]}`)). Build(t) err := provider.CleanUp("example.com", "", "123d==") diff --git a/providers/dns/hostinger/internal/client.go b/providers/dns/hostinger/internal/client.go index b4681bb2b..0fbf8acf6 100644 --- a/providers/dns/hostinger/internal/client.go +++ b/providers/dns/hostinger/internal/client.go @@ -73,6 +73,19 @@ func (c *Client) UpdateDNSRecords(ctx context.Context, domain string, zone ZoneR return c.do(req, nil) } +// DeleteDNSRecords deletes DNS records for the selected domain. +// https://developers.hostinger.com/#tag/dns-zone/delete/api/dns/v1/zones/{domain} +func (c *Client) DeleteDNSRecords(ctx context.Context, domain string, filters []Filter) error { + endpoint := c.BaseURL.JoinPath("/api/dns/v1/zones/", domain) + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, Filters{Filters: filters}) + if err != nil { + return err + } + + return c.do(req, nil) +} + func (c *Client) do(req *http.Request, result any) error { req.Header.Set(authorizationHeader, "Bearer "+c.token) diff --git a/providers/dns/hostinger/internal/client_test.go b/providers/dns/hostinger/internal/client_test.go index a031b0b5c..69cab5587 100644 --- a/providers/dns/hostinger/internal/client_test.go +++ b/providers/dns/hostinger/internal/client_test.go @@ -85,20 +85,11 @@ func TestClient_UpdateDNSRecords(t *testing.T) { { Name: "_acme-challenge", Records: []Record{ - {Content: "aaa"}, {Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"}, }, - TTL: 14400, + TTL: 120, Type: "TXT", }, - { - Name: "_acme-challenge", - Records: []Record{{ - Content: "example.com.", - }}, - TTL: 14400, - Type: "A", - }, }, } @@ -128,3 +119,36 @@ func TestClient_UpdateDNSRecords_error(t *testing.T) { require.EqualError(t, err, "26a91bd9-f8c8-4a83-9df9-83e23d696fe3: The name field is required. (and 1 more error): field_1: The field_1 field is required., The field_1 must be a number.") } + +func TestClient_DeleteDNSRecords(t *testing.T) { + client := mockBuilder(). + Route("DELETE /api/dns/v1/zones/example.com", + servermock.ResponseFromFixture("delete_dns_records.json"), + servermock.CheckRequestJSONBody(`{"filters":[{"name":"_acme-challenge","type":"TXT"}]}`)). + Build(t) + + filters := []Filter{{ + Name: "_acme-challenge", + Type: "TXT", + }} + + err := client.DeleteDNSRecords(t.Context(), "example.com", filters) + require.NoError(t, err) +} + +func TestClient_DeleteDNSRecords_error(t *testing.T) { + client := mockBuilder(). + Route("DELETE /api/dns/v1/zones/example.com", + servermock.ResponseFromFixture("error_401.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + filters := []Filter{{ + Name: "_acme-challenge", + Type: "TXT", + }} + + err := client.DeleteDNSRecords(t.Context(), "example.com", filters) + + require.EqualError(t, err, "26a91bd9-f8c8-4a83-9df9-83e23d696fe3: Unauthenticated") +} diff --git a/providers/dns/hostinger/internal/fixtures/delete_dns_records.json b/providers/dns/hostinger/internal/fixtures/delete_dns_records.json new file mode 100644 index 000000000..11d2582b4 --- /dev/null +++ b/providers/dns/hostinger/internal/fixtures/delete_dns_records.json @@ -0,0 +1,3 @@ +{ + "message": "Request accepted" +} diff --git a/providers/dns/hostinger/internal/fixtures/get_dns_records_empty.json b/providers/dns/hostinger/internal/fixtures/get_dns_records_empty.json index 89a0ebf9d..9989a3fc4 100644 --- a/providers/dns/hostinger/internal/fixtures/get_dns_records_empty.json +++ b/providers/dns/hostinger/internal/fixtures/get_dns_records_empty.json @@ -1,4 +1,14 @@ [ + { + "name": "_acme-challenge", + "records": [ + { + "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" + } + ], + "ttl": 14400, + "type": "TXT" + }, { "name": "_acme-challenge", "records": [ diff --git a/providers/dns/hostinger/internal/fixtures/update_dns_records-request.json b/providers/dns/hostinger/internal/fixtures/update_dns_records-request.json index 83d4ab6fb..6f287b3fc 100644 --- a/providers/dns/hostinger/internal/fixtures/update_dns_records-request.json +++ b/providers/dns/hostinger/internal/fixtures/update_dns_records-request.json @@ -4,25 +4,12 @@ { "name": "_acme-challenge", "records": [ - { - "content": "aaa" - }, { "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" } ], - "ttl": 14400, + "ttl": 120, "type": "TXT" - }, - { - "name": "_acme-challenge", - "records": [ - { - "content": "example.com." - } - ], - "ttl": 14400, - "type": "A" } ] } diff --git a/providers/dns/hostinger/internal/fixtures/update_dns_records_base-request.json b/providers/dns/hostinger/internal/fixtures/update_dns_records_base-request.json index a348db233..c42ddc6d7 100644 --- a/providers/dns/hostinger/internal/fixtures/update_dns_records_base-request.json +++ b/providers/dns/hostinger/internal/fixtures/update_dns_records_base-request.json @@ -1,5 +1,5 @@ { - "overwrite": false, + "overwrite": true, "zone": [ { "name": "_acme-challenge", @@ -10,16 +10,6 @@ ], "ttl": 14400, "type": "TXT" - }, - { - "name": "_acme-challenge", - "records": [ - { - "content": "example.com." - } - ], - "ttl": 14400, - "type": "A" } ] } diff --git a/providers/dns/hostinger/internal/fixtures/update_dns_records_empty-request.json b/providers/dns/hostinger/internal/fixtures/update_dns_records_empty-request.json deleted file mode 100644 index 81b0c3cf0..000000000 --- a/providers/dns/hostinger/internal/fixtures/update_dns_records_empty-request.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "overwrite": false, - "zone": [ - { - "name": "_acme-challenge", - "records": [ - { - "content": "example.com." - } - ], - "ttl": 14400, - "type": "A" - }, - { - "name": "_acme-challenge", - "records": [ - { - "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" - } - ], - "ttl": 120, - "type": "TXT" - } - ] -} diff --git a/providers/dns/hostinger/internal/types.go b/providers/dns/hostinger/internal/types.go index a79e9f5dc..ba0970dfb 100644 --- a/providers/dns/hostinger/internal/types.go +++ b/providers/dns/hostinger/internal/types.go @@ -37,3 +37,12 @@ type Record struct { Content string `json:"content,omitempty"` IsDisabled bool `json:"is_disabled,omitempty"` } + +type Filters struct { + Filters []Filter `json:"filters"` +} + +type Filter struct { + Name string `json:"name"` + Type string `json:"type"` +} From 5ea0509b86682432529dd20e1f5556a4ddc1b026 Mon Sep 17 00:00:00 2001 From: CzBiX Date: Mon, 27 Oct 2025 18:43:19 +0800 Subject: [PATCH 195/298] docs: update name and links for Profiles Extension RFC (#2689) --- README.md | 2 +- acme/api/order.go | 2 +- acme/commons.go | 4 ++-- certificate/certificates.go | 4 ++-- cmd/cmd_renew.go | 2 +- cmd/cmd_run.go | 2 +- docs/content/_index.md | 2 +- docs/data/zz_cli_help.toml | 4 ++-- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e02eff0c7..c3d739f0c 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ So if you think that lego is worth it, please consider [donating](https://donate - Support [RFC 8737](https://www.rfc-editor.org/rfc/rfc8737.html): TLS Application‑Layer Protocol Negotiation (ALPN) Challenge Extension - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): certificates for IP addresses - Support [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html): Renewal Information (ARI) Extension - - Support [draft-aaron-acme-profiles-00](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/): Profiles Extension + - Support [draft-ietf-acme-profiles-00](https://datatracker.ietf.org/doc/draft-ietf-acme-profiles/): Profiles Extension - Comes with about [170 DNS providers](https://go-acme.github.io/lego/dns) - Register with CA - Obtain certificates, both from scratch or with an existing CSR diff --git a/acme/api/order.go b/acme/api/order.go index 13caa0d08..0c679fdb1 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -17,7 +17,7 @@ type OrderOptions struct { // A string uniquely identifying the profile // which will be used to affect issuance of the certificate requested by this Order. - // - https://www.ietf.org/id/draft-aaron-acme-profiles-00.html#section-4 + // - https://www.ietf.org/id/draft-ietf-acme-profiles-00.html#section-4 Profile string // A string uniquely identifying a previously-issued certificate which this diff --git a/acme/commons.go b/acme/commons.go index 489c74938..bd0506c09 100644 --- a/acme/commons.go +++ b/acme/commons.go @@ -77,7 +77,7 @@ type Meta struct { // profiles (optional, object): // A map of profile names to human-readable descriptions of those profiles. - // https://www.ietf.org/id/draft-aaron-acme-profiles-00.html#section-3 + // https://www.ietf.org/id/draft-ietf-acme-profiles-00.html#section-3 Profiles map[string]string `json:"profiles"` } @@ -156,7 +156,7 @@ type Order struct { // profile (string, optional): // A string uniquely identifying the profile // which will be used to affect issuance of the certificate requested by this Order. - // https://www.ietf.org/id/draft-aaron-acme-profiles-00.html#section-4 + // https://www.ietf.org/id/draft-ietf-acme-profiles-00.html#section-4 Profile string `json:"profile,omitempty"` // notBefore (optional, string): diff --git a/certificate/certificates.go b/certificate/certificates.go index e5830722d..df51dc333 100644 --- a/certificate/certificates.go +++ b/certificate/certificates.go @@ -77,7 +77,7 @@ type ObtainRequest struct { // A string uniquely identifying the profile // which will be used to affect issuance of the certificate requested by this Order. - // - https://www.ietf.org/id/draft-aaron-acme-profiles-00.html#section-4 + // - https://www.ietf.org/id/draft-ietf-acme-profiles-00.html#section-4 Profile string AlwaysDeactivateAuthorizations bool @@ -106,7 +106,7 @@ type ObtainForCSRRequest struct { // A string uniquely identifying the profile // which will be used to affect issuance of the certificate requested by this Order. - // - https://www.ietf.org/id/draft-aaron-acme-profiles-00.html#section-4 + // - https://www.ietf.org/id/draft-ietf-acme-profiles-00.html#section-4 Profile string AlwaysDeactivateAuthorizations bool diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index 9b1be8c95..afedee0b6 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -101,7 +101,7 @@ func createRenew() *cli.Command { }, &cli.StringFlag{ Name: flgProfile, - Usage: "If the CA offers multiple certificate profiles (draft-aaron-acme-profiles), choose this one.", + Usage: "If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one.", }, &cli.StringFlag{ Name: flgAlwaysDeactivateAuthorizations, diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index 8135e3546..905fc6bb2 100644 --- a/cmd/cmd_run.go +++ b/cmd/cmd_run.go @@ -76,7 +76,7 @@ func createRun() *cli.Command { }, &cli.StringFlag{ Name: flgProfile, - Usage: "If the CA offers multiple certificate profiles (draft-aaron-acme-profiles), choose this one.", + Usage: "If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one.", }, &cli.StringFlag{ Name: flgAlwaysDeactivateAuthorizations, diff --git a/docs/content/_index.md b/docs/content/_index.md index a211c5d62..229435e7d 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -13,7 +13,7 @@ Let's Encrypt client and ACME library written in Go. - Support [RFC 8737](https://www.rfc-editor.org/rfc/rfc8737.html): TLS Application‑Layer Protocol Negotiation (ALPN) Challenge Extension - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): issues certificates for IP addresses - Support [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html): Renewal Information (ARI) Extension - - Support [draft-aaron-acme-profiles-00](https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/): Profiles Extension + - Support [draft-ietf-acme-profiles-00](https://datatracker.ietf.org/doc/draft-ietf-acme-profiles/): Profiles Extension - Comes with about [150 DNS providers]({{% ref "dns" %}}) - Register with CA - Obtain certificates, both from scratch or with an existing CSR diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index bebc8b763..bab9bd525 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -76,7 +76,7 @@ OPTIONS: --not-after value Set the notAfter field in the certificate (RFC3339 format) --private-key value Path to private key (in PEM encoding) for the certificate. By default, the private key is generated. --preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. - --profile value If the CA offers multiple certificate profiles (draft-aaron-acme-profiles), choose this one. + --profile value If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one. --always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful. --run-hook value Define a hook. The hook is executed when the certificates are effectively created. --run-hook-timeout value Define the timeout for the hook execution. (default: 2m0s) @@ -103,7 +103,7 @@ OPTIONS: --not-before value Set the notBefore field in the certificate (RFC3339 format) --not-after value Set the notAfter field in the certificate (RFC3339 format) --preferred-chain value If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. - --profile value If the CA offers multiple certificate profiles (draft-aaron-acme-profiles), choose this one. + --profile value If the CA offers multiple certificate profiles (draft-ietf-acme-profiles), choose this one. --always-deactivate-authorizations value Force the authorizations to be relinquished even if the certificate request was successful. --renew-hook value Define a hook. The hook is executed only when the certificates are effectively renewed. --renew-hook-timeout value Define the timeout for the hook execution. (default: 2m0s) From 12dc42accff7e02be0d89b935139d0f581a826a1 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 28 Oct 2025 17:56:58 +0100 Subject: [PATCH 196/298] chore: update vegadns client (#2691) --- go.mod | 2 +- go.sum | 4 +- providers/dns/vegadns/vegadns.go | 53 ++++++++++++++++++--------- providers/dns/vegadns/vegadns_test.go | 8 ++-- 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index d99e9ff70..0a7bbb119 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,6 @@ require ( github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 github.com/Azure/go-autorest/autorest/to v0.4.1 github.com/BurntSushi/toml v1.5.0 - github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 github.com/alibabacloud-go/tea v1.3.13 @@ -66,6 +65,7 @@ require ( github.com/nrdcg/oci-go-sdk/common/v1065 v1065.102.0 github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.102.0 github.com/nrdcg/porkbun v0.4.0 + github.com/nrdcg/vegadns v0.2.0 github.com/nzdjb/go-metaname v1.0.0 github.com/ovh/go-ovh v1.9.0 github.com/pquerna/otp v1.5.0 diff --git a/go.sum b/go.sum index 606cecb72..c60a4be6e 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,6 @@ github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXY github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24= -github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= @@ -711,6 +709,8 @@ github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.102.0 h1:gAOs1dkE7LFoWflzqrDqAhOprc0 github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.102.0/go.mod h1:EUBSYwop1K40VpcKy1haIK6kFK/gPT1atEk89OkY0Kg= github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= +github.com/nrdcg/vegadns v0.2.0 h1:tLs8WDuWORJP+YlDOKf+eQCKtIwXeUW17R5Ls5NcyB8= +github.com/nrdcg/vegadns v0.2.0/go.mod h1:NqSyRKZuJlAsv8VI/7rSubfPXN68NwaJ0aG9KxQVFVo= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= diff --git a/providers/dns/vegadns/vegadns.go b/providers/dns/vegadns/vegadns.go index 824f727eb..6ddb33728 100644 --- a/providers/dns/vegadns/vegadns.go +++ b/providers/dns/vegadns/vegadns.go @@ -2,14 +2,15 @@ package vegadns import ( + "context" "errors" "fmt" "time" - vegaClient "github.com/OpenDNS/vegadns2client" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/nrdcg/vegadns" ) // Environment variables names. @@ -49,7 +50,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { config *Config - client vegaClient.VegaDNSClient + client *vegadns.Client } // NewDNSProvider returns a DNSProvider instance configured for VegaDNS. @@ -75,11 +76,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("vegadns: the configuration of the DNS provider is nil") } - vega := vegaClient.NewVegaDNSClient(config.BaseURL) - vega.APIKey = config.APIKey - vega.APISecret = config.APISecret + client, err := vegadns.NewClient(config.BaseURL, vegadns.WithOAuth(config.APIKey, config.APISecret)) + if err != nil { + return nil, fmt.Errorf("vegadns: %w", err) + } - return &DNSProvider{client: vega, config: config}, nil + return &DNSProvider{client: client, config: config}, nil } // Timeout returns the timeout and interval to use when checking for DNS propagation. @@ -90,39 +92,56 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record to fulfill the dns-01 challenge. func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) - _, domainID, err := d.client.GetAuthZone(info.EffectiveFQDN) + domainID, err := d.findDomainID(ctx, info.EffectiveFQDN) if err != nil { - return fmt.Errorf("vegadns: can't find Authoritative Zone for %s in Present: %w", info.EffectiveFQDN, err) + return fmt.Errorf("vegadns: find domain ID for %s: %w", info.EffectiveFQDN, err) } - err = d.client.CreateTXT(domainID, info.EffectiveFQDN, info.Value, d.config.TTL) + err = d.client.CreateTXTRecord(ctx, domainID, dns01.UnFqdn(info.EffectiveFQDN), info.Value, d.config.TTL) if err != nil { - return fmt.Errorf("vegadns: %w", err) + return fmt.Errorf("vegadns: create TXT record: %w", err) } + return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) - _, domainID, err := d.client.GetAuthZone(info.EffectiveFQDN) + domainID, err := d.findDomainID(ctx, info.EffectiveFQDN) if err != nil { - return fmt.Errorf("vegadns: can't find Authoritative Zone for %s in CleanUp: %w", info.EffectiveFQDN, err) + return fmt.Errorf("vegadns: find domain ID for %s: %w", info.EffectiveFQDN, err) } - txt := dns01.UnFqdn(info.EffectiveFQDN) - - recordID, err := d.client.GetRecordID(domainID, txt, "TXT") + recordID, err := d.client.GetRecordID(ctx, domainID, dns01.UnFqdn(info.EffectiveFQDN), "TXT") if err != nil { - return fmt.Errorf("vegadns: couldn't get Record ID in CleanUp: %w", err) + return fmt.Errorf("vegadns: get Record ID: %w", err) } - err = d.client.DeleteRecord(recordID) + err = d.client.DeleteRecord(ctx, recordID) if err != nil { return fmt.Errorf("vegadns: %w", err) } + return nil } + +func (d *DNSProvider) findDomainID(ctx context.Context, fqdn string) (int, error) { + for host := range dns01.UnFqdnDomainsSeq(fqdn) { + id, err := d.client.GetDomainID(ctx, host) + if err != nil { + continue + } + + return id, nil + } + + return 0, errors.New("domain not found") +} diff --git a/providers/dns/vegadns/vegadns_test.go b/providers/dns/vegadns/vegadns_test.go index 48f54faab..fe40057c4 100644 --- a/providers/dns/vegadns/vegadns_test.go +++ b/providers/dns/vegadns/vegadns_test.go @@ -61,7 +61,7 @@ func TestDNSProvider_Present(t *testing.T) { Route("GET /1.0/domains", servermock.Noop(). WithStatusCode(http.StatusNotFound)), - expectedError: "vegadns: can't find Authoritative Zone for _acme-challenge.example.com. in Present: Unable to find auth zone for fqdn _acme-challenge.example.com", + expectedError: "vegadns: find domain ID for _acme-challenge.example.com.: domain not found", }, { desc: "FailToCreateTXT", @@ -72,7 +72,7 @@ func TestDNSProvider_Present(t *testing.T) { Route("POST /1.0/records", servermock.Noop(). WithStatusCode(http.StatusBadRequest)), - expectedError: "vegadns: Got bad answer from VegaDNS on CreateTXT. Code: 400. Message: ", + expectedError: "vegadns: create TXT record: bad answer from VegaDNS (code: 400, message: )", }, } @@ -119,7 +119,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { Route("GET /1.0/domains", servermock.Noop(). WithStatusCode(http.StatusNotFound)), - expectedError: "vegadns: can't find Authoritative Zone for _acme-challenge.example.com. in CleanUp: Unable to find auth zone for fqdn _acme-challenge.example.com", + expectedError: "vegadns: find domain ID for _acme-challenge.example.com.: domain not found", }, { desc: "FailToGetRecordID", @@ -131,7 +131,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { servermock.Noop(). WithStatusCode(http.StatusNotFound), servermock.CheckQueryParameter().With("domain_id", "1")), - expectedError: "vegadns: couldn't get Record ID in CleanUp: Got bad answer from VegaDNS on GetRecordID. Code: 404. Message: ", + expectedError: "vegadns: get Record ID: bad answer from VegaDNS (code: 404, message: )", }, } From da8280ac49d72e40d96a764f55a0cce2ebfba02a Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 29 Oct 2025 19:18:38 +0100 Subject: [PATCH 197/298] chore: add debug transport on DNS API clients (#2692) Co-authored-by: Dominik Menke --- cmd/zz_gen_cmd_dnshelp.go | 5 + docs/content/dns/zz_gen_bunny.md | 1 + docs/content/dns/zz_gen_mailinabox.md | 1 + docs/content/dns/zz_gen_scaleway.md | 1 + docs/content/dns/zz_gen_transip.md | 1 + docs/content/dns/zz_gen_vinyldns.md | 1 + docs/content/usage/cli/Options.md | 19 ++ go.mod | 4 +- go.sum | 8 +- providers/dns/active24/active24.go | 3 + providers/dns/allinkl/allinkl.go | 5 + providers/dns/anexia/anexia.go | 3 + providers/dns/arvancloud/arvancloud.go | 3 + providers/dns/auroradns/auroradns.go | 3 +- providers/dns/autodns/autodns.go | 3 + providers/dns/axelname/axelname.go | 3 + providers/dns/azion/azion.go | 3 + providers/dns/azuredns/azuredns.go | 3 + providers/dns/beget/beget.go | 3 + providers/dns/beget/beget_test.go | 2 +- providers/dns/binarylane/binarylane.go | 3 + providers/dns/bindman/bindman.go | 14 +- providers/dns/bluecat/bluecat.go | 3 + providers/dns/bookmyname/bookmyname.go | 3 + providers/dns/brandit/brandit.go | 3 + providers/dns/bunny/bunny.go | 25 ++- providers/dns/bunny/bunny.toml | 1 + providers/dns/checkdomain/checkdomain.go | 7 +- providers/dns/civo/civo.go | 7 +- providers/dns/clouddns/clouddns.go | 3 + providers/dns/cloudflare/internal/client.go | 3 + providers/dns/cloudns/cloudns.go | 7 +- providers/dns/cloudru/cloudru.go | 3 + providers/dns/conoha/conoha.go | 5 + providers/dns/conohav3/conohav3.go | 5 + providers/dns/constellix/constellix.go | 3 +- providers/dns/corenetworks/corenetworks.go | 3 + providers/dns/cpanel/cpanel.go | 5 + providers/dns/derak/derak.go | 3 + providers/dns/desec/desec.go | 8 + providers/dns/digitalocean/digitalocean.go | 7 +- providers/dns/directadmin/directadmin.go | 3 + providers/dns/dnshomede/dnshomede.go | 7 + providers/dns/dnsimple/dnsimple.go | 11 +- providers/dns/dnsmadeeasy/dnsmadeeasy.go | 8 +- providers/dns/dnspod/dnspod.go | 8 +- providers/dns/dode/dode.go | 3 + providers/dns/domeneshop/domeneshop.go | 3 + providers/dns/dreamhost/dreamhost.go | 3 + providers/dns/duckdns/duckdns.go | 3 + providers/dns/dyn/dyn.go | 3 + providers/dns/dyndnsfree/dyndnsfree.go | 3 + providers/dns/dynu/dynu.go | 4 +- providers/dns/easydns/easydns.go | 3 + providers/dns/efficientip/efficientip.go | 3 + providers/dns/epik/epik.go | 3 + providers/dns/exoscale/exoscale.go | 3 +- providers/dns/f5xc/f5xc.go | 3 + providers/dns/freemyip/freemyip.go | 3 + providers/dns/gandi/gandi.go | 3 + providers/dns/gandiv5/gandiv5.go | 3 + providers/dns/gcloud/googlecloud.go | 3 +- providers/dns/gcore/gcore.go | 3 + providers/dns/glesys/glesys.go | 3 + providers/dns/godaddy/godaddy.go | 3 + .../hetzner/internal/hetznerv1/hetznerv1.go | 7 +- .../dns/hetzner/internal/legacy/hetzner.go | 3 + providers/dns/hostingde/hostingde.go | 11 +- providers/dns/hostinger/hostinger.go | 3 + providers/dns/hostinger/hostinger_test.go | 2 +- providers/dns/hosttech/hosttech.go | 7 +- providers/dns/httpnet/httpnet.go | 7 + providers/dns/httpreq/httpreq.go | 4 + providers/dns/hurricane/hurricane.go | 7 + providers/dns/hyperone/hyperone.go | 3 + providers/dns/infomaniak/infomaniak.go | 7 +- .../dns/internal/clientdebug/.gitattributes | 1 + providers/dns/internal/clientdebug/client.go | 131 ++++++++++++++ .../dns/internal/clientdebug/client_test.go | 168 ++++++++++++++++++ .../clientdebug/testdata/env_vars.txt | 32 ++++ .../internal/clientdebug/testdata/headers.txt | 32 ++++ .../internal/clientdebug/testdata/values.txt | 32 ++++ providers/dns/internetbs/internetbs.go | 3 + providers/dns/ionos/ionos.go | 3 + providers/dns/ipv64/ipv64.go | 3 + providers/dns/iwantmyname/iwantmyname.go | 3 + providers/dns/joker/provider_dmapi.go | 3 + providers/dns/joker/provider_svc.go | 3 + providers/dns/keyhelp/keyhelp.go | 3 + providers/dns/liara/liara.go | 7 +- providers/dns/limacity/limacity.go | 7 + providers/dns/linode/linode.go | 3 +- providers/dns/loopia/loopia.go | 3 + providers/dns/luadns/luadns.go | 3 + providers/dns/mailinabox/mailinabox.go | 15 +- providers/dns/mailinabox/mailinabox.toml | 1 + providers/dns/manageengine/internal/client.go | 4 +- .../dns/manageengine/internal/client_test.go | 4 +- .../dns/manageengine/internal/identity.go | 2 +- providers/dns/manageengine/manageengine.go | 9 +- providers/dns/metaregistrar/metaregistrar.go | 3 + providers/dns/mijnhost/mijnhost.go | 7 + providers/dns/mittwald/mittwald.go | 11 +- providers/dns/myaddr/myaddr.go | 3 + providers/dns/mydnsjp/mydnsjp.go | 11 +- providers/dns/mythicbeasts/mythicbeasts.go | 3 + providers/dns/namecheap/namecheap.go | 3 + providers/dns/namedotcom/namedotcom.go | 8 +- providers/dns/namesilo/namesilo.go | 7 +- .../dns/nearlyfreespeech/nearlyfreespeech.go | 3 + providers/dns/netcup/netcup.go | 7 +- providers/dns/netlify/netlify.go | 7 +- providers/dns/nicmanager/nicmanager.go | 3 + providers/dns/nicru/nicru.go | 3 +- providers/dns/nifcloud/nifcloud.go | 3 + providers/dns/njalla/njalla.go | 3 + providers/dns/nodion/nodion.go | 3 + providers/dns/ns1/ns1.go | 8 +- providers/dns/octenium/octenium.go | 7 +- providers/dns/octenium/octenium_test.go | 2 +- providers/dns/oraclecloud/oraclecloud.go | 3 +- providers/dns/otc/otc.go | 3 + providers/dns/ovh/ovh.go | 7 + providers/dns/pdns/pdns.go | 7 + providers/dns/plesk/plesk.go | 3 + providers/dns/porkbun/porkbun.go | 3 + providers/dns/rackspace/rackspace.go | 3 + providers/dns/rainyun/rainyun.go | 3 + providers/dns/rcodezero/rcodezero.go | 3 + providers/dns/regfish/regfish.go | 10 ++ providers/dns/regru/regru.go | 3 + providers/dns/rimuhosting/rimuhosting.go | 3 + providers/dns/safedns/safedns.go | 3 + providers/dns/sakuracloud/sakuracloud.go | 3 +- providers/dns/scaleway/scaleway.go | 18 +- providers/dns/scaleway/scaleway.toml | 1 + providers/dns/selectel/selectel.go | 4 + providers/dns/selectelv2/selectelv2.go | 3 +- providers/dns/selfhostde/selfhostde.go | 3 + providers/dns/servercow/servercow.go | 3 + providers/dns/shellrent/shellrent.go | 3 + providers/dns/simply/simply.go | 3 + providers/dns/sonic/sonic.go | 3 + providers/dns/spaceship/spaceship.go | 3 + providers/dns/stackpath/internal/client.go | 4 +- .../dns/stackpath/internal/client_test.go | 5 +- providers/dns/stackpath/internal/identity.go | 2 +- providers/dns/stackpath/stackpath.go | 12 +- providers/dns/technitium/technitium.go | 3 + providers/dns/timewebcloud/timewebcloud.go | 7 +- providers/dns/transip/transip.go | 19 +- providers/dns/transip/transip.toml | 1 + providers/dns/variomedia/variomedia.go | 3 + providers/dns/vegadns/vegadns.go | 25 ++- providers/dns/vercel/vercel.go | 8 +- providers/dns/versio/versio.go | 3 + providers/dns/vinyldns/vinyldns.go | 16 +- providers/dns/vinyldns/vinyldns.toml | 1 + providers/dns/vinyldns/vinyldns_test.go | 10 +- providers/dns/vscale/vscale.go | 4 + providers/dns/vultr/vultr.go | 5 +- providers/dns/webnames/webnames.go | 3 + providers/dns/websupport/websupport.go | 3 + providers/dns/wedos/wedos.go | 3 + providers/dns/westcn/internal/client.go | 2 +- providers/dns/westcn/westcn.go | 3 + providers/dns/yandex/yandex.go | 3 + providers/dns/yandex360/yandex360.go | 3 + providers/dns/zoneedit/zoneedit.go | 3 + providers/dns/zoneee/zoneee.go | 4 + providers/dns/zonomi/zonomi.go | 3 + 171 files changed, 1100 insertions(+), 91 deletions(-) create mode 100644 providers/dns/internal/clientdebug/.gitattributes create mode 100644 providers/dns/internal/clientdebug/client.go create mode 100644 providers/dns/internal/clientdebug/client_test.go create mode 100644 providers/dns/internal/clientdebug/testdata/env_vars.txt create mode 100644 providers/dns/internal/clientdebug/testdata/headers.txt create mode 100644 providers/dns/internal/clientdebug/testdata/values.txt diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 4c76a0f51..898f87ec9 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -614,6 +614,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) + ew.writeln(` - "BUNNY_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "BUNNY_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "BUNNY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln(` - "BUNNY_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) @@ -2171,6 +2172,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) + ew.writeln(` - "MAILINABOX_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "MAILINABOX_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 4)`) ew.writeln(` - "MAILINABOX_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) @@ -3001,6 +3003,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "SCW_ACCESS_KEY": Access key`) + ew.writeln(` - "SCW_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "SCW_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) ew.writeln(` - "SCW_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln(` - "SCW_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) @@ -3281,6 +3284,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) + ew.writeln(` - "TRANSIP_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "TRANSIP_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) ew.writeln(` - "TRANSIP_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 600)`) ew.writeln(` - "TRANSIP_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 10)`) @@ -3409,6 +3413,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`Additional Configuration:`) + ew.writeln(` - "VINYLDNS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "VINYLDNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 4)`) ew.writeln(` - "VINYLDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) ew.writeln(` - "VINYLDNS_QUOTE_VALUE": Adds quotes around the TXT record value (Default: false)`) diff --git a/docs/content/dns/zz_gen_bunny.md b/docs/content/dns/zz_gen_bunny.md index 7b4db2020..884c61aea 100644 --- a/docs/content/dns/zz_gen_bunny.md +++ b/docs/content/dns/zz_gen_bunny.md @@ -47,6 +47,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| +| `BUNNY_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `BUNNY_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `BUNNY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | | `BUNNY_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | diff --git a/docs/content/dns/zz_gen_mailinabox.md b/docs/content/dns/zz_gen_mailinabox.md index 8b5048c60..3ffed1cc7 100644 --- a/docs/content/dns/zz_gen_mailinabox.md +++ b/docs/content/dns/zz_gen_mailinabox.md @@ -51,6 +51,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| +| `MAILINABOX_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `MAILINABOX_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 4) | | `MAILINABOX_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | diff --git a/docs/content/dns/zz_gen_scaleway.md b/docs/content/dns/zz_gen_scaleway.md index 7f9d6b7c7..2f6af9d8a 100644 --- a/docs/content/dns/zz_gen_scaleway.md +++ b/docs/content/dns/zz_gen_scaleway.md @@ -49,6 +49,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `SCW_ACCESS_KEY` | Access key | +| `SCW_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `SCW_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | | `SCW_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | | `SCW_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | diff --git a/docs/content/dns/zz_gen_transip.md b/docs/content/dns/zz_gen_transip.md index 68b0f7acf..769fbc734 100644 --- a/docs/content/dns/zz_gen_transip.md +++ b/docs/content/dns/zz_gen_transip.md @@ -49,6 +49,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| +| `TRANSIP_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `TRANSIP_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | | `TRANSIP_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 600) | | `TRANSIP_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 10) | diff --git a/docs/content/dns/zz_gen_vinyldns.md b/docs/content/dns/zz_gen_vinyldns.md index 9a9c4bef0..666bc39c4 100644 --- a/docs/content/dns/zz_gen_vinyldns.md +++ b/docs/content/dns/zz_gen_vinyldns.md @@ -51,6 +51,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| +| `VINYLDNS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `VINYLDNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 4) | | `VINYLDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | | `VINYLDNS_QUOTE_VALUE` | Adds quotes around the TXT record value (Default: false) | diff --git a/docs/content/usage/cli/Options.md b/docs/content/usage/cli/Options.md index 25ba7e593..7b5df027a 100644 --- a/docs/content/usage/cli/Options.md +++ b/docs/content/usage/cli/Options.md @@ -143,6 +143,25 @@ Example: LEGO_DEBUG_CLIENT_VERBOSE_ERROR=true ``` +### LEGO_DEBUG_DNS_API_HTTP_CLIENT + +> **⚠️ WARNING: This will expose credentials in the log output! ⚠️** +> +> Do not run this in production environments, or if you can't be sure that logs aren't accessed by third parties or tools (like log collectors). +> +> You have been warned. Here be dragons. + +The environment variable `LEGO_DEBUG_DNS_API_HTTP_CLIENT` allows debugging the DNS API interaction. +It will dump the full request and response to the log output. + +Some DNS providers don't support this option. + +Example: + +```bash +LEGO_DEBUG_DNS_API_HTTP_CLIENT=true +``` + ### LEGO_DEBUG_ACME_HTTP_CLIENT The environment variable `LEGO_DEBUG_ACME_HTTP_CLIENT` allows debug the calls to the ACME server. diff --git a/go.mod b/go.mod index 0a7bbb119..2337d068e 100644 --- a/go.mod +++ b/go.mod @@ -53,13 +53,13 @@ require ( github.com/mimuret/golang-iij-dpf v0.9.1 github.com/namedotcom/go/v4 v4.0.2 github.com/nrdcg/auroradns v1.1.0 - github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea + github.com/nrdcg/bunny-go v0.1.0 github.com/nrdcg/desec v0.11.0 github.com/nrdcg/dnspod-go v0.4.0 github.com/nrdcg/freemyip v0.3.0 github.com/nrdcg/goacmedns v0.2.0 github.com/nrdcg/goinwx v0.11.0 - github.com/nrdcg/mailinabox v0.2.0 + github.com/nrdcg/mailinabox v0.3.0 github.com/nrdcg/namesilo v0.5.0 github.com/nrdcg/nodion v0.1.0 github.com/nrdcg/oci-go-sdk/common/v1065 v1065.102.0 diff --git a/go.sum b/go.sum index c60a4be6e..382959ff4 100644 --- a/go.sum +++ b/go.sum @@ -685,8 +685,8 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo= github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk= -github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea h1:OSgRS4kqOs/WuxuFOObP2gwrenL4/qiKXQbQugr/Two= -github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea/go.mod h1:IDRRngAngb2eTEaWgpO0hukQFI/vJId46fT1KErMytA= +github.com/nrdcg/bunny-go v0.1.0 h1:GAHTRpHaG/TxfLZlqoJ8OJFzw8rI74+jOTkzxWh0uHA= +github.com/nrdcg/bunny-go v0.1.0/go.mod h1:u+C9dgsspgtWVaAz6QkyV17s9fxD8viwwKoxb9XMz1A= github.com/nrdcg/desec v0.11.0 h1:XZVHy07sg12y8FozMp+l7XvzPsdzog0AYXuQMaHBsfs= github.com/nrdcg/desec v0.11.0/go.mod h1:5+4vyhMRTs49V9CNoODF/HwT8Mwxv9DJ6j+7NekUnBs= github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U= @@ -697,8 +697,8 @@ github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0 github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg= github.com/nrdcg/goinwx v0.11.0 h1:GER0SE3POub7rxARt3Y3jRy1OON1hwF1LRxHz5xsFBw= github.com/nrdcg/goinwx v0.11.0/go.mod h1:0BXSC0FxVtU4aTjX0Zw3x0DK32tjugLzeNIAGtwXvPQ= -github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk= -github.com/nrdcg/mailinabox v0.2.0/go.mod h1:0yxqeYOiGyxAu7Sb94eMxHPIOsPYXAjTeA9ZhePhGnc= +github.com/nrdcg/mailinabox v0.3.0 h1:PHkC1elKXKAjEvdx2HHFMgcEGZFqudAl7aU3L2JDhM4= +github.com/nrdcg/mailinabox v0.3.0/go.mod h1:1eFIGcM4lI+AfFOUpbs548SFGz1ZWoMOGbECBmkghw4= github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE= github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= diff --git a/providers/dns/active24/active24.go b/providers/dns/active24/active24.go index 1acd72f61..c8107cab6 100644 --- a/providers/dns/active24/active24.go +++ b/providers/dns/active24/active24.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/internal/active24" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) const baseAPIDomain = "active24.cz" @@ -87,6 +88,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/allinkl/allinkl.go b/providers/dns/allinkl/allinkl.go index b1a40ae64..5be194ed2 100644 --- a/providers/dns/allinkl/allinkl.go +++ b/providers/dns/allinkl/allinkl.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/allinkl/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -92,12 +93,16 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { identifier.HTTPClient = config.HTTPClient } + identifier.HTTPClient = clientdebug.Wrap(identifier.HTTPClient) + client := internal.NewClient(config.Login) if config.HTTPClient != nil { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, identifier: identifier, diff --git a/providers/dns/anexia/anexia.go b/providers/dns/anexia/anexia.go index 9328c14c5..568ef5263 100644 --- a/providers/dns/anexia/anexia.go +++ b/providers/dns/anexia/anexia.go @@ -15,6 +15,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/anexia/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -105,6 +106,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/arvancloud/arvancloud.go b/providers/dns/arvancloud/arvancloud.go index 3dd4eee70..4b5fbab62 100644 --- a/providers/dns/arvancloud/arvancloud.go +++ b/providers/dns/arvancloud/arvancloud.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/arvancloud/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -95,6 +96,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/auroradns/auroradns.go b/providers/dns/auroradns/auroradns.go index d41b271ed..95d6ab759 100644 --- a/providers/dns/auroradns/auroradns.go +++ b/providers/dns/auroradns/auroradns.go @@ -10,6 +10,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/miekg/dns" "github.com/nrdcg/auroradns" ) @@ -94,7 +95,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("aurora: %w", err) } - client, err := auroradns.NewClient(tr.Client(), auroradns.WithBaseURL(config.BaseURL)) + client, err := auroradns.NewClient(clientdebug.Wrap(tr.Client()), auroradns.WithBaseURL(config.BaseURL)) if err != nil { return nil, fmt.Errorf("aurora: %w", err) } diff --git a/providers/dns/autodns/autodns.go b/providers/dns/autodns/autodns.go index 61f3005f1..770bac99b 100644 --- a/providers/dns/autodns/autodns.go +++ b/providers/dns/autodns/autodns.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/autodns/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -105,6 +106,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/axelname/axelname.go b/providers/dns/axelname/axelname.go index 033ccc92b..96d26236e 100644 --- a/providers/dns/axelname/axelname.go +++ b/providers/dns/axelname/axelname.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/axelname/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -84,6 +85,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/azion/azion.go b/providers/dns/azion/azion.go index b319e1779..a257fa0f1 100644 --- a/providers/dns/azion/azion.go +++ b/providers/dns/azion/azion.go @@ -12,6 +12,7 @@ import ( "github.com/aziontech/azionapi-go-sdk/idns" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -92,6 +93,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { clientConfig.HTTPClient = config.HTTPClient } + clientConfig.HTTPClient = clientdebug.Wrap(clientConfig.HTTPClient) + client := idns.NewAPIClient(clientConfig) return &DNSProvider{ diff --git a/providers/dns/azuredns/azuredns.go b/providers/dns/azuredns/azuredns.go index dcd4543b0..b8effadea 100644 --- a/providers/dns/azuredns/azuredns.go +++ b/providers/dns/azuredns/azuredns.go @@ -11,6 +11,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -171,6 +172,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { config.HTTPClient = &http.Client{Timeout: 5 * time.Second} } + config.HTTPClient = clientdebug.Wrap(config.HTTPClient) + credentials, err := getCredentials(config) if err != nil { return nil, fmt.Errorf("azuredns: Unable to retrieve valid credentials: %w", err) diff --git a/providers/dns/beget/beget.go b/providers/dns/beget/beget.go index a8040bc0e..e0d67572f 100644 --- a/providers/dns/beget/beget.go +++ b/providers/dns/beget/beget.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/beget/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -90,6 +91,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/beget/beget_test.go b/providers/dns/beget/beget_test.go index 7ceb7b140..e89b626b6 100644 --- a/providers/dns/beget/beget_test.go +++ b/providers/dns/beget/beget_test.go @@ -158,13 +158,13 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { config := NewDefaultConfig() config.Username = "user" config.Password = "secret" + config.HTTPClient = server.Client() p, err := NewDNSProviderConfig(config) if err != nil { return nil, err } - p.client.HTTPClient = server.Client() p.client.BaseURL, _ = url.Parse(server.URL) return p, nil diff --git a/providers/dns/binarylane/binarylane.go b/providers/dns/binarylane/binarylane.go index d8f459e2f..83016fff7 100644 --- a/providers/dns/binarylane/binarylane.go +++ b/providers/dns/binarylane/binarylane.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/binarylane/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -85,6 +86,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/bindman/bindman.go b/providers/dns/bindman/bindman.go index fbaddcbec..bd026bf74 100644 --- a/providers/dns/bindman/bindman.go +++ b/providers/dns/bindman/bindman.go @@ -10,7 +10,8 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/labbsr0x/bindman-dns-webhook/src/client" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + bindman "github.com/labbsr0x/bindman-dns-webhook/src/client" ) // Environment variables names. @@ -48,7 +49,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { config *Config - client *client.DNSWebhookClient + client *bindman.DNSWebhookClient } // NewDNSProvider returns a DNSProvider instance configured for Bindman. @@ -75,12 +76,17 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("bindman: bindman manager address missing") } - bClient, err := client.New(config.BaseURL, config.HTTPClient) + // Because the client.New uses the http.DefaultClient. + if config.HTTPClient == nil { + config.HTTPClient = &http.Client{Timeout: time.Minute} + } + + client, err := bindman.New(config.BaseURL, clientdebug.Wrap(config.HTTPClient)) if err != nil { return nil, fmt.Errorf("bindman: %w", err) } - return &DNSProvider{config: config, client: bClient}, nil + return &DNSProvider{config: config, client: client}, nil } // Present creates a TXT record using the specified parameters. diff --git a/providers/dns/bluecat/bluecat.go b/providers/dns/bluecat/bluecat.go index 8ba026f49..b26fab8be 100644 --- a/providers/dns/bluecat/bluecat.go +++ b/providers/dns/bluecat/bluecat.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/bluecat/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -110,6 +111,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/bookmyname/bookmyname.go b/providers/dns/bookmyname/bookmyname.go index 991420619..6f42dfd78 100644 --- a/providers/dns/bookmyname/bookmyname.go +++ b/providers/dns/bookmyname/bookmyname.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/bookmyname/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -87,6 +88,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/brandit/brandit.go b/providers/dns/brandit/brandit.go index 437d1642a..012e5ad15 100644 --- a/providers/dns/brandit/brandit.go +++ b/providers/dns/brandit/brandit.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/brandit/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -92,6 +93,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/bunny/bunny.go b/providers/dns/bunny/bunny.go index 1489d1c5e..67febeca6 100644 --- a/providers/dns/bunny/bunny.go +++ b/providers/dns/bunny/bunny.go @@ -5,13 +5,16 @@ import ( "context" "errors" "fmt" + "net/http" "slices" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/ptr" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" "github.com/nrdcg/bunny-go" "golang.org/x/net/publicsuffix" ) @@ -25,6 +28,7 @@ const ( EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) const minTTL = 60 @@ -33,10 +37,12 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - APIKey string + APIKey string + PropagationTimeout time.Duration PollingInterval time.Duration TTL int + HTTPClient *http.Client } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -45,6 +51,9 @@ func NewDefaultConfig() *Config { TTL: env.GetOrDefaultInt(EnvTTL, minTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, } } @@ -82,9 +91,19 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("bunny: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) } - client := bunny.NewClient(config.APIKey) + if config.HTTPClient == nil { + config.HTTPClient = &http.Client{Timeout: 30 * time.Second} + } - return &DNSProvider{config: config, client: client}, nil + config.HTTPClient = clientdebug.Wrap(config.HTTPClient) + + return &DNSProvider{ + config: config, + client: bunny.NewClient(config.APIKey, + bunny.WithUserAgent(useragent.Get()), + bunny.WithHTTPClient(config.HTTPClient), + ), + }, nil } // Timeout returns the timeout and interval to use when checking for DNS propagation. diff --git a/providers/dns/bunny/bunny.toml b/providers/dns/bunny/bunny.toml index bdbbf3177..cbe22d6db 100644 --- a/providers/dns/bunny/bunny.toml +++ b/providers/dns/bunny/bunny.toml @@ -16,6 +16,7 @@ lego --email you@example.com --dns bunny -d '*.example.com' -d example.com run BUNNY_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" BUNNY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" BUNNY_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + BUNNY_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://docs.bunny.net/reference/dnszonepublic_index" diff --git a/providers/dns/checkdomain/checkdomain.go b/providers/dns/checkdomain/checkdomain.go index e2d7a05aa..c615f5733 100644 --- a/providers/dns/checkdomain/checkdomain.go +++ b/providers/dns/checkdomain/checkdomain.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/checkdomain/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -86,7 +87,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("checkdomain: missing token") } - client := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.Token)) + client := internal.NewClient( + clientdebug.Wrap( + internal.OAuthStaticAccessToken(config.HTTPClient, config.Token), + ), + ) if config.Endpoint != nil { client.BaseURL = config.Endpoint diff --git a/providers/dns/civo/civo.go b/providers/dns/civo/civo.go index 46c474b52..a6af01e8a 100644 --- a/providers/dns/civo/civo.go +++ b/providers/dns/civo/civo.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/civo/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -91,7 +92,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } // Create a Civo client - DNS is region independent, we can use any region - client, err := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.Token), "LON1") + client, err := internal.NewClient( + clientdebug.Wrap( + internal.OAuthStaticAccessToken(config.HTTPClient, config.Token), + ), + "LON1") if err != nil { return nil, fmt.Errorf("civo: %w", err) } diff --git a/providers/dns/clouddns/clouddns.go b/providers/dns/clouddns/clouddns.go index 379dd3cf2..77b673738 100644 --- a/providers/dns/clouddns/clouddns.go +++ b/providers/dns/clouddns/clouddns.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/clouddns/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -93,6 +94,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{client: client, config: config}, nil } diff --git a/providers/dns/cloudflare/internal/client.go b/providers/dns/cloudflare/internal/client.go index 7ba4b06e0..33b7b1ba8 100644 --- a/providers/dns/cloudflare/internal/client.go +++ b/providers/dns/cloudflare/internal/client.go @@ -17,6 +17,7 @@ import ( "net/url" "time" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/errutils" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" ) @@ -61,6 +62,8 @@ func NewClient(opts ...Option) (*Client, error) { return nil, errors.New("invalid credentials: authEmail and authKey must be set together") } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return client, nil } diff --git a/providers/dns/cloudns/cloudns.go b/providers/dns/cloudns/cloudns.go index 7fbbe1062..39a4d45cd 100644 --- a/providers/dns/cloudns/cloudns.go +++ b/providers/dns/cloudns/cloudns.go @@ -15,6 +15,7 @@ import ( "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" "github.com/go-acme/lego/v4/providers/dns/cloudns/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -100,7 +101,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("ClouDNS: %w", err) } - client.HTTPClient = config.HTTPClient + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) return &DNSProvider{client: client, config: config}, nil } diff --git a/providers/dns/cloudru/cloudru.go b/providers/dns/cloudru/cloudru.go index 314c20445..287c12045 100644 --- a/providers/dns/cloudru/cloudru.go +++ b/providers/dns/cloudru/cloudru.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/cloudru/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -99,6 +100,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/conoha/conoha.go b/providers/dns/conoha/conoha.go index aa6c68ce9..f7658647c 100644 --- a/providers/dns/conoha/conoha.go +++ b/providers/dns/conoha/conoha.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/conoha/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -98,6 +99,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { identifier.HTTPClient = config.HTTPClient } + identifier.HTTPClient = clientdebug.Wrap(identifier.HTTPClient) + auth := internal.Auth{ TenantID: config.TenantID, PasswordCredentials: internal.PasswordCredentials{ @@ -120,6 +123,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/conohav3/conohav3.go b/providers/dns/conohav3/conohav3.go index a6cb12cb1..c1eace827 100644 --- a/providers/dns/conohav3/conohav3.go +++ b/providers/dns/conohav3/conohav3.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/conohav3/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -98,6 +99,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { identifier.HTTPClient = config.HTTPClient } + identifier.HTTPClient = clientdebug.Wrap(identifier.HTTPClient) + auth := internal.Auth{ Identity: internal.Identity{ Methods: []string{"password"}, @@ -129,6 +132,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/constellix/constellix.go b/providers/dns/constellix/constellix.go index f981b4974..66543903a 100644 --- a/providers/dns/constellix/constellix.go +++ b/providers/dns/constellix/constellix.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/constellix/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/hashicorp/go-retryablehttp" ) @@ -96,7 +97,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { retryClient.HTTPClient = tr.Wrap(config.HTTPClient) retryClient.Backoff = backoff - client := internal.NewClient(retryClient.StandardClient()) + client := internal.NewClient(clientdebug.Wrap(retryClient.StandardClient())) return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/corenetworks/corenetworks.go b/providers/dns/corenetworks/corenetworks.go index 119b3c16b..cde58a2bf 100644 --- a/providers/dns/corenetworks/corenetworks.go +++ b/providers/dns/corenetworks/corenetworks.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/corenetworks/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -90,6 +91,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/cpanel/cpanel.go b/providers/dns/cpanel/cpanel.go index 4c80e4db8..a61a05c81 100644 --- a/providers/dns/cpanel/cpanel.go +++ b/providers/dns/cpanel/cpanel.go @@ -17,6 +17,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/cpanel/internal/cpanel" "github.com/go-acme/lego/v4/providers/dns/cpanel/internal/shared" "github.com/go-acme/lego/v4/providers/dns/cpanel/internal/whm" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -314,6 +315,8 @@ func createClient(config *Config) (apiClient, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return client, nil case "whm": @@ -326,6 +329,8 @@ func createClient(config *Config) (apiClient, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return client, nil default: diff --git a/providers/dns/derak/derak.go b/providers/dns/derak/derak.go index 6e726620a..8a05d7608 100644 --- a/providers/dns/derak/derak.go +++ b/providers/dns/derak/derak.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/derak/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/miekg/dns" ) @@ -94,6 +95,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/desec/desec.go b/providers/dns/desec/desec.go index 9d1e20e53..08aebc2b4 100644 --- a/providers/dns/desec/desec.go +++ b/providers/dns/desec/desec.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/desec" ) @@ -87,7 +88,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { opts := desec.NewDefaultClientOptions() if config.HTTPClient != nil { opts.HTTPClient = config.HTTPClient + } else { + // Because the desec.NewDefaultClientOptions uses the http.DefaultClient. + // TODO(ldez): change the desec lib. + opts.HTTPClient = &http.Client{Timeout: 30 * time.Second} } + + opts.HTTPClient = clientdebug.Wrap(opts.HTTPClient) + opts.Logger = log.Default() client := desec.New(config.Token, opts) diff --git a/providers/dns/digitalocean/digitalocean.go b/providers/dns/digitalocean/digitalocean.go index 0b68aa5c9..f7ae68d60 100644 --- a/providers/dns/digitalocean/digitalocean.go +++ b/providers/dns/digitalocean/digitalocean.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/digitalocean/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -88,7 +89,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("digitalocean: credentials missing") } - client := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.AuthToken)) + client := internal.NewClient( + clientdebug.Wrap( + internal.OAuthStaticAccessToken(config.HTTPClient, config.AuthToken), + ), + ) if config.BaseURL != "" { var err error diff --git a/providers/dns/directadmin/directadmin.go b/providers/dns/directadmin/directadmin.go index de9b14945..8dfa132ae 100644 --- a/providers/dns/directadmin/directadmin.go +++ b/providers/dns/directadmin/directadmin.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/directadmin/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -99,6 +100,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{client: client, config: config}, nil } diff --git a/providers/dns/dnshomede/dnshomede.go b/providers/dns/dnshomede/dnshomede.go index 91b0b11e3..e3d56f098 100644 --- a/providers/dns/dnshomede/dnshomede.go +++ b/providers/dns/dnshomede/dnshomede.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/dnshomede/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -92,6 +93,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client := internal.NewClient(config.Credentials) + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/dnsimple/dnsimple.go b/providers/dns/dnsimple/dnsimple.go index 5d1a7ba80..4b7df0943 100644 --- a/providers/dns/dnsimple/dnsimple.go +++ b/providers/dns/dnsimple/dnsimple.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" "golang.org/x/oauth2" ) @@ -79,8 +80,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("dnsimple: OAuth token is missing") } - ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.AccessToken}) - client := dnsimple.NewClient(oauth2.NewClient(context.Background(), ts)) + client := dnsimple.NewClient( + clientdebug.Wrap( + oauth2.NewClient( + context.Background(), + oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.AccessToken}), + ), + ), + ) client.SetUserAgent(useragent.Get()) if config.BaseURL != "" { diff --git a/providers/dns/dnsmadeeasy/dnsmadeeasy.go b/providers/dns/dnsmadeeasy/dnsmadeeasy.go index fcfe6714c..7d2f92726 100644 --- a/providers/dns/dnsmadeeasy/dnsmadeeasy.go +++ b/providers/dns/dnsmadeeasy/dnsmadeeasy.go @@ -15,6 +15,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/dnsmadeeasy/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -112,7 +113,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("dnsmadeeasy: %w", err) } - client.HTTPClient = config.HTTPClient + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + client.BaseURL, err = url.Parse(baseURL) if err != nil { return nil, err diff --git a/providers/dns/dnspod/dnspod.go b/providers/dns/dnspod/dnspod.go index ab8f20c8d..46893fe5a 100644 --- a/providers/dns/dnspod/dnspod.go +++ b/providers/dns/dnspod/dnspod.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/dnspod-go" ) @@ -82,7 +83,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { params := dnspod.CommonParams{LoginToken: config.LoginToken, Format: "json"} client := dnspod.NewClient(params) - client.HTTPClient = config.HTTPClient + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) return &DNSProvider{client: client, config: config}, nil } diff --git a/providers/dns/dode/dode.go b/providers/dns/dode/dode.go index 9f307f046..59ad785e8 100644 --- a/providers/dns/dode/dode.go +++ b/providers/dns/dode/dode.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/dode/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -85,6 +86,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/domeneshop/domeneshop.go b/providers/dns/domeneshop/domeneshop.go index c194f5608..fb16b442e 100644 --- a/providers/dns/domeneshop/domeneshop.go +++ b/providers/dns/domeneshop/domeneshop.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/domeneshop/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -86,6 +87,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/dreamhost/dreamhost.go b/providers/dns/dreamhost/dreamhost.go index 5b4960ee0..5ad2611d9 100644 --- a/providers/dns/dreamhost/dreamhost.go +++ b/providers/dns/dreamhost/dreamhost.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/dreamhost/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -86,6 +87,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + if config.BaseURL != "" { client.BaseURL = config.BaseURL } diff --git a/providers/dns/duckdns/duckdns.go b/providers/dns/duckdns/duckdns.go index 687f5bbac..1aae0a06c 100644 --- a/providers/dns/duckdns/duckdns.go +++ b/providers/dns/duckdns/duckdns.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/duckdns/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -86,6 +87,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/dyn/dyn.go b/providers/dns/dyn/dyn.go index 627626df6..0cd445c39 100644 --- a/providers/dns/dyn/dyn.go +++ b/providers/dns/dyn/dyn.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/dyn/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -92,6 +93,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/dyndnsfree/dyndnsfree.go b/providers/dns/dyndnsfree/dyndnsfree.go index 8c1d87aaa..13a192793 100644 --- a/providers/dns/dyndnsfree/dyndnsfree.go +++ b/providers/dns/dyndnsfree/dyndnsfree.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/dyndnsfree/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -81,6 +82,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/dynu/dynu.go b/providers/dns/dynu/dynu.go index af602ddfc..11df45281 100644 --- a/providers/dns/dynu/dynu.go +++ b/providers/dns/dynu/dynu.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/dynu/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -86,7 +87,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } client := internal.NewClient() - client.HTTPClient = tr.Wrap(config.HTTPClient) + + client.HTTPClient = clientdebug.Wrap(tr.Wrap(config.HTTPClient)) return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/easydns/easydns.go b/providers/dns/easydns/easydns.go index 7e5e219cb..c1119f3cc 100644 --- a/providers/dns/easydns/easydns.go +++ b/providers/dns/easydns/easydns.go @@ -16,6 +16,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/easydns/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -110,6 +111,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + if config.Endpoint != nil { client.BaseURL = config.Endpoint } diff --git a/providers/dns/efficientip/efficientip.go b/providers/dns/efficientip/efficientip.go index 15fa579ed..d99710920 100644 --- a/providers/dns/efficientip/efficientip.go +++ b/providers/dns/efficientip/efficientip.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/efficientip/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -113,6 +114,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/epik/epik.go b/providers/dns/epik/epik.go index 58390faa9..ef5de3c4b 100644 --- a/providers/dns/epik/epik.go +++ b/providers/dns/epik/epik.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/epik/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -86,6 +87,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/exoscale/exoscale.go b/providers/dns/exoscale/exoscale.go index fa76949d9..83baa9ade 100644 --- a/providers/dns/exoscale/exoscale.go +++ b/providers/dns/exoscale/exoscale.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" ) @@ -89,7 +90,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client, err := egoscale.NewClient( credentials.NewStaticCredentials(config.APIKey, config.APISecret), egoscale.ClientOptWithEndpoint(egoscale.Endpoint(config.Endpoint)), - egoscale.ClientOptWithHTTPClient(&http.Client{Timeout: config.HTTPTimeout}), + egoscale.ClientOptWithHTTPClient(clientdebug.Wrap(&http.Client{Timeout: config.HTTPTimeout})), egoscale.ClientOptWithUserAgent(useragent.Get()), ) if err != nil { diff --git a/providers/dns/f5xc/f5xc.go b/providers/dns/f5xc/f5xc.go index 9d74d1538..6f8a8c493 100644 --- a/providers/dns/f5xc/f5xc.go +++ b/providers/dns/f5xc/f5xc.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" "github.com/go-acme/lego/v4/providers/dns/f5xc/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -93,6 +94,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/freemyip/freemyip.go b/providers/dns/freemyip/freemyip.go index 7613f2b8d..fb6202e25 100644 --- a/providers/dns/freemyip/freemyip.go +++ b/providers/dns/freemyip/freemyip.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/freemyip" ) @@ -88,6 +89,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/gandi/gandi.go b/providers/dns/gandi/gandi.go index dd6622172..bb96a7d0f 100644 --- a/providers/dns/gandi/gandi.go +++ b/providers/dns/gandi/gandi.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/gandi/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -109,6 +110,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/gandiv5/gandiv5.go b/providers/dns/gandiv5/gandiv5.go index 3c35245de..cd236631c 100644 --- a/providers/dns/gandiv5/gandiv5.go +++ b/providers/dns/gandiv5/gandiv5.go @@ -15,6 +15,7 @@ import ( "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/gandiv5/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -120,6 +121,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/gcloud/googlecloud.go b/providers/dns/gcloud/googlecloud.go index 7c17abd4a..abf40ebd4 100644 --- a/providers/dns/gcloud/googlecloud.go +++ b/providers/dns/gcloud/googlecloud.go @@ -17,6 +17,7 @@ import ( "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/miekg/dns" "golang.org/x/net/context" "golang.org/x/oauth2" @@ -172,7 +173,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("googlecloud: unable to create Google Cloud DNS service: client is nil") } - svc, err := gdns.NewService(context.Background(), option.WithHTTPClient(config.HTTPClient)) + svc, err := gdns.NewService(context.Background(), option.WithHTTPClient(clientdebug.Wrap(config.HTTPClient))) if err != nil { return nil, fmt.Errorf("googlecloud: unable to create Google Cloud DNS service: %w", err) } diff --git a/providers/dns/gcore/gcore.go b/providers/dns/gcore/gcore.go index 646c5ab1c..19a548810 100644 --- a/providers/dns/gcore/gcore.go +++ b/providers/dns/gcore/gcore.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/gcore/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -89,6 +90,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/glesys/glesys.go b/providers/dns/glesys/glesys.go index 4b0d545ed..4fa689e28 100644 --- a/providers/dns/glesys/glesys.go +++ b/providers/dns/glesys/glesys.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/glesys/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -99,6 +100,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/godaddy/godaddy.go b/providers/dns/godaddy/godaddy.go index 38e470509..7c323ce0b 100644 --- a/providers/dns/godaddy/godaddy.go +++ b/providers/dns/godaddy/godaddy.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/godaddy/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -95,6 +96,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go b/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go index 63724712c..4fb95eb6f 100644 --- a/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go +++ b/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/hetznerv1/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "golang.org/x/net/idna" ) @@ -80,7 +81,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("hetzner: credentials missing") } - client, err := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.APIToken)) + client, err := internal.NewClient( + clientdebug.Wrap( + internal.OAuthStaticAccessToken(config.HTTPClient, config.APIToken), + ), + ) if err != nil { return nil, fmt.Errorf("hetzner: %w", err) } diff --git a/providers/dns/hetzner/internal/legacy/hetzner.go b/providers/dns/hetzner/internal/legacy/hetzner.go index 5fb978415..393a3d671 100644 --- a/providers/dns/hetzner/internal/legacy/hetzner.go +++ b/providers/dns/hetzner/internal/legacy/hetzner.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/hetzner/internal/legacy/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -91,6 +92,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/hostingde/hostingde.go b/providers/dns/hostingde/hostingde.go index 87fc73d34..3ad6e4a61 100644 --- a/providers/dns/hostingde/hostingde.go +++ b/providers/dns/hostingde/hostingde.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/hostingde" ) @@ -87,9 +88,17 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("hostingde: API key missing") } + client := hostingde.NewClient(config.APIKey) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, - client: hostingde.NewClient(config.APIKey), + client: client, recordIDs: make(map[string]string), }, nil } diff --git a/providers/dns/hostinger/hostinger.go b/providers/dns/hostinger/hostinger.go index 469e227f2..13d9ed0f8 100644 --- a/providers/dns/hostinger/hostinger.go +++ b/providers/dns/hostinger/hostinger.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/hostinger/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -82,6 +83,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/hostinger/hostinger_test.go b/providers/dns/hostinger/hostinger_test.go index 967674618..1315cee97 100644 --- a/providers/dns/hostinger/hostinger_test.go +++ b/providers/dns/hostinger/hostinger_test.go @@ -96,13 +96,13 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() config.APIToken = "secret" + config.HTTPClient = server.Client() p, err := NewDNSProviderConfig(config) if err != nil { return nil, err } - p.client.HTTPClient = server.Client() p.client.BaseURL, _ = url.Parse(server.URL) return p, nil diff --git a/providers/dns/hosttech/hosttech.go b/providers/dns/hosttech/hosttech.go index 22d3be7bd..20fa1d710 100644 --- a/providers/dns/hosttech/hosttech.go +++ b/providers/dns/hosttech/hosttech.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/hosttech/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -84,7 +85,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("hosttech: missing credentials") } - client := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.APIKey)) + client := internal.NewClient( + clientdebug.Wrap( + internal.OAuthStaticAccessToken(config.HTTPClient, config.APIKey), + ), + ) return &DNSProvider{ config: config, diff --git a/providers/dns/httpnet/httpnet.go b/providers/dns/httpnet/httpnet.go index 56bd92712..e69c43e6d 100644 --- a/providers/dns/httpnet/httpnet.go +++ b/providers/dns/httpnet/httpnet.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/hostingde" ) @@ -91,6 +92,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client := hostingde.NewClient(config.APIKey) client.BaseURL, _ = url.Parse(hostingde.DefaultHTTPNetBaseURL) + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/httpreq/httpreq.go b/providers/dns/httpreq/httpreq.go index 8f8311e0a..12eef7b8e 100644 --- a/providers/dns/httpreq/httpreq.go +++ b/providers/dns/httpreq/httpreq.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) @@ -88,6 +89,7 @@ func NewDNSProvider() (*DNSProvider, error) { config.Username = env.GetOrFile(EnvUsername) config.Password = env.GetOrFile(EnvPassword) config.Endpoint = endpoint + return NewDNSProviderConfig(config) } @@ -101,6 +103,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("httpreq: the endpoint is missing") } + config.HTTPClient = clientdebug.Wrap(config.HTTPClient) + return &DNSProvider{config: config}, nil } diff --git a/providers/dns/hurricane/hurricane.go b/providers/dns/hurricane/hurricane.go index 7ce646bc9..c8b05731a 100644 --- a/providers/dns/hurricane/hurricane.go +++ b/providers/dns/hurricane/hurricane.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/hurricane/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -83,6 +84,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client := internal.NewClient(config.Credentials) + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/hyperone/hyperone.go b/providers/dns/hyperone/hyperone.go index 890f9f627..33716cfdb 100644 --- a/providers/dns/hyperone/hyperone.go +++ b/providers/dns/hyperone/hyperone.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/hyperone/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Environment variables names. @@ -96,6 +97,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{client: client, config: config}, nil } diff --git a/providers/dns/infomaniak/infomaniak.go b/providers/dns/infomaniak/infomaniak.go index 79c6f577e..9b8b53590 100644 --- a/providers/dns/infomaniak/infomaniak.go +++ b/providers/dns/infomaniak/infomaniak.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/infomaniak/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) // Infomaniak API reference: https://api.infomaniak.com/doc @@ -96,7 +97,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("infomaniak: missing access token") } - client, err := internal.New(internal.OAuthStaticAccessToken(config.HTTPClient, config.AccessToken), config.APIEndpoint) + client, err := internal.New( + clientdebug.Wrap( + internal.OAuthStaticAccessToken(config.HTTPClient, config.AccessToken), + ), + config.APIEndpoint) if err != nil { return nil, fmt.Errorf("infomaniak: %w", err) } diff --git a/providers/dns/internal/clientdebug/.gitattributes b/providers/dns/internal/clientdebug/.gitattributes new file mode 100644 index 000000000..0ce5804f7 --- /dev/null +++ b/providers/dns/internal/clientdebug/.gitattributes @@ -0,0 +1 @@ +/testdata/** text eol=lf diff --git a/providers/dns/internal/clientdebug/client.go b/providers/dns/internal/clientdebug/client.go new file mode 100644 index 000000000..ad2a06405 --- /dev/null +++ b/providers/dns/internal/clientdebug/client.go @@ -0,0 +1,131 @@ +package clientdebug + +import ( + "fmt" + "io" + "net/http" + "net/http/httputil" + "os" + "regexp" + "strconv" + "strings" + + "github.com/go-acme/lego/v4/platform/config/env" +) + +const replacement = "***" + +type Option func(*DumpTransport) + +func WithEnvKeys(keys ...string) Option { + return func(d *DumpTransport) { + for _, key := range keys { + v := strings.TrimSpace(env.GetOrFile(key)) + if v == "" { + continue + } + + d.replacements = append(d.replacements, v, replacement) + } + } +} + +func WithValues(values ...string) Option { + return func(d *DumpTransport) { + for _, value := range values { + d.replacements = append(d.replacements, value, replacement) + } + } +} + +func WithHeaders(keys ...string) Option { + return func(d *DumpTransport) { + d.regexps = append(d.regexps, + regexp.MustCompile(fmt.Sprintf(`(?im)^(%s):.+$`, strings.Join(keys, "|")))) + } +} + +type DumpTransport struct { + rt http.RoundTripper + + replacements []string + replacer *strings.Replacer + + regexps []*regexp.Regexp + + writer io.Writer +} + +func NewDumpTransport(rt http.RoundTripper, opts ...Option) *DumpTransport { + if rt == nil { + rt = http.DefaultTransport + } + + d := &DumpTransport{ + rt: rt, + writer: os.Stdout, + } + + for _, opt := range opts { + opt(d) + } + + d.regexps = append(d.regexps, + regexp.MustCompile(`(?im)^(Authorization):.+$`), + regexp.MustCompile(`(?im)^(Token|X-Token):.+$`), + regexp.MustCompile(`(?im)^(Auth-Token|X-Auth-Token):.+$`), + regexp.MustCompile(`(?im)^(Api-Key|X-Api-Key|X-Api-Secret):.+$`), + ) + + if len(d.replacements) > 0 { + d.replacer = strings.NewReplacer(d.replacements...) + } + + return d +} + +func (d *DumpTransport) RoundTrip(h *http.Request) (*http.Response, error) { + data, _ := httputil.DumpRequestOut(h, true) + + _, _ = fmt.Fprintln(d.writer, "[HTTP Request]") + _, _ = fmt.Fprintln(d.writer, d.redact(data)) + + resp, err := d.rt.RoundTrip(h) + + data, _ = httputil.DumpResponse(resp, true) + + _, _ = fmt.Fprintln(d.writer, "[HTTP Response]") + _, _ = fmt.Fprintln(d.writer, d.redact(data)) + + return resp, err +} + +func (d *DumpTransport) redact(content []byte) string { + data := string(content) + + for _, r := range d.regexps { + data = r.ReplaceAllString(data, "$1: "+replacement) + } + + if d.replacer == nil { + return data + } + + return d.replacer.Replace(data) +} + +// Wrap wraps an HTTP client Transport with the [DumpTransport]. +func Wrap(client *http.Client, opts ...Option) *http.Client { + val, found := os.LookupEnv("LEGO_DEBUG_DNS_API_HTTP_CLIENT") + if !found { + return client + } + + if ok, _ := strconv.ParseBool(val); !ok { + return client + } + + client.Transport = NewDumpTransport(client.Transport, opts...) + + return client +} diff --git a/providers/dns/internal/clientdebug/client_test.go b/providers/dns/internal/clientdebug/client_test.go new file mode 100644 index 000000000..e467cf8f9 --- /dev/null +++ b/providers/dns/internal/clientdebug/client_test.go @@ -0,0 +1,168 @@ +package clientdebug + +import ( + "bytes" + "io" + "net/http" + "net/http/httptest" + "net/url" + "path/filepath" + "strings" + "testing" + "text/template" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWrap_redact_env_vars(t *testing.T) { + t.Setenv("LEGO_DEBUG_DNS_API_HTTP_CLIENT", "true") + + t.Setenv("MY_VAR_01", "env-aaaa-aaaa") + t.Setenv("MY_VAR_02", "query-aaaa-aaaa") + t.Setenv("MY_VAR_03", "path-aaaa-aaaa") + t.Setenv("MY_VAR_04", "request-body-aaaa-aaaa") + t.Setenv("MY_VAR_05", "request-header-aaaa-aaaa") + t.Setenv("MY_VAR_06", "response-body-aaaa-aaaa") + + buf := bytes.NewBufferString("") + + server, client, req := setupTest(t, buf, + WithEnvKeys("MY_VAR_01", "MY_VAR_02", "MY_VAR_03", "MY_VAR_04", "MY_VAR_05", "MY_VAR_06"), + ) + + resp, err := client.Transport.RoundTrip(req) + require.NoError(t, err) + + assert.Equal(t, http.StatusOK, resp.StatusCode) + + assertDump(t, server, buf, "env_vars.txt") +} + +func TestWrap_redact_headers(t *testing.T) { + t.Setenv("LEGO_DEBUG_DNS_API_HTTP_CLIENT", "true") + + buf := bytes.NewBufferString("") + + server, client, req := setupTest(t, buf, + WithHeaders("Secret-Request-Header", "Super-Secret-Request-Header", "Secret-Response-Header"), + ) + + resp, err := client.Transport.RoundTrip(req) + require.NoError(t, err) + + assert.Equal(t, http.StatusOK, resp.StatusCode) + + assertDump(t, server, buf, "headers.txt") +} + +func TestWrap_redact_values(t *testing.T) { + t.Setenv("LEGO_DEBUG_DNS_API_HTTP_CLIENT", "true") + + buf := bytes.NewBufferString("") + + server, client, req := setupTest(t, buf, + WithValues("query-aaaa-aaaa", "path-aaaa-aaaa", "request-body-aaaa-aaaa"), + ) + + resp, err := client.Transport.RoundTrip(req) + require.NoError(t, err) + + assert.Equal(t, http.StatusOK, resp.StatusCode) + + assertDump(t, server, buf, "values.txt") +} + +func fakeRequest(t *testing.T, baseURL string) *http.Request { + t.Helper() + + endpoint, err := url.Parse(baseURL) + require.NoError(t, err) + + query := endpoint.Query() + query.Set("foo", "query-aaaa-aaaa") + endpoint.RawQuery = query.Encode() + + endpoint = endpoint.JoinPath("path-aaaa-aaaa") + + body := `{ + "foo": "request-body-aaaa-aaaa" +} +` + + req := httptest.NewRequest(http.MethodGet, endpoint.String(), bytes.NewBufferString(body)) + + req.Header.Set("X-Authorization", "not-redacted") + + req.Header.Set("Secret-Request-Header", "request-header-aaaa-aaaa") + req.Header.Set("Super-Secret-Request-Header", "env-aaaa-aaaa") + + req.Header.Set("Authorization", "header-aaaa-0000") + req.Header.Set("Token", "header-aaaa-0001") + req.Header.Set("X-Token", "header-aaaa-0002") + req.Header.Set("Auth-Token", "header-aaaa-0003") + req.Header.Set("X-Auth-Token", "header-aaaa-0004") + req.Header.Set("Api-Key", "header-aaaa-0006") + req.Header.Set("X-Api-Key", "header-aaaa-0007") + req.Header.Set("X-Api-Secret", "header-aaaa-0008") + + req.SetBasicAuth("user", "secret") + + return req +} + +func fakeResponse() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Secret-Response-Header", "response-header-aaaa-aaaa") + _, _ = w.Write([]byte(`{ + "bar": "response-body-aaaa-aaaa" +}`, + )) + } +} + +func withWriter(w io.Writer) Option { + return func(d *DumpTransport) { + if w != nil { + d.writer = w + } + } +} + +func setupTest(t *testing.T, buf io.Writer, opts ...Option) (*httptest.Server, *http.Client, *http.Request) { + t.Helper() + + server := httptest.NewServer(fakeResponse()) + + opts = append(opts, withWriter(buf)) + + client := Wrap(server.Client(), opts...) + + req := fakeRequest(t, server.URL) + + return server, client, req +} + +func assertDump(t *testing.T, server *httptest.Server, actual *bytes.Buffer, filename string) { + t.Helper() + + tmpl, err := template.New(filename).ParseFiles(filepath.Join("testdata", filename)) + require.NoError(t, err) + + expected := bytes.NewBufferString("") + + location, err := time.LoadLocation("GMT") + require.NoError(t, err) + + baseURL, err := url.Parse(server.URL) + require.NoError(t, err) + + err = tmpl.Execute(expected, map[string]string{ + "Host": baseURL.Host, + "Date": time.Now().In(location).Format(time.RFC1123), + }) + require.NoError(t, err) + + assert.Equal(t, expected.String(), strings.ReplaceAll(actual.String(), "\r", "")) +} diff --git a/providers/dns/internal/clientdebug/testdata/env_vars.txt b/providers/dns/internal/clientdebug/testdata/env_vars.txt new file mode 100644 index 000000000..a2697850e --- /dev/null +++ b/providers/dns/internal/clientdebug/testdata/env_vars.txt @@ -0,0 +1,32 @@ +[HTTP Request] +GET /***?foo=*** HTTP/1.1 +Host: {{ .Host }} +User-Agent: Go-http-client/1.1 +Content-Length: 37 +Api-Key: *** +Auth-Token: *** +Authorization: *** +Secret-Request-Header: *** +Super-Secret-Request-Header: *** +Token: *** +X-Api-Key: *** +X-Api-Secret: *** +X-Auth-Token: *** +X-Authorization: not-redacted +X-Token: *** +Accept-Encoding: gzip + +{ + "foo": "***" +} + +[HTTP Response] +HTTP/1.1 200 OK +Content-Length: 37 +Content-Type: text/plain; charset=utf-8 +Date: {{ .Date }} +Secret-Response-Header: response-header-aaaa-aaaa + +{ + "bar": "***" +} diff --git a/providers/dns/internal/clientdebug/testdata/headers.txt b/providers/dns/internal/clientdebug/testdata/headers.txt new file mode 100644 index 000000000..fe803fb22 --- /dev/null +++ b/providers/dns/internal/clientdebug/testdata/headers.txt @@ -0,0 +1,32 @@ +[HTTP Request] +GET /path-aaaa-aaaa?foo=query-aaaa-aaaa HTTP/1.1 +Host: {{ .Host }} +User-Agent: Go-http-client/1.1 +Content-Length: 37 +Api-Key: *** +Auth-Token: *** +Authorization: *** +Secret-Request-Header: *** +Super-Secret-Request-Header: *** +Token: *** +X-Api-Key: *** +X-Api-Secret: *** +X-Auth-Token: *** +X-Authorization: not-redacted +X-Token: *** +Accept-Encoding: gzip + +{ + "foo": "request-body-aaaa-aaaa" +} + +[HTTP Response] +HTTP/1.1 200 OK +Content-Length: 37 +Content-Type: text/plain; charset=utf-8 +Date: {{ .Date }} +Secret-Response-Header: *** + +{ + "bar": "response-body-aaaa-aaaa" +} diff --git a/providers/dns/internal/clientdebug/testdata/values.txt b/providers/dns/internal/clientdebug/testdata/values.txt new file mode 100644 index 000000000..b40f51f14 --- /dev/null +++ b/providers/dns/internal/clientdebug/testdata/values.txt @@ -0,0 +1,32 @@ +[HTTP Request] +GET /***?foo=*** HTTP/1.1 +Host: {{ .Host }} +User-Agent: Go-http-client/1.1 +Content-Length: 37 +Api-Key: *** +Auth-Token: *** +Authorization: *** +Secret-Request-Header: request-header-aaaa-aaaa +Super-Secret-Request-Header: env-aaaa-aaaa +Token: *** +X-Api-Key: *** +X-Api-Secret: *** +X-Auth-Token: *** +X-Authorization: not-redacted +X-Token: *** +Accept-Encoding: gzip + +{ + "foo": "***" +} + +[HTTP Response] +HTTP/1.1 200 OK +Content-Length: 37 +Content-Type: text/plain; charset=utf-8 +Date: {{ .Date }} +Secret-Response-Header: response-header-aaaa-aaaa + +{ + "bar": "response-body-aaaa-aaaa" +} diff --git a/providers/dns/internetbs/internetbs.go b/providers/dns/internetbs/internetbs.go index 9d6c17676..e8cb868d2 100644 --- a/providers/dns/internetbs/internetbs.go +++ b/providers/dns/internetbs/internetbs.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internetbs/internal" ) @@ -88,6 +89,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/ionos/ionos.go b/providers/dns/ionos/ionos.go index 394def027..1c2bf118d 100644 --- a/providers/dns/ionos/ionos.go +++ b/providers/dns/ionos/ionos.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/ionos/internal" ) @@ -96,6 +97,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/ipv64/ipv64.go b/providers/dns/ipv64/ipv64.go index 6e8d1c5bb..078fe5ca1 100644 --- a/providers/dns/ipv64/ipv64.go +++ b/providers/dns/ipv64/ipv64.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/ipv64/internal" "github.com/miekg/dns" ) @@ -85,6 +86,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/iwantmyname/iwantmyname.go b/providers/dns/iwantmyname/iwantmyname.go index 2b53377ed..dd3e9fc1a 100644 --- a/providers/dns/iwantmyname/iwantmyname.go +++ b/providers/dns/iwantmyname/iwantmyname.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/iwantmyname/internal" ) @@ -88,6 +89,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/joker/provider_dmapi.go b/providers/dns/joker/provider_dmapi.go index 5c623467a..7b32ce804 100644 --- a/providers/dns/joker/provider_dmapi.go +++ b/providers/dns/joker/provider_dmapi.go @@ -10,6 +10,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/joker/internal/dmapi" ) @@ -66,6 +67,8 @@ func newDmapiProviderConfig(config *Config) (*dmapiProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &dmapiProvider{config: config, client: client}, nil } diff --git a/providers/dns/joker/provider_svc.go b/providers/dns/joker/provider_svc.go index 991772fe7..f4d8fcf3f 100644 --- a/providers/dns/joker/provider_svc.go +++ b/providers/dns/joker/provider_svc.go @@ -9,6 +9,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/joker/internal/svc" ) @@ -47,6 +48,8 @@ func newSvcProviderConfig(config *Config) (*svcProvider, error) { client := svc.NewClient(config.Username, config.Password) + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &svcProvider{config: config, client: client}, nil } diff --git a/providers/dns/keyhelp/keyhelp.go b/providers/dns/keyhelp/keyhelp.go index dfe3af556..cbf641cfe 100644 --- a/providers/dns/keyhelp/keyhelp.go +++ b/providers/dns/keyhelp/keyhelp.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/keyhelp/internal" ) @@ -88,6 +89,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/liara/liara.go b/providers/dns/liara/liara.go index a0437b0eb..7894afc98 100644 --- a/providers/dns/liara/liara.go +++ b/providers/dns/liara/liara.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/liara/internal" "github.com/hashicorp/go-retryablehttp" ) @@ -105,7 +106,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } retryClient.Logger = log.Logger - client := internal.NewClient(internal.OAuthStaticAccessToken(retryClient.StandardClient(), config.APIKey)) + client := internal.NewClient( + clientdebug.Wrap( + internal.OAuthStaticAccessToken(retryClient.StandardClient(), config.APIKey), + ), + ) return &DNSProvider{ config: config, diff --git a/providers/dns/limacity/limacity.go b/providers/dns/limacity/limacity.go index b403ecedf..502208f2a 100644 --- a/providers/dns/limacity/limacity.go +++ b/providers/dns/limacity/limacity.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/limacity/internal" ) @@ -89,6 +90,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client := internal.NewClient(config.APIKey) + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/linode/linode.go b/providers/dns/linode/linode.go index 6e5951d71..449c84a1a 100644 --- a/providers/dns/linode/linode.go +++ b/providers/dns/linode/linode.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" "github.com/linode/linodego" "golang.org/x/oauth2" @@ -102,7 +103,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { }, } - client := linodego.NewClient(oauth2Client) + client := linodego.NewClient(clientdebug.Wrap(oauth2Client)) client.SetUserAgent(useragent.Get()) return &DNSProvider{config: config, client: &client}, nil diff --git a/providers/dns/loopia/loopia.go b/providers/dns/loopia/loopia.go index 8389ae5f6..be3416ddf 100644 --- a/providers/dns/loopia/loopia.go +++ b/providers/dns/loopia/loopia.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/loopia/internal" ) @@ -113,6 +114,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + if config.BaseURL != "" { client.BaseURL = config.BaseURL } diff --git a/providers/dns/luadns/luadns.go b/providers/dns/luadns/luadns.go index 026a0da70..02108ce62 100644 --- a/providers/dns/luadns/luadns.go +++ b/providers/dns/luadns/luadns.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/luadns/internal" ) @@ -100,6 +101,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/mailinabox/mailinabox.go b/providers/dns/mailinabox/mailinabox.go index 3ea8a9f29..cf6202a92 100644 --- a/providers/dns/mailinabox/mailinabox.go +++ b/providers/dns/mailinabox/mailinabox.go @@ -5,11 +5,13 @@ import ( "context" "errors" "fmt" + "net/http" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/mailinabox" ) @@ -23,6 +25,7 @@ const ( EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) var _ challenge.ProviderTimeout = (*DNSProvider)(nil) @@ -34,6 +37,7 @@ type Config struct { BaseURL string PropagationTimeout time.Duration PollingInterval time.Duration + HTTPClient *http.Client } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -41,6 +45,9 @@ func NewDefaultConfig() *Config { return &Config{ PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 4*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, } } @@ -81,7 +88,13 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("mailinabox: missing base URL") } - client, err := mailinabox.New(config.BaseURL, config.Email, config.Password) + if config.HTTPClient == nil { + config.HTTPClient = &http.Client{Timeout: 30 * time.Second} + } + + config.HTTPClient = clientdebug.Wrap(config.HTTPClient) + + client, err := mailinabox.New(config.BaseURL, config.Email, config.Password, mailinabox.WithHTTPClient(config.HTTPClient)) if err != nil { return nil, fmt.Errorf("mailinabox: %w", err) } diff --git a/providers/dns/mailinabox/mailinabox.toml b/providers/dns/mailinabox/mailinabox.toml index 4b30dd9e2..e0072ebdd 100644 --- a/providers/dns/mailinabox/mailinabox.toml +++ b/providers/dns/mailinabox/mailinabox.toml @@ -19,6 +19,7 @@ lego --email you@example.com --dns mailinabox -d '*.example.com' -d example.com [Configuration.Additional] MAILINABOX_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 4)" MAILINABOX_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + MAILINABOX_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://mailinabox.email/api-docs.html" diff --git a/providers/dns/manageengine/internal/client.go b/providers/dns/manageengine/internal/client.go index b360840f0..debb62812 100644 --- a/providers/dns/manageengine/internal/client.go +++ b/providers/dns/manageengine/internal/client.go @@ -24,12 +24,12 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(ctx context.Context, clientID, clientSecret string) *Client { +func NewClient(hc *http.Client) *Client { baseURL, _ := url.Parse(defaultBaseURL) return &Client{ baseURL: baseURL, - httpClient: createOAuthClient(ctx, clientID, clientSecret), + httpClient: hc, } } diff --git a/providers/dns/manageengine/internal/client_test.go b/providers/dns/manageengine/internal/client_test.go index 0c18a245f..25d1730f6 100644 --- a/providers/dns/manageengine/internal/client_test.go +++ b/providers/dns/manageengine/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "net/http" "net/http/httptest" "net/url" @@ -15,9 +14,8 @@ import ( func mockBuilder() *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { - client := NewClient(context.Background(), "abc", "secret") + client := NewClient(server.Client()) - client.httpClient = server.Client() client.baseURL, _ = url.Parse(server.URL) return client, nil diff --git a/providers/dns/manageengine/internal/identity.go b/providers/dns/manageengine/internal/identity.go index 66a659188..ec28121e4 100644 --- a/providers/dns/manageengine/internal/identity.go +++ b/providers/dns/manageengine/internal/identity.go @@ -9,7 +9,7 @@ import ( const defaultAuthURL = "https://clouddns.manageengine.com/oauth2/token/" -func createOAuthClient(ctx context.Context, clientID, clientSecret string) *http.Client { +func CreateOAuthClient(ctx context.Context, clientID, clientSecret string) *http.Client { config := &clientcredentials.Config{ TokenURL: defaultAuthURL, ClientID: clientID, diff --git a/providers/dns/manageengine/manageengine.go b/providers/dns/manageengine/manageengine.go index f26ae33b5..3863a6597 100644 --- a/providers/dns/manageengine/manageengine.go +++ b/providers/dns/manageengine/manageengine.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/manageengine/internal" ) @@ -75,11 +76,13 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("manageengine: credentials missing") } - client := internal.NewClient(context.Background(), config.ClientID, config.ClientSecret) - return &DNSProvider{ config: config, - client: client, + client: internal.NewClient( + clientdebug.Wrap( + internal.CreateOAuthClient(context.Background(), config.ClientID, config.ClientSecret), + ), + ), }, nil } diff --git a/providers/dns/metaregistrar/metaregistrar.go b/providers/dns/metaregistrar/metaregistrar.go index 28526fcb4..7a601ef21 100644 --- a/providers/dns/metaregistrar/metaregistrar.go +++ b/providers/dns/metaregistrar/metaregistrar.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/metaregistrar/internal" ) @@ -82,6 +83,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/mijnhost/mijnhost.go b/providers/dns/mijnhost/mijnhost.go index 515caa2f6..adb3e9ce3 100644 --- a/providers/dns/mijnhost/mijnhost.go +++ b/providers/dns/mijnhost/mijnhost.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/mijnhost/internal" ) @@ -86,6 +87,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client := internal.NewClient(config.APIKey) + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/mittwald/mittwald.go b/providers/dns/mittwald/mittwald.go index 2c3c5a8f3..f60745659 100644 --- a/providers/dns/mittwald/mittwald.go +++ b/providers/dns/mittwald/mittwald.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/mittwald/internal" ) @@ -92,9 +93,17 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("mittwald: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) } + client := internal.NewClient(config.Token) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, - client: internal.NewClient(config.Token), + client: client, zoneIDs: map[string]string{}, }, nil } diff --git a/providers/dns/myaddr/myaddr.go b/providers/dns/myaddr/myaddr.go index df280f2f4..fb7ea66a0 100644 --- a/providers/dns/myaddr/myaddr.go +++ b/providers/dns/myaddr/myaddr.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/myaddr/internal" ) @@ -91,6 +92,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/mydnsjp/mydnsjp.go b/providers/dns/mydnsjp/mydnsjp.go index d0565e8bd..934fe764a 100644 --- a/providers/dns/mydnsjp/mydnsjp.go +++ b/providers/dns/mydnsjp/mydnsjp.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/mydnsjp/internal" ) @@ -79,9 +80,17 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("mydnsjp: some credentials information are missing") } + client := internal.NewClient(config.MasterID, config.Password) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, - client: internal.NewClient(config.MasterID, config.Password), + client: client, }, nil } diff --git a/providers/dns/mythicbeasts/mythicbeasts.go b/providers/dns/mythicbeasts/mythicbeasts.go index ae8f72d33..30d1eaa3e 100644 --- a/providers/dns/mythicbeasts/mythicbeasts.go +++ b/providers/dns/mythicbeasts/mythicbeasts.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/mythicbeasts/internal" ) @@ -117,6 +118,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/namecheap/namecheap.go b/providers/dns/namecheap/namecheap.go index 48b9492c4..e21ddf556 100644 --- a/providers/dns/namecheap/namecheap.go +++ b/providers/dns/namecheap/namecheap.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/namecheap/internal" "golang.org/x/net/publicsuffix" ) @@ -127,6 +128,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/namedotcom/namedotcom.go b/providers/dns/namedotcom/namedotcom.go index 789599552..1c0f162de 100644 --- a/providers/dns/namedotcom/namedotcom.go +++ b/providers/dns/namedotcom/namedotcom.go @@ -10,6 +10,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/namedotcom/go/v4/namecom" ) @@ -97,7 +98,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } client := namecom.New(config.Username, config.APIToken) - client.Client = config.HTTPClient + + if config.HTTPClient != nil { + client.Client = config.HTTPClient + } + + client.Client = clientdebug.Wrap(client.Client) if config.Server != "" { client.Server = config.Server diff --git a/providers/dns/namesilo/namesilo.go b/providers/dns/namesilo/namesilo.go index 8b12821e6..e2f66f8c9 100644 --- a/providers/dns/namesilo/namesilo.go +++ b/providers/dns/namesilo/namesilo.go @@ -10,6 +10,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/namesilo" ) @@ -84,7 +85,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("namesilo: credentials missing") } - return &DNSProvider{client: namesilo.NewClient(config.APIKey), config: config}, nil + client := namesilo.NewClient(config.APIKey) + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{client: client, config: config}, nil } // Present creates a TXT record to fulfill the dns-01 challenge. diff --git a/providers/dns/nearlyfreespeech/nearlyfreespeech.go b/providers/dns/nearlyfreespeech/nearlyfreespeech.go index 464ac35d0..af5e5363c 100644 --- a/providers/dns/nearlyfreespeech/nearlyfreespeech.go +++ b/providers/dns/nearlyfreespeech/nearlyfreespeech.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/nearlyfreespeech/internal" ) @@ -92,6 +93,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/netcup/netcup.go b/providers/dns/netcup/netcup.go index f0544bbcd..13b329e07 100644 --- a/providers/dns/netcup/netcup.go +++ b/providers/dns/netcup/netcup.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/netcup/internal" ) @@ -92,7 +93,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("netcup: %w", err) } - client.HTTPClient = config.HTTPClient + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) return &DNSProvider{client: client, config: config}, nil } diff --git a/providers/dns/netlify/netlify.go b/providers/dns/netlify/netlify.go index 1d4c78f4f..b95a1a128 100644 --- a/providers/dns/netlify/netlify.go +++ b/providers/dns/netlify/netlify.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/netlify/internal" ) @@ -84,7 +85,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("netlify: incomplete credentials, missing token") } - client := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.Token)) + client := internal.NewClient( + clientdebug.Wrap( + internal.OAuthStaticAccessToken(config.HTTPClient, config.Token), + ), + ) return &DNSProvider{ config: config, diff --git a/providers/dns/nicmanager/nicmanager.go b/providers/dns/nicmanager/nicmanager.go index 2a5742373..ff9a20125 100644 --- a/providers/dns/nicmanager/nicmanager.go +++ b/providers/dns/nicmanager/nicmanager.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/nicmanager/internal" ) @@ -128,6 +129,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{client: client, config: config}, nil } diff --git a/providers/dns/nicru/nicru.go b/providers/dns/nicru/nicru.go index 9320f94c2..cf4255bdb 100644 --- a/providers/dns/nicru/nicru.go +++ b/providers/dns/nicru/nicru.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/nicru/internal" ) @@ -90,7 +91,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("nicru: %w", err) } - client, err := internal.NewClient(oauthClient) + client, err := internal.NewClient(clientdebug.Wrap(oauthClient)) if err != nil { return nil, fmt.Errorf("nicru: unable to build API client: %w", err) } diff --git a/providers/dns/nifcloud/nifcloud.go b/providers/dns/nifcloud/nifcloud.go index 415921b52..2310d3805 100644 --- a/providers/dns/nifcloud/nifcloud.go +++ b/providers/dns/nifcloud/nifcloud.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/nifcloud/internal" ) @@ -94,6 +95,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + if config.BaseURL != "" { baseURL, err := url.Parse(config.BaseURL) if err != nil { diff --git a/providers/dns/njalla/njalla.go b/providers/dns/njalla/njalla.go index b08ce69de..f35e32f37 100644 --- a/providers/dns/njalla/njalla.go +++ b/providers/dns/njalla/njalla.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/njalla/internal" "github.com/miekg/dns" ) @@ -90,6 +91,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/nodion/nodion.go b/providers/dns/nodion/nodion.go index 1fdc8b87d..55af3a847 100644 --- a/providers/dns/nodion/nodion.go +++ b/providers/dns/nodion/nodion.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/nodion" ) @@ -93,6 +94,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/ns1/ns1.go b/providers/dns/ns1/ns1.go index c3bf168cb..83faf7e5e 100644 --- a/providers/dns/ns1/ns1.go +++ b/providers/dns/ns1/ns1.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "gopkg.in/ns1/ns1-go.v2/rest" "gopkg.in/ns1/ns1-go.v2/rest/model/dns" ) @@ -80,7 +81,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("ns1: credentials missing") } - client := rest.NewClient(config.HTTPClient, rest.SetAPIKey(config.APIKey)) + if config.HTTPClient == nil { + // Because the rest.NewClient uses the http.DefaultClient. + config.HTTPClient = &http.Client{Timeout: 10 * time.Second} + } + + client := rest.NewClient(clientdebug.Wrap(config.HTTPClient), rest.SetAPIKey(config.APIKey)) return &DNSProvider{client: client, config: config}, nil } diff --git a/providers/dns/octenium/octenium.go b/providers/dns/octenium/octenium.go index 1ace82bf5..383523575 100644 --- a/providers/dns/octenium/octenium.go +++ b/providers/dns/octenium/octenium.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/octenium/internal" "github.com/hashicorp/go-retryablehttp" ) @@ -89,11 +90,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { retryClient := retryablehttp.NewClient() retryClient.RetryMax = 5 - if config.HTTPClient != nil { - retryClient.HTTPClient = config.HTTPClient - } + retryClient.HTTPClient = client.HTTPClient retryClient.Logger = log.Logger + client.HTTPClient = clientdebug.Wrap(retryClient.StandardClient()) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/octenium/octenium_test.go b/providers/dns/octenium/octenium_test.go index c3d0ef558..a6c801bad 100644 --- a/providers/dns/octenium/octenium_test.go +++ b/providers/dns/octenium/octenium_test.go @@ -131,13 +131,13 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() config.APIKey = "secret" + config.HTTPClient = server.Client() p, err := NewDNSProviderConfig(config) if err != nil { return nil, err } - p.client.HTTPClient = server.Client() p.client.BaseURL, _ = url.Parse(server.URL) return p, nil diff --git a/providers/dns/oraclecloud/oraclecloud.go b/providers/dns/oraclecloud/oraclecloud.go index 47902568c..4ef891322 100644 --- a/providers/dns/oraclecloud/oraclecloud.go +++ b/providers/dns/oraclecloud/oraclecloud.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/oci-go-sdk/common/v1065" "github.com/nrdcg/oci-go-sdk/common/v1065/auth" "github.com/nrdcg/oci-go-sdk/dns/v1065" @@ -147,7 +148,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient + client.HTTPClient = clientdebug.Wrap(config.HTTPClient) } return &DNSProvider{client: &client, config: config}, nil diff --git a/providers/dns/otc/otc.go b/providers/dns/otc/otc.go index a6374f822..65b362124 100644 --- a/providers/dns/otc/otc.go +++ b/providers/dns/otc/otc.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/otc/internal" ) @@ -130,6 +131,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/ovh/ovh.go b/providers/dns/ovh/ovh.go index c70e943bc..7ef89eff2 100644 --- a/providers/dns/ovh/ovh.go +++ b/providers/dns/ovh/ovh.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" "github.com/ovh/go-ovh/ovh" ) @@ -277,5 +278,11 @@ func newClient(config *Config) (*ovh.Client, error) { client.UserAgent = useragent.Get() + if config.HTTPClient != nil { + client.Client = config.HTTPClient + } + + client.Client = clientdebug.Wrap(client.Client) + return client, nil } diff --git a/providers/dns/pdns/pdns.go b/providers/dns/pdns/pdns.go index ec0ae2a70..0d3c6fdea 100644 --- a/providers/dns/pdns/pdns.go +++ b/providers/dns/pdns/pdns.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/pdns/internal" ) @@ -103,6 +104,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client := internal.NewClient(config.Host, config.ServerName, config.APIVersion, config.APIKey) + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + if config.APIVersion <= 0 { err := client.SetAPIVersion(context.Background()) if err != nil { diff --git a/providers/dns/plesk/plesk.go b/providers/dns/plesk/plesk.go index b7a7ebf77..f377cb8ac 100644 --- a/providers/dns/plesk/plesk.go +++ b/providers/dns/plesk/plesk.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/plesk/internal" ) @@ -107,6 +108,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/porkbun/porkbun.go b/providers/dns/porkbun/porkbun.go index 44bf1857b..4805418ee 100644 --- a/providers/dns/porkbun/porkbun.go +++ b/providers/dns/porkbun/porkbun.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/porkbun" ) @@ -100,6 +101,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/rackspace/rackspace.go b/providers/dns/rackspace/rackspace.go index b9ce8f6e3..f796a494d 100644 --- a/providers/dns/rackspace/rackspace.go +++ b/providers/dns/rackspace/rackspace.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/rackspace/internal" ) @@ -118,6 +119,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/rainyun/rainyun.go b/providers/dns/rainyun/rainyun.go index 43ef9cb1b..a4d1c4035 100644 --- a/providers/dns/rainyun/rainyun.go +++ b/providers/dns/rainyun/rainyun.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/rainyun/internal" ) @@ -85,6 +86,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/rcodezero/rcodezero.go b/providers/dns/rcodezero/rcodezero.go index 93f3e957a..010a6dadc 100644 --- a/providers/dns/rcodezero/rcodezero.go +++ b/providers/dns/rcodezero/rcodezero.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/rcodezero/internal" ) @@ -86,6 +87,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/regfish/regfish.go b/providers/dns/regfish/regfish.go index 6a8ccee98..fb2ffaeb6 100644 --- a/providers/dns/regfish/regfish.go +++ b/providers/dns/regfish/regfish.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" regfishapi "github.com/regfish/regfish-dnsapi-go" ) @@ -84,6 +85,15 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client := regfishapi.NewClient(config.APIKey) + if config.HTTPClient != nil { + client.Client = config.HTTPClient + } else { + // Because the regfishapi.NewClient uses an empty http.Client. + client.Client = &http.Client{Timeout: 30 * time.Second} + } + + client.Client = clientdebug.Wrap(client.Client) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/regru/regru.go b/providers/dns/regru/regru.go index 1501863bd..b06b355c1 100644 --- a/providers/dns/regru/regru.go +++ b/providers/dns/regru/regru.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/regru/internal" ) @@ -97,6 +98,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + if config.TLSCert != "" || config.TLSKey != "" { if config.TLSCert == "" { return nil, errors.New("regru: TLS certificate is missing") diff --git a/providers/dns/rimuhosting/rimuhosting.go b/providers/dns/rimuhosting/rimuhosting.go index 9051d0add..08d7ad413 100644 --- a/providers/dns/rimuhosting/rimuhosting.go +++ b/providers/dns/rimuhosting/rimuhosting.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/rimuhosting" ) @@ -87,6 +88,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/safedns/safedns.go b/providers/dns/safedns/safedns.go index d979108a6..ce5c27672 100644 --- a/providers/dns/safedns/safedns.go +++ b/providers/dns/safedns/safedns.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/safedns/internal" "github.com/miekg/dns" ) @@ -90,6 +91,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/sakuracloud/sakuracloud.go b/providers/dns/sakuracloud/sakuracloud.go index 940b6ac5c..fad675611 100644 --- a/providers/dns/sakuracloud/sakuracloud.go +++ b/providers/dns/sakuracloud/sakuracloud.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" client "github.com/sacloud/api-client-go" "github.com/sacloud/iaas-api-go" @@ -101,7 +102,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { Options: &client.Options{ AccessToken: config.Token, AccessTokenSecret: config.Secret, - HttpClient: config.HTTPClient, + HttpClient: clientdebug.Wrap(config.HTTPClient), UserAgent: fmt.Sprintf("%s %s", iaas.DefaultUserAgent, useragent.Get()), }, } diff --git a/providers/dns/scaleway/scaleway.go b/providers/dns/scaleway/scaleway.go index 5976e77a2..9d08f93b9 100644 --- a/providers/dns/scaleway/scaleway.go +++ b/providers/dns/scaleway/scaleway.go @@ -5,6 +5,7 @@ package scaleway import ( "errors" "fmt" + "net/http" "strconv" "strings" "time" @@ -12,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" scwdomain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1" "github.com/scaleway/scaleway-sdk-go/scw" @@ -32,6 +34,7 @@ const ( EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) const ( @@ -47,12 +50,14 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - ProjectID string - Token string // TODO(ldez) rename to SecretKey in the next major. - AccessKey string + ProjectID string + Token string // TODO(ldez) rename to SecretKey in the next major. + AccessKey string + PropagationTimeout time.Duration PollingInterval time.Duration TTL int + HTTPClient *http.Client } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -62,6 +67,9 @@ func NewDefaultConfig() *Config { TTL: env.GetOneWithFallback(EnvTTL, minTTL, strconv.Atoi, altEnvName(EnvTTL)), PropagationTimeout: env.GetOneWithFallback(EnvPropagationTimeout, defaultPropagationTimeout, env.ParseSecond, altEnvName(EnvPropagationTimeout)), PollingInterval: env.GetOneWithFallback(EnvPollingInterval, defaultPollingInterval, env.ParseSecond, altEnvName(EnvPollingInterval)), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, } } @@ -107,6 +115,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { scw.WithUserAgent(useragent.Get()), } + if config.HTTPClient != nil { + configuration = append(configuration, scw.WithHTTPClient(clientdebug.Wrap(config.HTTPClient))) + } + if config.ProjectID != "" { configuration = append(configuration, scw.WithDefaultProjectID(config.ProjectID)) } diff --git a/providers/dns/scaleway/scaleway.toml b/providers/dns/scaleway/scaleway.toml index 21839e061..212cea295 100644 --- a/providers/dns/scaleway/scaleway.toml +++ b/providers/dns/scaleway/scaleway.toml @@ -18,6 +18,7 @@ lego --email you@example.com --dns scaleway -d '*.example.com' -d example.com ru SCW_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" SCW_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" SCW_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + SCW_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://developers.scaleway.com/en/products/domain/dns/api/" diff --git a/providers/dns/selectel/selectel.go b/providers/dns/selectel/selectel.go index c5da2215f..a7fb97cac 100644 --- a/providers/dns/selectel/selectel.go +++ b/providers/dns/selectel/selectel.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/selectel" ) @@ -92,10 +93,13 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } client := selectel.NewClient(config.Token) + if config.HTTPClient != nil { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + var err error client.BaseURL, err = url.Parse(config.BaseURL) if err != nil { diff --git a/providers/dns/selectelv2/selectelv2.go b/providers/dns/selectelv2/selectelv2.go index 2654cd742..6e3c1f42c 100644 --- a/providers/dns/selectelv2/selectelv2.go +++ b/providers/dns/selectelv2/selectelv2.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" "github.com/miekg/dns" selectelapi "github.com/selectel/domains-go/pkg/v2" @@ -134,7 +135,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { useragent.SetHeader(headers) return &DNSProvider{ - baseClient: selectelapi.NewClient(config.BaseURL, config.HTTPClient, headers), + baseClient: selectelapi.NewClient(config.BaseURL, clientdebug.Wrap(config.HTTPClient), headers), config: config, }, nil } diff --git a/providers/dns/selfhostde/selfhostde.go b/providers/dns/selfhostde/selfhostde.go index 0fea9f1d0..ccaba4647 100644 --- a/providers/dns/selfhostde/selfhostde.go +++ b/providers/dns/selfhostde/selfhostde.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/selfhostde/internal" ) @@ -132,6 +133,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/servercow/servercow.go b/providers/dns/servercow/servercow.go index 56f89f900..8583e7d9e 100644 --- a/providers/dns/servercow/servercow.go +++ b/providers/dns/servercow/servercow.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/servercow/internal" ) @@ -85,6 +86,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/shellrent/shellrent.go b/providers/dns/shellrent/shellrent.go index 488509a84..bc8809943 100644 --- a/providers/dns/shellrent/shellrent.go +++ b/providers/dns/shellrent/shellrent.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/shellrent/internal" ) @@ -103,6 +104,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/simply/simply.go b/providers/dns/simply/simply.go index d2bfb1874..434bb0d30 100644 --- a/providers/dns/simply/simply.go +++ b/providers/dns/simply/simply.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/simply/internal" ) @@ -99,6 +100,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/sonic/sonic.go b/providers/dns/sonic/sonic.go index 80f5ea295..5bda2b533 100644 --- a/providers/dns/sonic/sonic.go +++ b/providers/dns/sonic/sonic.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/sonic/internal" ) @@ -91,6 +92,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{client: client, config: config}, nil } diff --git a/providers/dns/spaceship/spaceship.go b/providers/dns/spaceship/spaceship.go index 9e8f0158e..e34c584c5 100644 --- a/providers/dns/spaceship/spaceship.go +++ b/providers/dns/spaceship/spaceship.go @@ -10,6 +10,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/spaceship/internal" ) @@ -84,6 +85,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/stackpath/internal/client.go b/providers/dns/stackpath/internal/client.go index bd11bf235..f06feb807 100644 --- a/providers/dns/stackpath/internal/client.go +++ b/providers/dns/stackpath/internal/client.go @@ -25,13 +25,13 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(ctx context.Context, stackID, clientID, clientSecret string) *Client { +func NewClient(stackID string, hc *http.Client) *Client { baseURL, _ := url.Parse(defaultBaseURL) return &Client{ baseURL: baseURL, stackID: stackID, - httpClient: createOAuthClient(ctx, clientID, clientSecret), + httpClient: hc, } } diff --git a/providers/dns/stackpath/internal/client_test.go b/providers/dns/stackpath/internal/client_test.go index 5195aa973..baac84397 100644 --- a/providers/dns/stackpath/internal/client_test.go +++ b/providers/dns/stackpath/internal/client_test.go @@ -1,7 +1,6 @@ package internal import ( - "context" "net/http" "net/http/httptest" "net/url" @@ -15,8 +14,8 @@ import ( func mockBuilder() *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { - client := NewClient(context.Background(), "STACK_ID", "CLIENT_ID", "CLIENT_SECRET") - client.httpClient = server.Client() + client := NewClient("STACK_ID", server.Client()) + client.baseURL, _ = url.Parse(server.URL + "/") return client, nil diff --git a/providers/dns/stackpath/internal/identity.go b/providers/dns/stackpath/internal/identity.go index 5c6e6ab17..fa3e9df07 100644 --- a/providers/dns/stackpath/internal/identity.go +++ b/providers/dns/stackpath/internal/identity.go @@ -9,7 +9,7 @@ import ( const defaultAuthURL = "https://gateway.stackpath.com/identity/v1/oauth2/token" -func createOAuthClient(ctx context.Context, clientID, clientSecret string) *http.Client { +func CreateOAuthClient(ctx context.Context, clientID, clientSecret string) *http.Client { config := &clientcredentials.Config{ TokenURL: defaultAuthURL, ClientID: clientID, diff --git a/providers/dns/stackpath/stackpath.go b/providers/dns/stackpath/stackpath.go index 6d12ce875..2e193b8a9 100644 --- a/providers/dns/stackpath/stackpath.go +++ b/providers/dns/stackpath/stackpath.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/stackpath/internal" ) @@ -86,9 +87,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("stackpath: stack id missing") } - client := internal.NewClient(context.Background(), config.StackID, config.ClientID, config.ClientSecret) - - return &DNSProvider{config: config, client: client}, nil + return &DNSProvider{ + config: config, + client: internal.NewClient(config.StackID, + clientdebug.Wrap( + internal.CreateOAuthClient(context.Background(), config.ClientID, config.ClientSecret), + ), + ), + }, nil } // Present creates a TXT record to fulfill the dns-01 challenge. diff --git a/providers/dns/technitium/technitium.go b/providers/dns/technitium/technitium.go index b2cf2d701..fc60c09ad 100644 --- a/providers/dns/technitium/technitium.go +++ b/providers/dns/technitium/technitium.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/technitium/internal" ) @@ -87,6 +88,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/timewebcloud/timewebcloud.go b/providers/dns/timewebcloud/timewebcloud.go index a2ab0dd65..0d3a36b46 100644 --- a/providers/dns/timewebcloud/timewebcloud.go +++ b/providers/dns/timewebcloud/timewebcloud.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/timewebcloud/internal" ) @@ -81,7 +82,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("timewebcloud: authentication token is missing") } - client := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.AuthToken)) + client := internal.NewClient( + clientdebug.Wrap( + internal.OAuthStaticAccessToken(config.HTTPClient, config.AuthToken), + ), + ) return &DNSProvider{ config: config, diff --git a/providers/dns/transip/transip.go b/providers/dns/transip/transip.go index 779704a21..a58a1bfe0 100644 --- a/providers/dns/transip/transip.go +++ b/providers/dns/transip/transip.go @@ -4,6 +4,7 @@ package transip import ( "errors" "fmt" + "net/http" "time" "github.com/go-acme/lego/v4/challenge" @@ -23,6 +24,7 @@ const ( EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) var _ challenge.ProviderTimeout = (*DNSProvider)(nil) @@ -34,6 +36,7 @@ type Config struct { PropagationTimeout time.Duration PollingInterval time.Duration TTL int64 + HTTPClient *http.Client } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -42,6 +45,9 @@ func NewDefaultConfig() *Config { TTL: int64(env.GetOrDefaultInt(EnvTTL, 10)), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 10*time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, } } @@ -73,10 +79,19 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("transip: the configuration of the DNS provider is nil") } - client, err := gotransip.NewClient(gotransip.ClientConfiguration{ + cfg := gotransip.ClientConfiguration{ AccountName: config.AccountName, PrivateKeyPath: config.PrivateKeyPath, - }) + } + + if config.HTTPClient != nil { + cfg.HTTPClient = config.HTTPClient + } else { + // Uses an explicit default HTTP client because the desec.NewDefaultClientOptions uses the http.DefaultClient. + cfg.HTTPClient = &http.Client{Timeout: 30 * time.Second} + } + + client, err := gotransip.NewClient(cfg) if err != nil { return nil, fmt.Errorf("transip: %w", err) } diff --git a/providers/dns/transip/transip.toml b/providers/dns/transip/transip.toml index 0625f819b..a894cc3e3 100644 --- a/providers/dns/transip/transip.toml +++ b/providers/dns/transip/transip.toml @@ -18,6 +18,7 @@ lego --email you@example.com --dns transip -d '*.example.com' -d example.com run TRANSIP_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" TRANSIP_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 600)" TRANSIP_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 10)" + TRANSIP_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://api.transip.eu/rest/docs.html" diff --git a/providers/dns/variomedia/variomedia.go b/providers/dns/variomedia/variomedia.go index 67d7b9a50..2dbf546b1 100644 --- a/providers/dns/variomedia/variomedia.go +++ b/providers/dns/variomedia/variomedia.go @@ -16,6 +16,7 @@ import ( "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/platform/wait" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/variomedia/internal" ) @@ -92,6 +93,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/vegadns/vegadns.go b/providers/dns/vegadns/vegadns.go index 6ddb33728..6375ecc26 100644 --- a/providers/dns/vegadns/vegadns.go +++ b/providers/dns/vegadns/vegadns.go @@ -5,11 +5,13 @@ import ( "context" "errors" "fmt" + "net/http" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/nrdcg/vegadns" ) @@ -24,18 +26,21 @@ const ( EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - BaseURL string - APIKey string - APISecret string + BaseURL string + APIKey string + APISecret string + PropagationTimeout time.Duration PollingInterval time.Duration TTL int + HTTPClient *http.Client } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -44,6 +49,9 @@ func NewDefaultConfig() *Config { TTL: env.GetOrDefaultInt(EnvTTL, 10), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 12*time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, time.Minute), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, } } @@ -76,7 +84,16 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("vegadns: the configuration of the DNS provider is nil") } - client, err := vegadns.NewClient(config.BaseURL, vegadns.WithOAuth(config.APIKey, config.APISecret)) + if config.HTTPClient == nil { + config.HTTPClient = &http.Client{Timeout: 30 * time.Second} + } + + config.HTTPClient = clientdebug.Wrap(config.HTTPClient) + + client, err := vegadns.NewClient(config.BaseURL, + vegadns.WithOAuth(config.APIKey, config.APISecret), + vegadns.WithHTTPClient(config.HTTPClient), + ) if err != nil { return nil, fmt.Errorf("vegadns: %w", err) } diff --git a/providers/dns/vercel/vercel.go b/providers/dns/vercel/vercel.go index 9ba92e21f..447165262 100644 --- a/providers/dns/vercel/vercel.go +++ b/providers/dns/vercel/vercel.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/vercel/internal" ) @@ -86,7 +87,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("vercel: credentials missing") } - client := internal.NewClient(internal.OAuthStaticAccessToken(config.HTTPClient, config.AuthToken), config.TeamID) + client := internal.NewClient( + clientdebug.Wrap( + internal.OAuthStaticAccessToken(config.HTTPClient, config.AuthToken), + ), + config.TeamID, + ) return &DNSProvider{ config: config, diff --git a/providers/dns/versio/versio.go b/providers/dns/versio/versio.go index 78ddd9bac..bc999a674 100644 --- a/providers/dns/versio/versio.go +++ b/providers/dns/versio/versio.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/versio/internal" ) @@ -108,6 +109,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/vinyldns/vinyldns.go b/providers/dns/vinyldns/vinyldns.go index 098347af4..62d7c3442 100644 --- a/providers/dns/vinyldns/vinyldns.go +++ b/providers/dns/vinyldns/vinyldns.go @@ -5,12 +5,14 @@ import ( "context" "errors" "fmt" + "net/http" "strconv" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/useragent" "github.com/vinyldns/go-vinyldns/vinyldns" ) @@ -27,6 +29,7 @@ const ( EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) var _ challenge.ProviderTimeout = (*DNSProvider)(nil) @@ -41,6 +44,7 @@ type Config struct { TTL int PropagationTimeout time.Duration PollingInterval time.Duration + HTTPClient *http.Client } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -49,6 +53,9 @@ func NewDefaultConfig() *Config { TTL: env.GetOrDefaultInt(EnvTTL, 30), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 4*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, } } @@ -97,7 +104,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { UserAgent: useragent.Get(), }) - client.HTTPClient.Timeout = 30 * time.Second + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } else { + // For compatibility, it should be removed in v5. + client.HTTPClient.Timeout = 30 * time.Second + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) return &DNSProvider{client: client, config: config}, nil } diff --git a/providers/dns/vinyldns/vinyldns.toml b/providers/dns/vinyldns/vinyldns.toml index 8c9f1b3a6..5789d10ab 100644 --- a/providers/dns/vinyldns/vinyldns.toml +++ b/providers/dns/vinyldns/vinyldns.toml @@ -26,6 +26,7 @@ Users are required to have DELETE ACL level or zone admin permissions on the Vin VINYLDNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 4)" VINYLDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" VINYLDNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 30)" + VINYLDNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://www.vinyldns.io/api/" diff --git a/providers/dns/vinyldns/vinyldns_test.go b/providers/dns/vinyldns/vinyldns_test.go index 05a6cf0df..c4741ea1c 100644 --- a/providers/dns/vinyldns/vinyldns_test.go +++ b/providers/dns/vinyldns/vinyldns_test.go @@ -162,15 +162,9 @@ func mockBuilder() *servermock.Builder[*DNSProvider] { config.AccessKey = "foo" config.SecretKey = "bar" config.Host = server.URL + config.HTTPClient = server.Client() - provider, err := NewDNSProviderConfig(config) - if err != nil { - return nil, err - } - - provider.client.HTTPClient = server.Client() - - return provider, nil + return NewDNSProviderConfig(config) }) } diff --git a/providers/dns/vscale/vscale.go b/providers/dns/vscale/vscale.go index 6c51ae5ca..1ecff3a60 100644 --- a/providers/dns/vscale/vscale.go +++ b/providers/dns/vscale/vscale.go @@ -14,6 +14,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/selectel" ) @@ -92,10 +93,13 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } client := selectel.NewClient(config.Token) + if config.HTTPClient != nil { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + var err error client.BaseURL, err = url.Parse(config.BaseURL) if err != nil { diff --git a/providers/dns/vultr/vultr.go b/providers/dns/vultr/vultr.go index 7672d2054..73e3480a2 100644 --- a/providers/dns/vultr/vultr.go +++ b/providers/dns/vultr/vultr.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/vultr/govultr/v3" "golang.org/x/oauth2" ) @@ -38,7 +39,7 @@ type Config struct { PollingInterval time.Duration TTL int HTTPClient *http.Client - HTTPTimeout time.Duration + HTTPTimeout time.Duration // TODO(ldez): remove in v5 } // NewDefaultConfig returns a default configuration for the DNSProvider. @@ -84,7 +85,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { authClient := OAuthStaticAccessToken(config.HTTPClient, config.APIKey) authClient.Timeout = config.HTTPTimeout - client := govultr.NewClient(authClient) + client := govultr.NewClient(clientdebug.Wrap(authClient)) return &DNSProvider{client: client, config: config}, nil } diff --git a/providers/dns/webnames/webnames.go b/providers/dns/webnames/webnames.go index 78905e22c..5dc5c4f2d 100644 --- a/providers/dns/webnames/webnames.go +++ b/providers/dns/webnames/webnames.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/webnames/internal" ) @@ -83,6 +84,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/websupport/websupport.go b/providers/dns/websupport/websupport.go index aa3c93578..7f93653c9 100644 --- a/providers/dns/websupport/websupport.go +++ b/providers/dns/websupport/websupport.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/internal/active24" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) const baseAPIDomain = "websupport.sk" @@ -88,6 +89,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/wedos/wedos.go b/providers/dns/wedos/wedos.go index 85187ec46..164fb5f10 100644 --- a/providers/dns/wedos/wedos.go +++ b/providers/dns/wedos/wedos.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/wedos/internal" ) @@ -94,6 +95,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } diff --git a/providers/dns/westcn/internal/client.go b/providers/dns/westcn/internal/client.go index 4d967f5e1..bfed159ae 100644 --- a/providers/dns/westcn/internal/client.go +++ b/providers/dns/westcn/internal/client.go @@ -14,8 +14,8 @@ import ( "strings" "time" + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" querystring "github.com/google/go-querystring/query" - "github.com/nrdcg/mailinabox/errutils" "golang.org/x/text/encoding" "golang.org/x/text/encoding/simplifiedchinese" "golang.org/x/text/transform" diff --git a/providers/dns/westcn/westcn.go b/providers/dns/westcn/westcn.go index 37f357b70..7efcfab21 100644 --- a/providers/dns/westcn/westcn.go +++ b/providers/dns/westcn/westcn.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/westcn/internal" ) @@ -91,6 +92,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/yandex/yandex.go b/providers/dns/yandex/yandex.go index c51602f67..d832f8859 100644 --- a/providers/dns/yandex/yandex.go +++ b/providers/dns/yandex/yandex.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/yandex/internal" "github.com/miekg/dns" ) @@ -88,6 +89,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{client: client, config: config}, nil } diff --git a/providers/dns/yandex360/yandex360.go b/providers/dns/yandex360/yandex360.go index aa749cf8f..0f4571750 100644 --- a/providers/dns/yandex360/yandex360.go +++ b/providers/dns/yandex360/yandex360.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/yandex360/internal" "github.com/miekg/dns" ) @@ -98,6 +99,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ client: client, config: config, diff --git a/providers/dns/zoneedit/zoneedit.go b/providers/dns/zoneedit/zoneedit.go index 875b84233..c815f975a 100644 --- a/providers/dns/zoneedit/zoneedit.go +++ b/providers/dns/zoneedit/zoneedit.go @@ -9,6 +9,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/zoneedit/internal" ) @@ -80,6 +81,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{ config: config, client: client, diff --git a/providers/dns/zoneee/zoneee.go b/providers/dns/zoneee/zoneee.go index 7dbbc4314..82e8effaf 100644 --- a/providers/dns/zoneee/zoneee.go +++ b/providers/dns/zoneee/zoneee.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/zoneee/internal" ) @@ -105,6 +106,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config.HTTPClient != nil { client.HTTPClient = config.HTTPClient } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + if config.Endpoint != nil { client.BaseURL = config.Endpoint } diff --git a/providers/dns/zonomi/zonomi.go b/providers/dns/zonomi/zonomi.go index 8c7a2943f..e6eae08de 100644 --- a/providers/dns/zonomi/zonomi.go +++ b/providers/dns/zonomi/zonomi.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/rimuhosting" ) @@ -87,6 +88,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = config.HTTPClient } + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + return &DNSProvider{config: config, client: client}, nil } From 5dba10703f09d8755b9eb2db7ede9e464bb71711 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 29 Oct 2025 19:39:54 +0100 Subject: [PATCH 198/298] iwantmyname: provider deprecation (#2694) --- README.md | 2 +- cmd/zz_gen_cmd_dnshelp.go | 2 +- docs/content/dns/zz_gen_iwantmyname.md | 8 +- providers/dns/iwantmyname/internal/client.go | 66 -------- .../dns/iwantmyname/internal/client_test.go | 46 ------ providers/dns/iwantmyname/internal/types.go | 9 -- providers/dns/iwantmyname/iwantmyname.go | 63 +------- providers/dns/iwantmyname/iwantmyname.toml | 8 +- providers/dns/iwantmyname/iwantmyname_test.go | 142 ------------------ 9 files changed, 15 insertions(+), 331 deletions(-) delete mode 100644 providers/dns/iwantmyname/internal/client.go delete mode 100644 providers/dns/iwantmyname/internal/client_test.go delete mode 100644 providers/dns/iwantmyname/internal/types.go delete mode 100644 providers/dns/iwantmyname/iwantmyname_test.go diff --git a/README.md b/README.md index c3d739f0c..630c7a14d 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Ionos IPv64 - iwantmyname + iwantmyname (Deprecated) Joker Joohoi's ACME-DNS diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 898f87ec9..cab22c30a 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -1946,7 +1946,7 @@ func displayDNSHelp(w io.Writer, name string) error { case "iwantmyname": // generated from: providers/dns/iwantmyname/iwantmyname.toml - ew.writeln(`Configuration for iwantmyname.`) + ew.writeln(`Configuration for iwantmyname (Deprecated).`) ew.writeln(`Code: 'iwantmyname'`) ew.writeln(`Since: 'v4.7.0'`) ew.writeln() diff --git a/docs/content/dns/zz_gen_iwantmyname.md b/docs/content/dns/zz_gen_iwantmyname.md index 251bf2096..cbdb29cb3 100644 --- a/docs/content/dns/zz_gen_iwantmyname.md +++ b/docs/content/dns/zz_gen_iwantmyname.md @@ -1,5 +1,5 @@ --- -title: "iwantmyname" +title: "iwantmyname (Deprecated)" date: 2019-03-03T16:39:46+01:00 draft: false slug: iwantmyname @@ -13,8 +13,10 @@ dnsprovider: +The iwantmyname API has shut down. + +https://github.com/go-acme/lego/issues/2563 -Configuration for [iwantmyname](https://iwantmyname.com). @@ -23,7 +25,7 @@ Configuration for [iwantmyname](https://iwantmyname.com). - Since: v4.7.0 -Here is an example bash command using the iwantmyname provider: +Here is an example bash command using the iwantmyname (Deprecated) provider: ```bash IWANTMYNAME_USERNAME=xxxxxxxx \ diff --git a/providers/dns/iwantmyname/internal/client.go b/providers/dns/iwantmyname/internal/client.go deleted file mode 100644 index c3418c854..000000000 --- a/providers/dns/iwantmyname/internal/client.go +++ /dev/null @@ -1,66 +0,0 @@ -package internal - -import ( - "context" - "fmt" - "net/http" - "net/url" - "time" - - "github.com/go-acme/lego/v4/providers/dns/internal/errutils" - querystring "github.com/google/go-querystring/query" -) - -const defaultBaseURL = "https://iwantmyname.com/basicauth/ddns" - -// Client iwantmyname client. -type Client struct { - username string - password string - - baseURL *url.URL - HTTPClient *http.Client -} - -// NewClient creates a new Client. -func NewClient(username, password string) *Client { - baseURL, _ := url.Parse(defaultBaseURL) - - return &Client{ - username: username, - password: password, - baseURL: baseURL, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, - } -} - -// SendRequest send a request (create/add/delete) to the API. -func (c *Client) SendRequest(ctx context.Context, record Record) error { - values, err := querystring.Values(record) - if err != nil { - return err - } - - endpoint := c.baseURL - endpoint.RawQuery = values.Encode() - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), http.NoBody) - if err != nil { - return fmt.Errorf("unable to create request: %w", err) - } - - req.SetBasicAuth(c.username, c.password) - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return errutils.NewHTTPDoError(req, err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode/100 != 2 { - return errutils.NewUnexpectedResponseStatusCodeError(req, resp) - } - - return nil -} diff --git a/providers/dns/iwantmyname/internal/client_test.go b/providers/dns/iwantmyname/internal/client_test.go deleted file mode 100644 index c25eb56ef..000000000 --- a/providers/dns/iwantmyname/internal/client_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package internal - -import ( - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "testing" - - "github.com/go-acme/lego/v4/platform/tester/servermock" - "github.com/stretchr/testify/require" -) - -func setupClient(server *httptest.Server) (*Client, error) { - client := NewClient("user", "secret") - client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) - - return client, nil -} - -func TestClient_Do(t *testing.T) { - client := servermock.NewBuilder[*Client](setupClient, - servermock.CheckHeader(). - WithBasicAuth("user", "secret"), - ). - Route("POST /", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - fmt.Println(req) - }), - servermock.CheckQueryParameter().Strict(). - With("hostname", "example.com"). - With("ttl", "120"). - With("type", "TXT"). - With("value", "data")). - Build(t) - - record := Record{ - Hostname: "example.com", - Type: "TXT", - Value: "data", - TTL: 120, - } - - err := client.SendRequest(t.Context(), record) - require.NoError(t, err) -} diff --git a/providers/dns/iwantmyname/internal/types.go b/providers/dns/iwantmyname/internal/types.go deleted file mode 100644 index b259235f5..000000000 --- a/providers/dns/iwantmyname/internal/types.go +++ /dev/null @@ -1,9 +0,0 @@ -package internal - -// Record represents a record. -type Record struct { - Hostname string `url:"hostname,omitempty"` - Type string `url:"type,omitempty"` - Value string `url:"value,omitempty"` - TTL int `url:"ttl,omitempty"` -} diff --git a/providers/dns/iwantmyname/iwantmyname.go b/providers/dns/iwantmyname/iwantmyname.go index dd3e9fc1a..f53287e69 100644 --- a/providers/dns/iwantmyname/iwantmyname.go +++ b/providers/dns/iwantmyname/iwantmyname.go @@ -2,17 +2,13 @@ package iwantmyname import ( - "context" "errors" "fmt" "net/http" "time" "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/iwantmyname/internal" ) // Environment variables names. @@ -42,20 +38,12 @@ type Config struct { // 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), - }, - } + return &Config{} } // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { config *Config - client *internal.Client } // NewDNSProvider returns a DNSProvider instance configured for iwantmyname. @@ -75,26 +63,7 @@ func NewDNSProvider() (*DNSProvider, error) { // NewDNSProviderConfig return a DNSProvider instance configured for iwantmyname. func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { - if config == nil { - return nil, errors.New("iwantmyname: the configuration of the DNS provider is nil") - } - - if config.Username == "" || config.Password == "" { - return nil, errors.New("iwantmyname: credentials missing") - } - - client := internal.NewClient(config.Username, config.Password) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - }, nil + return nil, errors.New("iwantmyname: the iwantmyname API has shut down https://github.com/go-acme/lego/issues/2563") } // Timeout returns the timeout and interval to use when checking for DNS propagation. @@ -105,38 +74,10 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, _, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - record := internal.Record{ - Hostname: dns01.UnFqdn(info.EffectiveFQDN), - Type: "TXT", - Value: info.Value, - TTL: d.config.TTL, - } - - err := d.client.SendRequest(context.Background(), record) - if err != nil { - return fmt.Errorf("iwantmyname: %w", err) - } - return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - record := internal.Record{ - Hostname: dns01.UnFqdn(info.EffectiveFQDN), - Type: "TXT", - Value: "delete", - TTL: d.config.TTL, - } - - err := d.client.SendRequest(context.Background(), record) - if err != nil { - return fmt.Errorf("iwantmyname: %w", err) - } - return nil } diff --git a/providers/dns/iwantmyname/iwantmyname.toml b/providers/dns/iwantmyname/iwantmyname.toml index 00a45b714..a138dee9e 100644 --- a/providers/dns/iwantmyname/iwantmyname.toml +++ b/providers/dns/iwantmyname/iwantmyname.toml @@ -1,5 +1,9 @@ -Name = "iwantmyname" -Description = '''''' +Name = "iwantmyname (Deprecated)" +Description = ''' +The iwantmyname API has shut down. + +https://github.com/go-acme/lego/issues/2563 +''' URL = "https://iwantmyname.com" Code = "iwantmyname" Since = "v4.7.0" diff --git a/providers/dns/iwantmyname/iwantmyname_test.go b/providers/dns/iwantmyname/iwantmyname_test.go deleted file mode 100644 index 7ae4545b2..000000000 --- a/providers/dns/iwantmyname/iwantmyname_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package iwantmyname - -import ( - "testing" - - "github.com/go-acme/lego/v4/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: "user", - EnvPassword: "secret", - }, - }, - { - desc: "missing credentials", - envVars: map[string]string{}, - expected: "iwantmyname: some credentials information are missing: IWANTMYNAME_USERNAME,IWANTMYNAME_PASSWORD", - }, - { - desc: "missing username", - envVars: map[string]string{ - EnvPassword: "secret", - }, - expected: "iwantmyname: some credentials information are missing: IWANTMYNAME_USERNAME", - }, - { - desc: "missing password", - envVars: map[string]string{ - EnvUsername: "user", - }, - expected: "iwantmyname: some credentials information are missing: IWANTMYNAME_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 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 - username string - password string - expected string - }{ - { - desc: "success", - username: "user", - password: "secret", - }, - { - desc: "missing credentials", - expected: "iwantmyname: credentials missing", - }, - { - desc: "missing username", - password: "secret", - expected: "iwantmyname: credentials missing", - }, - { - desc: "missing password", - username: "user", - expected: "iwantmyname: credentials missing", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - config := NewDefaultConfig() - config.Username = test.username - config.Password = test.password - - 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) -} From 81e0f2b42aba8dfda167646308583d919b7087b2 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 30 Oct 2025 13:02:35 +0100 Subject: [PATCH 199/298] chore: update linter (#2699) --- .github/workflows/pr.yml | 2 +- .golangci.yml | 3 --- acme/api/account.go | 5 +++++ acme/api/api.go | 1 + acme/api/authorization.go | 4 ++++ acme/api/challenge.go | 4 ++++ acme/api/internal/nonces/nonce_manager.go | 6 ++++- .../api/internal/nonces/nonce_manager_test.go | 5 +++++ acme/api/internal/secure/jws.go | 4 ++++ acme/api/internal/secure/jws_test.go | 5 +++++ acme/api/internal/sender/sender.go | 2 ++ acme/api/internal/sender/sender_test.go | 3 +++ acme/api/order.go | 3 +++ acme/api/order_test.go | 2 ++ acme/api/service.go | 2 ++ acme/commons.go | 8 ++++--- acme/errors.go | 19 ++++++++++------ certcrypto/crypto.go | 17 ++++++++++---- certcrypto/crypto_test.go | 1 + certificate/authorization.go | 2 ++ certificate/certificates.go | 5 +++++ certificate/renewal.go | 1 + challenge/challenges.go | 1 + challenge/dns01/dns_challenge.go | 6 +++++ challenge/dns01/dns_challenge_manual_test.go | 1 + challenge/dns01/dns_challenge_test.go | 1 + challenge/dns01/fqdn.go | 1 + challenge/dns01/mock_test.go | 3 +++ challenge/dns01/nameserver.go | 19 +++++++++++----- challenge/dns01/precheck.go | 3 +++ challenge/http01/domain_matcher.go | 9 ++++++++ challenge/http01/http_challenge.go | 2 ++ challenge/http01/http_challenge_server.go | 2 ++ challenge/http01/http_challenge_test.go | 5 +++++ challenge/resolver/errors.go | 2 ++ challenge/resolver/prober.go | 14 ++++++++++-- challenge/resolver/solver_manager.go | 3 +++ challenge/resolver/solver_manager_test.go | 2 ++ challenge/tlsalpn01/tls_alpn_challenge.go | 2 ++ .../tlsalpn01/tls_alpn_challenge_test.go | 11 ++++++++-- cmd/accounts_storage.go | 7 ++++++ cmd/certs_storage.go | 4 ++++ cmd/cmd_list.go | 3 +++ cmd/cmd_renew.go | 22 +++++++++++++++---- cmd/cmd_run.go | 7 ++++++ cmd/flags.go | 1 + cmd/hook.go | 2 ++ cmd/lego/main.go | 1 + cmd/setup.go | 5 +++++ cmd/setup_challenges.go | 5 +++++ e2e/challenges_test.go | 17 ++++++++++++++ e2e/dnschallenge/dns_challenges_test.go | 6 +++++ e2e/loader/loader.go | 17 ++++++++++++++ internal/clihelp/generator.go | 5 +++++ internal/dns/docs/generator.go | 11 ++++++++-- internal/dns/providers/generator.go | 1 + internal/releaser/releaser.go | 4 ++++ platform/config/env/env.go | 5 +++++ platform/tester/api.go | 1 + platform/tester/env.go | 2 ++ platform/tester/env_test.go | 3 +++ platform/tester/servermock/link_form.go | 3 +++ platform/tester/servermock/link_headers.go | 1 + platform/tester/servermock/link_query.go | 3 +++ .../tester/servermock/link_request_body.go | 1 + .../servermock/link_request_body_json.go | 3 +++ platform/wait/wait.go | 4 ++++ platform/wait/wait_test.go | 1 + providers/dns/acmedns/acmedns.go | 1 + providers/dns/acmedns/mock_test.go | 1 + providers/dns/active24/active24_test.go | 3 +++ providers/dns/alidns/alidns.go | 3 +++ providers/dns/alidns/alidns_test.go | 3 +++ providers/dns/allinkl/allinkl.go | 1 + providers/dns/allinkl/allinkl_test.go | 3 +++ providers/dns/allinkl/internal/client.go | 3 +++ providers/dns/allinkl/internal/types.go | 2 ++ providers/dns/anexia/anexia.go | 1 + providers/dns/anexia/anexia_test.go | 3 +++ providers/dns/anexia/internal/client.go | 2 ++ providers/dns/arvancloud/arvancloud.go | 1 + providers/dns/arvancloud/arvancloud_test.go | 3 +++ providers/dns/arvancloud/internal/client.go | 2 ++ .../dns/arvancloud/internal/client_test.go | 6 +++-- providers/dns/auroradns/auroradns_test.go | 1 + providers/dns/autodns/autodns_test.go | 3 +++ providers/dns/autodns/internal/client.go | 1 + providers/dns/axelname/axelname_test.go | 3 +++ providers/dns/axelname/internal/client.go | 1 + providers/dns/azion/azion.go | 3 +++ providers/dns/azion/azion_test.go | 3 +++ providers/dns/azure/azure.go | 5 +++++ providers/dns/azure/azure_test.go | 4 ++++ providers/dns/azure/private.go | 3 +++ providers/dns/azure/public.go | 3 +++ providers/dns/azuredns/azuredns_test.go | 3 +++ providers/dns/azuredns/credentials.go | 1 + providers/dns/azuredns/private.go | 1 + providers/dns/azuredns/public.go | 1 + providers/dns/azuredns/servicediscovery.go | 1 + providers/dns/baiducloud/baiducloud_test.go | 3 +++ providers/dns/beget/beget.go | 1 + providers/dns/beget/beget_test.go | 3 +++ providers/dns/beget/internal/client.go | 2 ++ providers/dns/beget/internal/types.go | 2 +- providers/dns/binarylane/binarylane.go | 1 + providers/dns/binarylane/binarylane_test.go | 3 +++ providers/dns/binarylane/internal/client.go | 2 ++ providers/dns/binarylane/internal/types.go | 8 ++++--- providers/dns/bindman/bindman.go | 2 ++ providers/dns/bindman/bindman_test.go | 3 +++ providers/dns/bluecat/bluecat_test.go | 3 +++ providers/dns/bluecat/internal/client.go | 2 ++ providers/dns/bluecat/internal/client_test.go | 1 + providers/dns/bookmyname/bookmyname_test.go | 3 +++ providers/dns/brandit/brandit.go | 2 ++ providers/dns/brandit/brandit_test.go | 3 +++ providers/dns/brandit/internal/client.go | 3 +++ providers/dns/bunny/bunny.go | 3 +++ providers/dns/bunny/bunny_test.go | 3 +++ providers/dns/checkdomain/checkdomain.go | 1 + providers/dns/checkdomain/checkdomain_test.go | 3 +++ providers/dns/checkdomain/internal/client.go | 6 +++++ providers/dns/civo/civo.go | 1 + providers/dns/civo/civo_test.go | 3 +++ providers/dns/civo/internal/client.go | 1 + providers/dns/civo/internal/client_test.go | 1 - providers/dns/clouddns/clouddns_test.go | 3 +++ providers/dns/clouddns/internal/client.go | 3 +++ providers/dns/clouddns/internal/identity.go | 1 + providers/dns/clouddns/internal/types.go | 2 +- providers/dns/cloudflare/cloudflare.go | 2 ++ providers/dns/cloudflare/cloudflare_test.go | 7 ++++++ providers/dns/cloudflare/internal/client.go | 1 + providers/dns/cloudflare/internal/types.go | 13 ++++++----- providers/dns/cloudflare/wrapper.go | 1 + providers/dns/cloudns/cloudns.go | 1 + providers/dns/cloudns/cloudns_test.go | 3 +++ providers/dns/cloudns/internal/client.go | 2 ++ providers/dns/cloudns/internal/client_test.go | 1 + providers/dns/cloudru/cloudru_test.go | 3 +++ providers/dns/cloudru/internal/client.go | 3 +++ providers/dns/cloudru/internal/identity.go | 2 ++ providers/dns/cloudru/internal/types.go | 6 ++--- providers/dns/conoha/conoha_test.go | 3 +++ providers/dns/conoha/internal/client.go | 1 + providers/dns/conoha/internal/client_test.go | 2 ++ providers/dns/conohav3/conohav3_test.go | 3 +++ providers/dns/conohav3/internal/client.go | 1 + .../dns/conohav3/internal/client_test.go | 2 ++ providers/dns/conohav3/internal/identity.go | 1 + providers/dns/constellix/constellix.go | 1 + providers/dns/constellix/constellix_test.go | 3 +++ providers/dns/constellix/internal/auth.go | 2 ++ providers/dns/constellix/internal/domains.go | 3 +++ .../dns/constellix/internal/txtrecords.go | 5 +++++ .../dns/corenetworks/corenetworks_test.go | 3 +++ providers/dns/corenetworks/internal/client.go | 3 +++ .../dns/corenetworks/internal/identity.go | 1 + providers/dns/cpanel/cpanel.go | 18 +++++++++++---- providers/dns/cpanel/cpanel_test.go | 3 +++ providers/dns/cpanel/internal/cpanel/types.go | 2 +- providers/dns/cpanel/internal/whm/types.go | 2 +- providers/dns/derak/derak.go | 1 + providers/dns/derak/derak_test.go | 3 +++ providers/dns/derak/internal/client.go | 7 ++++++ providers/dns/derak/internal/types.go | 2 +- providers/dns/desec/desec.go | 1 + providers/dns/desec/desec_test.go | 3 +++ providers/dns/designate/designate.go | 3 ++- providers/dns/designate/designate_test.go | 4 ++++ providers/dns/digitalocean/digitalocean.go | 2 ++ .../dns/digitalocean/digitalocean_test.go | 1 + providers/dns/digitalocean/internal/client.go | 2 ++ providers/dns/directadmin/directadmin_test.go | 3 +++ providers/dns/directadmin/internal/client.go | 1 + providers/dns/dns_providers_test.go | 2 ++ providers/dns/dnshomede/dnshomede.go | 1 + providers/dns/dnshomede/dnshomede_test.go | 3 +++ providers/dns/dnsimple/dnsimple.go | 1 + providers/dns/dnsimple/dnsimple_test.go | 1 + providers/dns/dnsmadeeasy/dnsmadeeasy.go | 3 +++ providers/dns/dnsmadeeasy/dnsmadeeasy_test.go | 2 ++ providers/dns/dnsmadeeasy/internal/client.go | 4 ++++ providers/dns/dnspod/dnspod.go | 3 +++ providers/dns/dnspod/dnspod_test.go | 3 +++ providers/dns/dode/dode_test.go | 3 +++ providers/dns/dode/internal/client.go | 1 + providers/dns/domeneshop/domeneshop_test.go | 3 +++ providers/dns/dreamhost/dreamhost.go | 1 + providers/dns/dreamhost/dreamhost_test.go | 2 ++ providers/dns/dreamhost/internal/client.go | 1 + providers/dns/duckdns/duckdns_test.go | 3 +++ providers/dns/duckdns/internal/client.go | 2 ++ providers/dns/dyn/dyn_test.go | 3 +++ providers/dns/dyn/internal/client.go | 1 + providers/dns/dyn/internal/session.go | 1 + providers/dns/dyndnsfree/dyndnsfree.go | 1 - providers/dns/dyndnsfree/dyndnsfree_test.go | 3 +++ providers/dns/dynu/dynu_test.go | 3 +++ providers/dns/dynu/internal/auth.go | 1 + providers/dns/dynu/internal/client.go | 4 ++++ providers/dns/easydns/easydns.go | 2 ++ providers/dns/easydns/easydns_test.go | 3 +++ providers/dns/easydns/internal/client.go | 2 ++ providers/dns/edgedns/edgedns.go | 5 +++++ .../dns/edgedns/edgedns_integration_test.go | 2 ++ providers/dns/edgedns/edgedns_test.go | 2 ++ providers/dns/edgeone/edgeone.go | 1 + providers/dns/edgeone/edgeone_test.go | 3 +++ providers/dns/edgeone/wrapper.go | 1 + providers/dns/efficientip/efficientip.go | 3 +++ providers/dns/efficientip/efficientip_test.go | 3 +++ providers/dns/efficientip/internal/client.go | 2 ++ providers/dns/epik/epik_test.go | 3 +++ providers/dns/epik/internal/client.go | 4 ++++ providers/dns/exec/exec_test.go | 4 ++++ providers/dns/exoscale/exoscale.go | 2 ++ providers/dns/exoscale/exoscale_test.go | 3 +++ providers/dns/f5xc/f5xc_test.go | 3 +++ providers/dns/f5xc/internal/client.go | 1 + providers/dns/f5xc/internal/types.go | 4 ++-- providers/dns/freemyip/freemyip_test.go | 3 +++ providers/dns/gandi/gandi_test.go | 3 +++ providers/dns/gandi/internal/client.go | 8 +++++++ providers/dns/gandi/internal/types.go | 3 +++ providers/dns/gandiv5/gandiv5.go | 4 ++++ providers/dns/gandiv5/gandiv5_test.go | 3 +++ providers/dns/gandiv5/internal/client.go | 4 ++++ providers/dns/gcloud/googlecloud.go | 11 ++++++++++ providers/dns/gcloud/googlecloud_test.go | 4 ++++ providers/dns/gcore/gcore_test.go | 3 +++ providers/dns/gcore/internal/client.go | 3 +++ providers/dns/glesys/glesys.go | 2 ++ providers/dns/glesys/glesys_test.go | 3 +++ providers/dns/glesys/internal/client.go | 1 + providers/dns/godaddy/godaddy.go | 2 ++ providers/dns/godaddy/godaddy_test.go | 3 +++ providers/dns/godaddy/internal/client.go | 2 ++ providers/dns/godaddy/internal/types.go | 14 ++++++++---- providers/dns/hetzner/hetzner_test.go | 1 + .../internal/hetznerv1/hetznerv1_test.go | 3 +++ .../internal/hetznerv1/internal/client.go | 4 ++++ .../internal/hetznerv1/internal/types.go | 14 +++++++----- .../hetzner/internal/legacy/hetzner_test.go | 3 +++ .../internal/legacy/internal/client.go | 2 ++ .../hetzner/internal/legacy/internal/types.go | 4 ++-- providers/dns/hostingde/hostingde.go | 2 ++ providers/dns/hostingde/hostingde_test.go | 3 +++ providers/dns/hostinger/hostinger_test.go | 3 +++ providers/dns/hostinger/internal/client.go | 2 ++ providers/dns/hostinger/internal/types.go | 8 ++++--- providers/dns/hosttech/hosttech.go | 1 + providers/dns/hosttech/hosttech_test.go | 3 +++ providers/dns/hosttech/internal/client.go | 5 +++++ providers/dns/hosttech/internal/types.go | 11 +++++++--- providers/dns/httpnet/httpnet.go | 2 ++ providers/dns/httpnet/httpnet_test.go | 3 +++ providers/dns/httpreq/httpreq.go | 5 +++++ providers/dns/httpreq/httpreq_test.go | 2 ++ providers/dns/huaweicloud/huaweicloud.go | 1 + providers/dns/huaweicloud/huaweicloud_test.go | 3 +++ providers/dns/hurricane/hurricane.go | 1 + providers/dns/hurricane/hurricane_test.go | 3 +++ providers/dns/hyperone/hyperone.go | 2 ++ providers/dns/hyperone/hyperone_test.go | 3 +++ providers/dns/hyperone/internal/passport.go | 1 + providers/dns/hyperone/internal/token_test.go | 2 ++ providers/dns/ibmcloud/ibmcloud_test.go | 3 +++ providers/dns/iij/iij.go | 2 ++ providers/dns/iij/iij_test.go | 3 +++ providers/dns/iijdpf/iijdpf_test.go | 3 +++ providers/dns/iijdpf/wrapper.go | 3 +++ providers/dns/infoblox/infoblox.go | 1 + providers/dns/infoblox/infoblox_test.go | 3 +++ providers/dns/infomaniak/infomaniak_test.go | 3 +++ providers/dns/infomaniak/internal/client.go | 2 ++ providers/dns/internal/active24/client.go | 4 ++++ providers/dns/internal/hostingde/types.go | 4 +++- providers/dns/internal/rimuhosting/client.go | 4 ++++ providers/dns/internal/selectel/client.go | 5 +++++ providers/dns/internetbs/internal/client.go | 3 +++ providers/dns/internetbs/internetbs_test.go | 3 +++ providers/dns/inwx/inwx.go | 2 ++ providers/dns/inwx/inwx_test.go | 2 ++ providers/dns/ionos/internal/client.go | 3 +++ providers/dns/ionos/internal/types.go | 13 ++++++----- providers/dns/ionos/ionos.go | 1 + providers/dns/ionos/ionos_test.go | 3 +++ providers/dns/ipv64/internal/client.go | 1 + providers/dns/ipv64/internal/types.go | 2 ++ providers/dns/ipv64/ipv64_test.go | 3 +++ providers/dns/joker/internal/dmapi/client.go | 3 +++ .../dns/joker/internal/dmapi/identity.go | 2 ++ providers/dns/joker/joker_test.go | 3 +++ providers/dns/joker/provider_dmapi.go | 3 +++ providers/dns/joker/provider_dmapi_test.go | 1 + providers/dns/joker/provider_svc_test.go | 1 + providers/dns/keyhelp/internal/client.go | 1 + providers/dns/keyhelp/keyhelp.go | 2 ++ providers/dns/keyhelp/keyhelp_test.go | 3 +++ providers/dns/liara/internal/client.go | 4 ++++ providers/dns/liara/liara.go | 4 ++++ providers/dns/liara/liara_test.go | 3 +++ providers/dns/lightsail/lightsail.go | 1 + .../lightsail/lightsail_integration_test.go | 1 + providers/dns/lightsail/lightsail_test.go | 1 + providers/dns/limacity/internal/client.go | 6 +++++ providers/dns/limacity/internal/types.go | 2 +- providers/dns/limacity/limacity.go | 2 ++ providers/dns/limacity/limacity_test.go | 3 +++ providers/dns/linode/linode.go | 3 +++ providers/dns/linode/linode_test.go | 3 +++ providers/dns/liquidweb/liquidweb.go | 2 ++ providers/dns/liquidweb/liquidweb_test.go | 2 ++ providers/dns/liquidweb/servermock_test.go | 7 ++++++ providers/dns/loopia/internal/types.go | 4 ++++ providers/dns/loopia/loopia_test.go | 3 +++ providers/dns/luadns/internal/client.go | 3 +++ providers/dns/luadns/luadns_test.go | 3 +++ providers/dns/mailinabox/mailinabox_test.go | 3 +++ providers/dns/manageengine/internal/client.go | 2 ++ providers/dns/manageengine/manageengine.go | 1 + .../dns/manageengine/manageengine_test.go | 3 +++ providers/dns/metaname/metaname.go | 1 + providers/dns/metaname/metaname_test.go | 3 +++ .../dns/metaregistrar/internal/client.go | 1 + .../dns/metaregistrar/metaregistrar_test.go | 3 +++ providers/dns/mijnhost/internal/client.go | 3 +++ providers/dns/mijnhost/mijnhost_test.go | 3 +++ providers/dns/mittwald/internal/client.go | 5 +++++ providers/dns/mittwald/internal/types.go | 19 ++++++++++------ providers/dns/mittwald/mittwald.go | 1 + providers/dns/mittwald/mittwald_test.go | 3 +++ providers/dns/myaddr/myaddr_test.go | 3 +++ providers/dns/mydnsjp/mydnsjp.go | 2 ++ providers/dns/mydnsjp/mydnsjp_test.go | 3 +++ providers/dns/mythicbeasts/internal/client.go | 1 + .../dns/mythicbeasts/internal/identity.go | 2 ++ providers/dns/mythicbeasts/mythicbeasts.go | 1 + .../dns/mythicbeasts/mythicbeasts_test.go | 4 ++++ providers/dns/namecheap/internal/client.go | 3 +++ providers/dns/namecheap/namecheap.go | 10 +++++++-- providers/dns/namecheap/namecheap_test.go | 1 + providers/dns/namedotcom/namedotcom.go | 2 ++ providers/dns/namedotcom/namedotcom_test.go | 3 +++ providers/dns/namesilo/namesilo.go | 1 + providers/dns/namesilo/namesilo_test.go | 2 ++ .../dns/nearlyfreespeech/internal/client.go | 2 +- .../nearlyfreespeech/internal/client_test.go | 1 + .../nearlyfreespeech/nearlyfreespeech_test.go | 3 +++ providers/dns/netcup/internal/client.go | 3 +++ providers/dns/netcup/internal/session.go | 1 + providers/dns/netcup/netcup_test.go | 2 ++ providers/dns/netlify/internal/client.go | 2 ++ providers/dns/netlify/netlify.go | 1 + providers/dns/netlify/netlify_test.go | 3 +++ providers/dns/nicmanager/internal/client.go | 1 + providers/dns/nicmanager/nicmanager.go | 7 ++++-- providers/dns/nicmanager/nicmanager_test.go | 3 +++ providers/dns/nicru/internal/client.go | 1 + providers/dns/nicru/nicru_test.go | 3 +++ providers/dns/nifcloud/internal/client.go | 5 +++++ providers/dns/nifcloud/nifcloud.go | 2 ++ providers/dns/nifcloud/nifcloud_test.go | 3 +++ providers/dns/njalla/internal/client.go | 2 ++ providers/dns/njalla/njalla.go | 1 + providers/dns/njalla/njalla_test.go | 3 +++ providers/dns/nodion/nodion.go | 1 + providers/dns/nodion/nodion_test.go | 3 +++ providers/dns/ns1/ns1.go | 2 ++ providers/dns/ns1/ns1_test.go | 3 +++ providers/dns/octenium/octenium.go | 1 + providers/dns/octenium/octenium_test.go | 3 +++ providers/dns/oraclecloud/oraclecloud.go | 1 + providers/dns/oraclecloud/oraclecloud_test.go | 9 +++++--- providers/dns/otc/internal/client.go | 6 +++++ providers/dns/otc/internal/identity.go | 3 +++ providers/dns/otc/internal/types.go | 8 +++---- providers/dns/otc/otc_test.go | 3 +++ providers/dns/ovh/ovh.go | 10 +++++++-- providers/dns/ovh/ovh_test.go | 4 ++++ providers/dns/pdns/internal/client.go | 5 +++++ providers/dns/pdns/pdns.go | 1 + providers/dns/pdns/pdns_test.go | 3 +++ providers/dns/plesk/internal/client.go | 2 ++ providers/dns/plesk/plesk.go | 1 + providers/dns/plesk/plesk_test.go | 3 +++ providers/dns/porkbun/porkbun.go | 1 + providers/dns/porkbun/porkbun_test.go | 3 +++ providers/dns/rackspace/internal/client.go | 2 ++ providers/dns/rackspace/internal/identity.go | 1 + providers/dns/rackspace/rackspace.go | 1 + providers/dns/rackspace/rackspace_test.go | 3 +++ providers/dns/rainyun/internal/client.go | 2 ++ providers/dns/rainyun/rainyun_test.go | 3 +++ providers/dns/rcodezero/internal/client.go | 2 ++ providers/dns/rcodezero/rcodezero_test.go | 2 ++ providers/dns/regfish/regfish.go | 1 + providers/dns/regfish/regfish_test.go | 3 +++ providers/dns/regru/internal/client.go | 2 ++ providers/dns/regru/regru_test.go | 3 +++ providers/dns/rfc2136/rfc2136.go | 3 +++ providers/dns/rfc2136/rfc2136_test.go | 2 ++ providers/dns/rimuhosting/rimuhosting_test.go | 3 +++ providers/dns/route53/route53.go | 5 +++++ providers/dns/route53/route53_test.go | 6 +++++ providers/dns/safedns/internal/client.go | 2 ++ providers/dns/safedns/safedns.go | 1 + providers/dns/safedns/safedns_test.go | 3 +++ providers/dns/sakuracloud/sakuracloud.go | 1 + providers/dns/sakuracloud/sakuracloud_test.go | 3 +++ providers/dns/sakuracloud/wrapper.go | 2 ++ providers/dns/sakuracloud/wrapper_test.go | 2 ++ providers/dns/scaleway/scaleway_test.go | 3 +++ providers/dns/selectel/selectel.go | 3 +++ providers/dns/selectel/selectel_test.go | 3 +++ providers/dns/selectelv2/selectelv2_test.go | 3 +++ providers/dns/selfhostde/mapping.go | 6 +++-- providers/dns/selfhostde/selfhostde.go | 1 + providers/dns/selfhostde/selfhostde_test.go | 3 +++ providers/dns/servercow/internal/client.go | 4 ++++ providers/dns/servercow/internal/types.go | 1 + providers/dns/servercow/servercow.go | 2 ++ providers/dns/servercow/servercow_test.go | 3 +++ providers/dns/shellrent/internal/client.go | 3 +++ providers/dns/shellrent/internal/types.go | 2 ++ providers/dns/shellrent/shellrent.go | 1 + providers/dns/shellrent/shellrent_test.go | 3 +++ providers/dns/simply/internal/client.go | 2 ++ providers/dns/simply/simply.go | 1 + providers/dns/simply/simply_test.go | 3 +++ providers/dns/sonic/internal/client.go | 1 + providers/dns/sonic/sonic_test.go | 3 +++ providers/dns/spaceship/internal/client.go | 2 ++ providers/dns/spaceship/spaceship_test.go | 3 +++ providers/dns/stackpath/internal/client.go | 3 +++ providers/dns/stackpath/stackpath_test.go | 3 +++ providers/dns/technitium/internal/client.go | 2 ++ providers/dns/technitium/technitium_test.go | 3 +++ .../dns/tencentcloud/tencentcloud_test.go | 3 +++ providers/dns/tencentcloud/wrapper.go | 2 ++ providers/dns/timewebcloud/internal/client.go | 2 ++ providers/dns/timewebcloud/timewebcloud.go | 1 + .../dns/timewebcloud/timewebcloud_test.go | 3 +++ providers/dns/transip/transip.go | 1 + providers/dns/transip/transip_test.go | 4 ++++ providers/dns/ultradns/ultradns.go | 6 +++-- providers/dns/ultradns/ultradns_test.go | 1 + providers/dns/variomedia/internal/client.go | 4 ++++ providers/dns/variomedia/variomedia.go | 1 + providers/dns/variomedia/variomedia_test.go | 3 +++ providers/dns/vegadns/vegadns_test.go | 5 +++++ providers/dns/vercel/internal/client.go | 2 ++ providers/dns/vercel/vercel.go | 1 + providers/dns/vercel/vercel_test.go | 3 +++ providers/dns/versio/internal/client.go | 4 ++++ providers/dns/versio/versio.go | 5 +++++ providers/dns/versio/versio_test.go | 5 +++++ providers/dns/vinyldns/vinyldns.go | 1 + providers/dns/vinyldns/vinyldns_test.go | 3 +++ providers/dns/vinyldns/wrapper.go | 1 + providers/dns/vkcloud/internal/client.go | 2 ++ providers/dns/vkcloud/vkcloud.go | 1 + providers/dns/vkcloud/vkcloud_test.go | 3 +++ providers/dns/volcengine/volcengine.go | 1 + providers/dns/volcengine/volcengine_test.go | 3 +++ providers/dns/vscale/vscale.go | 3 +++ providers/dns/vscale/vscale_test.go | 3 +++ providers/dns/vultr/vultr.go | 2 ++ providers/dns/vultr/vultr_test.go | 3 +++ providers/dns/webnames/internal/client.go | 1 + providers/dns/webnames/webnames_test.go | 3 +++ providers/dns/websupport/websupport_test.go | 3 +++ providers/dns/wedos/internal/client.go | 1 + providers/dns/wedos/internal/token.go | 4 ++++ providers/dns/wedos/wedos_test.go | 3 +++ providers/dns/westcn/westcn.go | 1 + providers/dns/westcn/westcn_test.go | 3 +++ providers/dns/yandex/internal/client.go | 3 +++ providers/dns/yandex/internal/types.go | 3 +++ providers/dns/yandex/yandex.go | 2 ++ providers/dns/yandex/yandex_test.go | 3 +++ providers/dns/yandex360/internal/client.go | 1 + providers/dns/yandex360/yandex360_test.go | 3 +++ providers/dns/yandexcloud/yandexcloud.go | 2 ++ providers/dns/yandexcloud/yandexcloud_test.go | 3 +++ providers/dns/zoneedit/internal/client.go | 1 + providers/dns/zoneedit/zoneedit_test.go | 3 +++ providers/dns/zoneee/zoneee.go | 3 +++ providers/dns/zoneee/zoneee_test.go | 5 +++++ providers/dns/zonomi/zonomi_test.go | 3 +++ providers/http/memcached/memcached.go | 2 ++ providers/http/memcached/memcached_test.go | 8 +++++++ providers/http/s3/s3.go | 1 + providers/http/webroot/webroot.go | 1 + providers/http/webroot/webroot_test.go | 1 + registration/registar.go | 3 ++- 498 files changed, 1439 insertions(+), 112 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6b9bce14e..91977bc28 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: stable - GOLANGCI_LINT_VERSION: v2.5.0 + GOLANGCI_LINT_VERSION: v2.6.0 HUGO_VERSION: 0.148.2 CGO_ENABLED: 0 LEGO_E2E_TESTS: CI diff --git a/.golangci.yml b/.golangci.yml index 2fabe806c..a6f0c4bfa 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -50,11 +50,8 @@ linters: - tagliatelle - testpackage # not relevant - tparallel # not relevant - - usestdlibvars # false-positive https://github.com/sashamelentyev/usestdlibvars/issues/96 - varnamelen # not relevant - wrapcheck - - wsl_v5 # should be enabled the future. - - embeddedstructfieldcheck # should be enabled the future. settings: depguard: diff --git a/acme/api/account.go b/acme/api/account.go index cab5d477f..62e5ef9a6 100644 --- a/acme/api/account.go +++ b/acme/api/account.go @@ -13,6 +13,7 @@ type AccountService service // New Creates a new account. func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) { var account acme.Account + resp, err := a.core.post(a.core.GetDirectory().NewAccountURL, req, &account) location := getLocation(resp) @@ -51,10 +52,12 @@ func (a *AccountService) Get(accountURL string) (acme.Account, error) { } var account acme.Account + _, err := a.core.postAsGet(accountURL, &account) if err != nil { return acme.Account{}, err } + return account, nil } @@ -65,6 +68,7 @@ func (a *AccountService) Update(accountURL string, req acme.Account) (acme.Accou } var account acme.Account + _, err := a.core.post(accountURL, req, &account) if err != nil { return acme.Account{}, err @@ -81,6 +85,7 @@ func (a *AccountService) Deactivate(accountURL string) error { req := acme.Account{Status: acme.StatusDeactivated} _, err := a.core.post(accountURL, req, nil) + return err } diff --git a/acme/api/api.go b/acme/api/api.go index cdad156dc..da1c94d1b 100644 --- a/acme/api/api.go +++ b/acme/api/api.go @@ -155,6 +155,7 @@ func getDirectory(do *sender.Doer, caDirURL string) (acme.Directory, error) { if dir.NewAccountURL == "" { return dir, errors.New("directory missing new registration URL") } + if dir.NewOrderURL == "" { return dir, errors.New("directory missing new order URL") } diff --git a/acme/api/authorization.go b/acme/api/authorization.go index a9972aa94..4195bd1fe 100644 --- a/acme/api/authorization.go +++ b/acme/api/authorization.go @@ -15,10 +15,12 @@ func (c *AuthorizationService) Get(authzURL string) (acme.Authorization, error) } var authz acme.Authorization + _, err := c.core.postAsGet(authzURL, &authz) if err != nil { return acme.Authorization{}, err } + return authz, nil } @@ -29,6 +31,8 @@ func (c *AuthorizationService) Deactivate(authzURL string) error { } var disabledAuth acme.Authorization + _, err := c.core.post(authzURL, acme.Authorization{Status: acme.StatusDeactivated}, &disabledAuth) + return err } diff --git a/acme/api/challenge.go b/acme/api/challenge.go index 875dede6e..2af55fc1a 100644 --- a/acme/api/challenge.go +++ b/acme/api/challenge.go @@ -17,6 +17,7 @@ func (c *ChallengeService) New(chlgURL string) (acme.ExtendedChallenge, error) { // Challenge initiation is done by sending a JWS payload containing the trivial JSON object `{}`. // We use an empty struct instance as the postJSON payload here to achieve this result. var chlng acme.ExtendedChallenge + resp, err := c.core.post(chlgURL, struct{}{}, &chlng) if err != nil { return acme.ExtendedChallenge{}, err @@ -24,6 +25,7 @@ func (c *ChallengeService) New(chlgURL string) (acme.ExtendedChallenge, error) { chlng.AuthorizationURL = getLink(resp.Header, "up") chlng.RetryAfter = getRetryAfter(resp) + return chlng, nil } @@ -34,6 +36,7 @@ func (c *ChallengeService) Get(chlgURL string) (acme.ExtendedChallenge, error) { } var chlng acme.ExtendedChallenge + resp, err := c.core.postAsGet(chlgURL, &chlng) if err != nil { return acme.ExtendedChallenge{}, err @@ -41,5 +44,6 @@ func (c *ChallengeService) Get(chlgURL string) (acme.ExtendedChallenge, error) { chlng.AuthorizationURL = getLink(resp.Header, "up") chlng.RetryAfter = getRetryAfter(resp) + return chlng, nil } diff --git a/acme/api/internal/nonces/nonce_manager.go b/acme/api/internal/nonces/nonce_manager.go index d089cf07c..04a4ac620 100644 --- a/acme/api/internal/nonces/nonce_manager.go +++ b/acme/api/internal/nonces/nonce_manager.go @@ -11,10 +11,11 @@ import ( // Manager Manages nonces. type Manager struct { + sync.Mutex + do *sender.Doer nonceURL string nonces []string - sync.Mutex } // NewManager Creates a new Manager. @@ -36,6 +37,7 @@ func (n *Manager) Pop() (string, bool) { nonce := n.nonces[len(n.nonces)-1] n.nonces = n.nonces[:len(n.nonces)-1] + return nonce, true } @@ -43,6 +45,7 @@ func (n *Manager) Pop() (string, bool) { func (n *Manager) Push(nonce string) { n.Lock() defer n.Unlock() + n.nonces = append(n.nonces, nonce) } @@ -51,6 +54,7 @@ func (n *Manager) Nonce() (string, error) { if nonce, ok := n.Pop(); ok { return nonce, nil } + return n.getNonce() } diff --git a/acme/api/internal/nonces/nonce_manager_test.go b/acme/api/internal/nonces/nonce_manager_test.go index abaff02f5..4490165df 100644 --- a/acme/api/internal/nonces/nonce_manager_test.go +++ b/acme/api/internal/nonces/nonce_manager_test.go @@ -30,11 +30,13 @@ func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) { ch := make(chan bool) resultCh := make(chan bool) + go func() { _, errN := manager.Nonce() if errN != nil { t.Log(errN) } + ch <- true }() go func() { @@ -42,13 +44,16 @@ func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) { if errN != nil { t.Log(errN) } + ch <- true }() go func() { <-ch <-ch + resultCh <- true }() + select { case <-resultCh: case <-time.After(500 * time.Millisecond): diff --git a/acme/api/internal/secure/jws.go b/acme/api/internal/secure/jws.go index 7aa6c4c46..8cd598663 100644 --- a/acme/api/internal/secure/jws.go +++ b/acme/api/internal/secure/jws.go @@ -36,6 +36,7 @@ func (j *JWS) SetKid(kid string) { // SignContent Signs a content with the JWS. func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, error) { var alg jose.SignatureAlgorithm + switch k := j.privKey.(type) { case *rsa.PrivateKey: alg = jose.RS256 @@ -72,12 +73,14 @@ func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, e if err != nil { return nil, fmt.Errorf("failed to sign content: %w", err) } + return signed, nil } // SignEABContent Signs an external account binding content with the JWS. func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignature, error) { jwk := jose.JSONWebKey{Key: j.privKey} + jwkJSON, err := jwk.Public().MarshalJSON() if err != nil { return nil, fmt.Errorf("acme: error encoding eab jwk key: %w", err) @@ -108,6 +111,7 @@ func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignatu // GetKeyAuthorization Gets the key authorization for a token. func (j *JWS) GetKeyAuthorization(token string) (string, error) { var publicKey crypto.PublicKey + switch k := j.privKey.(type) { case *ecdsa.PrivateKey: publicKey = k.Public() diff --git a/acme/api/internal/secure/jws_test.go b/acme/api/internal/secure/jws_test.go index 6a9dc5459..d033cb0c4 100644 --- a/acme/api/internal/secure/jws_test.go +++ b/acme/api/internal/secure/jws_test.go @@ -31,11 +31,13 @@ func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) { ch := make(chan bool) resultCh := make(chan bool) + go func() { _, errN := manager.Nonce() if errN != nil { t.Log(errN) } + ch <- true }() go func() { @@ -43,13 +45,16 @@ func TestNotHoldingLockWhileMakingHTTPRequests(t *testing.T) { if errN != nil { t.Log(errN) } + ch <- true }() go func() { <-ch <-ch + resultCh <- true }() + select { case <-resultCh: case <-time.After(500 * time.Millisecond): diff --git a/acme/api/internal/sender/sender.go b/acme/api/internal/sender/sender.go index e70a4bd9c..d5db5d410 100644 --- a/acme/api/internal/sender/sender.go +++ b/acme/api/internal/sender/sender.go @@ -127,6 +127,7 @@ func checkError(req *http.Request, resp *http.Response) error { } var errorDetails *acme.ProblemDetails + err = json.Unmarshal(body, &errorDetails) if err != nil { return fmt.Errorf("%d ::%s :: %s :: %w :: %s", resp.StatusCode, req.Method, req.URL, err, string(body)) @@ -150,6 +151,7 @@ func checkError(req *http.Request, resp *http.Response) error { return errorDetails } + return nil } diff --git a/acme/api/internal/sender/sender_test.go b/acme/api/internal/sender/sender_test.go index 69bb8ae4e..1f25c6d26 100644 --- a/acme/api/internal/sender/sender_test.go +++ b/acme/api/internal/sender/sender_test.go @@ -12,6 +12,7 @@ import ( func TestDo_UserAgentOnAllHTTPMethod(t *testing.T) { var ua, method string + server := httptest.NewTLSServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { ua = r.Header.Get("User-Agent") method = r.Method @@ -60,9 +61,11 @@ func TestDo_CustomUserAgent(t *testing.T) { ua := doer.formatUserAgent() assert.Contains(t, ua, ourUserAgent) assert.Contains(t, ua, customUA) + if strings.HasSuffix(ua, " ") { t.Errorf("UA should not have trailing spaces; got '%s'", ua) } + assert.Len(t, strings.Split(ua, " "), 5) } diff --git a/acme/api/order.go b/acme/api/order.go index 0c679fdb1..fad6be2b8 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -56,6 +56,7 @@ func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acm } var order acme.Order + resp, err := o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order) if err != nil { are := &acme.AlreadyReplacedError{} @@ -107,6 +108,7 @@ func (o *OrderService) Get(orderURL string) (acme.ExtendedOrder, error) { } var order acme.Order + _, err := o.core.postAsGet(orderURL, &order) if err != nil { return acme.ExtendedOrder{}, err @@ -122,6 +124,7 @@ func (o *OrderService) UpdateForCSR(orderURL string, csr []byte) (acme.ExtendedO } var order acme.Order + _, err := o.core.post(orderURL, csrMsg, &order) if err != nil { return acme.ExtendedOrder{}, err diff --git a/acme/api/order_test.go b/acme/api/order_test.go index e2fcbdf2c..f74f473d2 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -32,6 +32,7 @@ func TestOrderService_NewWithOptions(t *testing.T) { } order := acme.Order{} + err = json.Unmarshal(body, &order) if err != nil { http.Error(rw, err.Error(), http.StatusBadRequest) @@ -107,6 +108,7 @@ func readSignedBody(r *http.Request, privateKey *rsa.PrivateKey) ([]byte, error) } sigAlgs := []jose.SignatureAlgorithm{jose.RS256} + jws, err := jose.ParseSigned(string(reqBody), sigAlgs) if err != nil { return nil, err diff --git a/acme/api/service.go b/acme/api/service.go index 6f812ee03..65518e1d9 100644 --- a/acme/api/service.go +++ b/acme/api/service.go @@ -23,11 +23,13 @@ func getLinks(header http.Header, rel string) []string { linkExpr := regexp.MustCompile(`<(.+?)>(?:;[^;]+)*?;\s*rel="(.+?)"`) var links []string + for _, link := range header["Link"] { for _, m := range linkExpr.FindAllStringSubmatch(link, -1) { if len(m) != 3 { continue } + if m[2] == rel { links = append(links, m[1]) } diff --git a/acme/commons.go b/acme/commons.go index bd0506c09..0af623e4e 100644 --- a/acme/commons.go +++ b/acme/commons.go @@ -84,6 +84,7 @@ type Meta struct { // ExtendedAccount an extended Account. type ExtendedAccount struct { Account + // Contains the value of the response header `Location` Location string `json:"-"` } @@ -220,11 +221,11 @@ type Authorization struct { // The timestamp after which the server will consider this authorization invalid, // encoded in the format specified in RFC 3339 [RFC3339]. // This field is REQUIRED for objects with "valid" in the "status" field. - Expires time.Time `json:"expires,omitempty"` + Expires time.Time `json:"expires,omitzero"` // identifier (required, object): // The identifier that the account is authorized to represent - Identifier Identifier `json:"identifier,omitempty"` + Identifier Identifier `json:"identifier"` // challenges (required, array of objects): // For pending authorizations, the challenges that the client can fulfill in order to prove possession of the identifier. @@ -244,6 +245,7 @@ type Authorization struct { // ExtendedChallenge a extended Challenge. type ExtendedChallenge struct { Challenge + // Contains the value of the response header `Retry-After` RetryAfter string `json:"-"` // Contains the value of the response header `Link` rel="up" @@ -270,7 +272,7 @@ type Challenge struct { // The time at which the server validated this challenge, // encoded in the format specified in RFC 3339 [RFC3339]. // This field is REQUIRED if the "status" field is "valid". - Validated time.Time `json:"validated,omitempty"` + Validated time.Time `json:"validated,omitzero"` // error (optional, object): // Error that occurred while the server was validating the challenge, if any, diff --git a/acme/errors.go b/acme/errors.go index 9a255468d..161a47c38 100644 --- a/acme/errors.go +++ b/acme/errors.go @@ -2,6 +2,7 @@ package acme import ( "fmt" + "strings" ) // Errors types. @@ -27,21 +28,25 @@ type ProblemDetails struct { } func (p *ProblemDetails) Error() string { - msg := fmt.Sprintf("acme: error: %d", p.HTTPStatus) + var msg strings.Builder + + msg.WriteString(fmt.Sprintf("acme: error: %d", p.HTTPStatus)) + if p.Method != "" || p.URL != "" { - msg += fmt.Sprintf(" :: %s :: %s", p.Method, p.URL) + msg.WriteString(fmt.Sprintf(" :: %s :: %s", p.Method, p.URL)) } - msg += fmt.Sprintf(" :: %s :: %s", p.Type, p.Detail) + + msg.WriteString(fmt.Sprintf(" :: %s :: %s", p.Type, p.Detail)) for _, sub := range p.SubProblems { - msg += fmt.Sprintf(", problem: %q :: %s", sub.Type, sub.Detail) + msg.WriteString(fmt.Sprintf(", problem: %q :: %s", sub.Type, sub.Detail)) } if p.Instance != "" { - msg += ", url: " + p.Instance + msg.WriteString(", url: " + p.Instance) } - return msg + return msg.String() } // SubProblem a "subproblems". @@ -49,7 +54,7 @@ func (p *ProblemDetails) Error() string { type SubProblem struct { Type string `json:"type,omitempty"` Detail string `json:"detail,omitempty"` - Identifier Identifier `json:"identifier,omitempty"` + Identifier Identifier `json:"identifier"` } // NonceError represents the error which is returned diff --git a/certcrypto/crypto.go b/certcrypto/crypto.go index d6f53c3a1..00f0654b9 100644 --- a/certcrypto/crypto.go +++ b/certcrypto/crypto.go @@ -57,8 +57,10 @@ type DERCertificateBytes []byte // ParsePEMBundle parses a certificate bundle from top to bottom and returns // a slice of x509 certificates. This function will error if no certificates are found. func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) { - var certificates []*x509.Certificate - var certDERBlock *pem.Block + var ( + certificates []*x509.Certificate + certDERBlock *pem.Block + ) for { certDERBlock, bundle = pem.Decode(bundle) @@ -71,6 +73,7 @@ func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) { if err != nil { return nil, err } + certificates = append(certificates, cert) } } @@ -152,8 +155,11 @@ type CSROptions struct { } func CreateCSR(privateKey crypto.PrivateKey, opts CSROptions) ([]byte, error) { - var dnsNames []string - var ipAddresses []net.IP + var ( + dnsNames []string + ipAddresses []net.IP + ) + for _, altname := range opts.SAN { if ip := net.ParseIP(altname); ip != nil { ipAddresses = append(ipAddresses, ip) @@ -185,6 +191,7 @@ func PEMEncode(data any) []byte { func PEMBlock(data any) *pem.Block { var pemBlock *pem.Block + switch key := data.(type) { case *ecdsa.PrivateKey: keyBytes, _ := x509.MarshalECPrivateKey(key) @@ -265,6 +272,7 @@ func ExtractDomains(cert *x509.Certificate) []string { if sanDomain == cert.Subject.CommonName { continue } + domains = append(domains, sanDomain) } @@ -316,6 +324,7 @@ func GeneratePemCert(privateKey *rsa.PrivateKey, domain string, extensions []pki func generateDerCert(privateKey *rsa.PrivateKey, expiration time.Time, domain string, extensions []pkix.Extension) ([]byte, error) { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { return nil, err diff --git a/certcrypto/crypto_test.go b/certcrypto/crypto_test.go index f6178fadd..f5609fdf4 100644 --- a/certcrypto/crypto_test.go +++ b/certcrypto/crypto_test.go @@ -179,6 +179,7 @@ func TestParsePEMPrivateKey(t *testing.T) { // ignoring precomputed values. decoded, err := ParsePEMPrivateKey(pemPrivateKey) require.NoError(t, err) + decodedRsaPrivateKey := decoded.(*rsa.PrivateKey) require.True(t, decodedRsaPrivateKey.Equal(privateKey)) diff --git a/certificate/authorization.go b/certificate/authorization.go index c77bcbd5f..49f958776 100644 --- a/certificate/authorization.go +++ b/certificate/authorization.go @@ -29,6 +29,7 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz var responses []acme.Authorization failures := newObtainError() + for range len(order.Authorizations) { select { case res := <-resc: @@ -62,6 +63,7 @@ func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder, force boo } log.Infof("Deactivating auth: %s", authzURL) + if c.core.Authorizations.Deactivate(authzURL) != nil { log.Infof("Unable to deactivate the authorization: %s", authzURL) } diff --git a/certificate/certificates.go b/certificate/certificates.go index df51dc333..04904e794 100644 --- a/certificate/certificates.go +++ b/certificate/certificates.go @@ -198,6 +198,7 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) { log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", ")) failures := newObtainError() + cert, err := c.getForOrder(domains, order, request) if err != nil { for _, auth := range authz { @@ -295,6 +296,7 @@ func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, requ if privateKey == nil { var err error + privateKey, err = certcrypto.GeneratePrivateKey(c.options.KeyType) if err != nil { return nil, err @@ -490,6 +492,7 @@ type RenewOptions struct { // If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle. // // For private key reuse the PrivateKey property of the passed in Resource should be non-nil. +// // Deprecated: use RenewWithOptions instead. func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool, preferredChain string) (*Resource, error) { return c.RenewWithOptions(certRes, &RenewOptions{ @@ -722,6 +725,7 @@ func checkOrderStatus(order acme.ExtendedOrder) (bool, error) { // https://www.rfc-editor.org/rfc/rfc5280.html#section-7 func sanitizeDomain(domains []string) []string { var sanitizedDomains []string + for _, domain := range domains { sanitizedDomain, err := idna.ToASCII(domain) if err != nil { @@ -730,5 +734,6 @@ func sanitizeDomain(domains []string) []string { sanitizedDomains = append(sanitizedDomains, sanitizedDomain) } } + return sanitizedDomains } diff --git a/certificate/renewal.go b/certificate/renewal.go index 0a9059501..15e804745 100644 --- a/certificate/renewal.go +++ b/certificate/renewal.go @@ -85,6 +85,7 @@ func (c *Certifier) GetRenewalInfo(req RenewalInfoRequest) (*RenewalInfoResponse defer resp.Body.Close() var info RenewalInfoResponse + err = json.NewDecoder(resp.Body).Decode(&info) if err != nil { return nil, err diff --git a/challenge/challenges.go b/challenge/challenges.go index 39bf3bee2..f6d5cdb28 100644 --- a/challenge/challenges.go +++ b/challenge/challenges.go @@ -40,5 +40,6 @@ func GetTargetedDomain(authz acme.Authorization) string { if authz.Wildcard { return "*." + authz.Identifier.Value } + return authz.Identifier.Value } diff --git a/challenge/dns01/dns_challenge.go b/challenge/dns01/dns_challenge.go index 8594d2799..1d106d7b7 100644 --- a/challenge/dns01/dns_challenge.go +++ b/challenge/dns01/dns_challenge.go @@ -40,6 +40,7 @@ func CondOption(condition bool, opt ChallengeOption) ChallengeOption { return nil } } + return opt } @@ -118,6 +119,7 @@ func (c *Challenge) Solve(authz acme.Authorization) error { info := GetChallengeInfo(authz.Identifier.Value, keyAuth) var timeout, interval time.Duration + switch provider := c.provider.(type) { case challenge.ProviderTimeout: timeout, interval = provider.Timeout() @@ -134,6 +136,7 @@ func (c *Challenge) Solve(authz acme.Authorization) error { if !stop || errP != nil { log.Infof("[%s] acme: Waiting for DNS record propagation.", domain) } + return stop, errP }) if err != nil { @@ -141,6 +144,7 @@ func (c *Challenge) Solve(authz acme.Authorization) error { } chlng.KeyAuthorization = keyAuth + return c.validate(c.core, domain, chlng) } @@ -165,6 +169,7 @@ func (c *Challenge) Sequential() (bool, time.Duration) { if p, ok := c.provider.(sequential); ok { return ok, p.Sequential() } + return false, 0 } @@ -173,6 +178,7 @@ type sequential interface { } // GetRecord returns a DNS record which will fulfill the `dns-01` challenge. +// // Deprecated: use GetChallengeInfo instead. func GetRecord(domain, keyAuth string) (fqdn, value string) { info := GetChallengeInfo(domain, keyAuth) diff --git a/challenge/dns01/dns_challenge_manual_test.go b/challenge/dns01/dns_challenge_manual_test.go index e0a2dc93a..c183822bb 100644 --- a/challenge/dns01/dns_challenge_manual_test.go +++ b/challenge/dns01/dns_challenge_manual_test.go @@ -18,6 +18,7 @@ func TestDNSProviderManual(t *testing.T) { Build(t)) backupStdin := os.Stdin + defer func() { os.Stdin = backupStdin }() testCases := []struct { diff --git a/challenge/dns01/dns_challenge_test.go b/challenge/dns01/dns_challenge_test.go index 7c723497c..325f1656c 100644 --- a/challenge/dns01/dns_challenge_test.go +++ b/challenge/dns01/dns_challenge_test.go @@ -184,6 +184,7 @@ func TestChallenge_Solve(t *testing.T) { if test.preCheck != nil { options = append(options, WrapPreCheck(test.preCheck)) } + chlg := NewChallenge(core, test.validate, test.provider, options...) authz := acme.Authorization{ diff --git a/challenge/dns01/fqdn.go b/challenge/dns01/fqdn.go index 665804d8f..11ac3d0c2 100644 --- a/challenge/dns01/fqdn.go +++ b/challenge/dns01/fqdn.go @@ -19,6 +19,7 @@ func UnFqdn(name string) string { if n != 0 && name[n-1] == '.' { return name[:n-1] } + return name } diff --git a/challenge/dns01/mock_test.go b/challenge/dns01/mock_test.go index 535d79cda..5dcad3013 100644 --- a/challenge/dns01/mock_test.go +++ b/challenge/dns01/mock_test.go @@ -40,6 +40,7 @@ func mockResolver(t *testing.T, addr net.Addr) { require.NoError(t, err) originalDefaultNameserverPort := defaultNameserverPort + t.Cleanup(func() { defaultNameserverPort = originalDefaultNameserverPort }) @@ -47,6 +48,7 @@ func mockResolver(t *testing.T, addr net.Addr) { defaultNameserverPort = port originalResolver := net.DefaultResolver + t.Cleanup(func() { net.DefaultResolver = originalResolver }) @@ -70,6 +72,7 @@ func useAsNameserver(t *testing.T, addr net.Addr) { }) originalRecursiveNameservers := recursiveNameservers + t.Cleanup(func() { recursiveNameservers = originalRecursiveNameservers }) diff --git a/challenge/dns01/nameserver.go b/challenge/dns01/nameserver.go index bb6dc0841..554eb7cc2 100644 --- a/challenge/dns01/nameserver.go +++ b/challenge/dns01/nameserver.go @@ -81,6 +81,7 @@ func getNameservers(path string, defaults []string) []string { func ParseNameservers(servers []string) []string { var resolvers []string + for _, resolver := range servers { // ensure all servers have a port number if _, _, err := net.SplitHostPort(resolver); err != nil { @@ -89,6 +90,7 @@ func ParseNameservers(servers []string) []string { resolvers = append(resolvers, resolver) } } + return resolvers } @@ -132,6 +134,7 @@ func FindPrimaryNsByFqdnCustom(fqdn string, nameservers []string) (string, error if err != nil { return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err) } + return soa.primaryNs, nil } @@ -148,6 +151,7 @@ func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) { if err != nil { return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err) } + return soa.zone, nil } @@ -172,8 +176,10 @@ func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) } func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) { - var err error - var r *dns.Msg + var ( + err error + r *dns.Msg + ) for domain := range DomainsSeq(fqdn) { r, err = dnsQuery(domain, dns.TypeSOA, nameservers, true) @@ -229,9 +235,11 @@ func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) ( return nil, &DNSError{Message: "empty list of nameservers"} } - var r *dns.Msg - var err error - var errAll error + var ( + r *dns.Msg + err error + errAll error + ) for _, ns := range nameservers { r, err = sendDNSQuery(m, ns) @@ -264,6 +272,7 @@ func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg { func sendDNSQuery(m *dns.Msg, ns string) (*dns.Msg, error) { if ok, _ := strconv.ParseBool(os.Getenv("LEGO_EXPERIMENTAL_DNS_TCP_ONLY")); ok { tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout} + r, _, err := tcp.Exchange(m, ns) if err != nil { return r, &DNSError{Message: "DNS call error", MsgIn: m, NS: ns, Err: err} diff --git a/challenge/dns01/precheck.go b/challenge/dns01/precheck.go index e10efa33e..45e17e3ac 100644 --- a/challenge/dns01/precheck.go +++ b/challenge/dns01/precheck.go @@ -29,6 +29,7 @@ func WrapPreCheck(wrap WrapPreCheckFunc) ChallengeOption { } // DisableCompletePropagationRequirement obsolete. +// // Deprecated: use DisableAuthoritativeNssPropagationRequirement instead. func DisableCompletePropagationRequirement() ChallengeOption { return DisableAuthoritativeNssPropagationRequirement() @@ -140,9 +141,11 @@ func checkNameserversPropagation(fqdn, value string, nameservers []string, addPo var records []string var found bool + for _, rr := range r.Answer { if txt, ok := rr.(*dns.TXT); ok { record := strings.Join(txt.Txt, "") + records = append(records, record) if record == value { found = true diff --git a/challenge/http01/domain_matcher.go b/challenge/http01/domain_matcher.go index c31aeed6a..058d1a314 100644 --- a/challenge/http01/domain_matcher.go +++ b/challenge/http01/domain_matcher.go @@ -88,6 +88,7 @@ func (m *forwardedMatcher) matches(r *http.Request, domain string) bool { } host := fwds[0]["host"] + return matchDomain(host, domain) } @@ -99,6 +100,7 @@ func parseForwardedHeader(s string) (elements []map[string]string, err error) { inquote := false pos := 0 + l := len(s) for i := 0; i < l; i++ { r := rune(s[i]) @@ -110,6 +112,7 @@ func parseForwardedHeader(s string) (elements []map[string]string, err error) { pos = i inquote = false } + continue } @@ -118,6 +121,7 @@ func parseForwardedHeader(s string) (elements []map[string]string, err error) { if key == "" { return nil, fmt.Errorf("unexpected quoted string as pos %d", i) } + inquote = true pos = i + 1 @@ -137,6 +141,7 @@ func parseForwardedHeader(s string) (elements []map[string]string, err error) { val = s[pos:i] cur[key] = val } + elements = append(elements, cur) cur = make(map[string]string) key = "" @@ -159,11 +164,14 @@ func parseForwardedHeader(s string) (elements []map[string]string, err error) { if pos < len(s) { val = s[pos:] } + cur[key] = val } + if len(cur) > 0 { elements = append(elements, cur) } + return elements, nil } @@ -178,6 +186,7 @@ func skipWS(s string, i int) int { for isWS(rune(s[i+1])) { i++ } + return i } diff --git a/challenge/http01/http_challenge.go b/challenge/http01/http_challenge.go index 79dbfb4d0..a042979c2 100644 --- a/challenge/http01/http_challenge.go +++ b/challenge/http01/http_challenge.go @@ -74,6 +74,7 @@ func (c *Challenge) Solve(authz acme.Authorization) error { if err != nil { return fmt.Errorf("[%s] acme: error presenting token: %w", domain, err) } + defer func() { err := c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth) if err != nil { @@ -86,5 +87,6 @@ func (c *Challenge) Solve(authz acme.Authorization) error { } chlng.KeyAuthorization = keyAuth + return c.validate(c.core, domain, chlng) } diff --git a/challenge/http01/http_challenge_server.go b/challenge/http01/http_challenge_server.go index 009271cec..ab962917e 100644 --- a/challenge/http01/http_challenge_server.go +++ b/challenge/http01/http_challenge_server.go @@ -44,6 +44,7 @@ func NewUnixProviderServer(socketPath string, mode fs.FileMode) *ProviderServer // Present starts a web server and makes the token available at `ChallengePath(token)` for web requests. func (s *ProviderServer) Present(domain, token, keyAuth string) error { var err error + s.listener, err = net.Listen(s.network, s.GetAddress()) if err != nil { return fmt.Errorf("could not start HTTP server for challenge: %w", err) @@ -120,6 +121,7 @@ func (s *ProviderServer) serve(domain, token, keyAuth string) { } log.Infof("[%s] Served key authentication", domain) + return } diff --git a/challenge/http01/http_challenge_test.go b/challenge/http01/http_challenge_test.go index 1b322491a..06c555e42 100644 --- a/challenge/http01/http_challenge_test.go +++ b/challenge/http01/http_challenge_test.go @@ -88,6 +88,7 @@ func TestChallenge(t *testing.T) { if err != nil { return err } + bodyStr := string(body) if bodyStr != chlng.KeyAuthorization { @@ -157,6 +158,7 @@ func TestChallengeUnix(t *testing.T) { if err != nil { return err } + bodyStr := string(body) if bodyStr != chlng.KeyAuthorization { @@ -224,6 +226,7 @@ func (h *testProxyHeader) update(r *http.Request) { if h == nil || len(h.values) == 0 { return } + if h.name == "Host" { r.Host = h.values[0] } else if h.name != "" { @@ -385,6 +388,7 @@ func testServeWithProxy(t *testing.T, header, extra *testProxyHeader, expectErro if err != nil { return err } + header.update(req) extra.update(req) @@ -402,6 +406,7 @@ func testServeWithProxy(t *testing.T, header, extra *testProxyHeader, expectErro if err != nil { return err } + bodyStr := string(body) if bodyStr != chlng.KeyAuthorization { diff --git a/challenge/resolver/errors.go b/challenge/resolver/errors.go index 94ccbd76a..6a859922c 100644 --- a/challenge/resolver/errors.go +++ b/challenge/resolver/errors.go @@ -16,10 +16,12 @@ func (e obtainError) Error() string { for domain := range e { domains = append(domains, domain) } + sort.Strings(domains) for _, domain := range domains { _, _ = fmt.Fprintf(buffer, "[%s] %s\n", domain, e[domain]) } + return buffer.String() } diff --git a/challenge/resolver/prober.go b/challenge/resolver/prober.go index 021facbb5..aac1016d8 100644 --- a/challenge/resolver/prober.go +++ b/challenge/resolver/prober.go @@ -50,11 +50,14 @@ func NewProber(solverManager *SolverManager) *Prober { func (p *Prober) Solve(authorizations []acme.Authorization) error { failures := make(obtainError) - var authSolvers []*selectedAuthSolver - var authSolversSequential []*selectedAuthSolver + var ( + authSolvers []*selectedAuthSolver + authSolversSequential []*selectedAuthSolver + ) // Loop through the resources, basically through the domains. // First pass just selects a solver for each authz. + for _, authz := range authorizations { domain := challenge.GetTargetedDomain(authz) if authz.Status == acme.StatusValid { @@ -90,6 +93,7 @@ func (p *Prober) Solve(authorizations []acme.Authorization) error { if len(failures) > 0 { return failures } + return nil } @@ -102,7 +106,9 @@ func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) { err := solvr.PreSolve(authSolver.authz) if err != nil { failures[domain] = err + cleanUp(authSolver.solver, authSolver.authz) + continue } } @@ -111,7 +117,9 @@ func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) { err := authSolver.solver.Solve(authSolver.authz) if err != nil { failures[domain] = err + cleanUp(authSolver.solver, authSolver.authz) + continue } @@ -149,6 +157,7 @@ func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) { // Finally solve all challenges for real for _, authSolver := range authSolvers { authz := authSolver.authz + domain := challenge.GetTargetedDomain(authz) if failures[domain] != nil { // already failed in previous loop @@ -165,6 +174,7 @@ func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) { func cleanUp(solvr solver, authz acme.Authorization) { if solvr, ok := solvr.(cleanup); ok { domain := challenge.GetTargetedDomain(authz) + err := solvr.CleanUp(authz) if err != nil { log.Warnf("[%s] acme: cleaning up failed: %v ", domain, err) diff --git a/challenge/resolver/solver_manager.go b/challenge/resolver/solver_manager.go index 07687aaaf..48d9194b9 100644 --- a/challenge/resolver/solver_manager.go +++ b/challenge/resolver/solver_manager.go @@ -71,6 +71,7 @@ func (c *SolverManager) chooseSolver(authz acme.Authorization) solver { log.Infof("[%s] acme: use %s solver", domain, chlg.Type) return solvr } + log.Infof("[%s] acme: Could not find solver for: %s", domain, chlg.Type) } @@ -101,6 +102,7 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error { // https://github.com/letsencrypt/boulder/blob/master/docs/acme-divergences.md#section-82 ra = 5 } + initialInterval := time.Duration(ra) * time.Second ctx := context.Background() @@ -162,6 +164,7 @@ func checkAuthorizationStatus(authz acme.Authorization) (bool, error) { return false, fmt.Errorf("invalid authorization: %w", chlg.Err()) } } + return false, errors.New("invalid authorization") default: return false, fmt.Errorf("the server returned an unexpected authorization status: %s", authz.Status) diff --git a/challenge/resolver/solver_manager_test.go b/challenge/resolver/solver_manager_test.go index f2875912a..77149c73a 100644 --- a/challenge/resolver/solver_manager_test.go +++ b/challenge/resolver/solver_manager_test.go @@ -260,6 +260,7 @@ func validateNoBody(privateKey *rsa.PrivateKey, r *http.Request) error { } sigAlgs := []jose.SignatureAlgorithm{jose.RS256} + jws, err := jose.ParseSigned(string(reqBody), sigAlgs) if err != nil { return err @@ -276,5 +277,6 @@ func validateNoBody(privateKey *rsa.PrivateKey, r *http.Request) error { if bodyStr := string(body); bodyStr != "{}" && bodyStr != "" { return fmt.Errorf(`expected JWS POST body "{}" or "", got %q`, bodyStr) } + return nil } diff --git a/challenge/tlsalpn01/tls_alpn_challenge.go b/challenge/tlsalpn01/tls_alpn_challenge.go index 559e1f905..d8e939106 100644 --- a/challenge/tlsalpn01/tls_alpn_challenge.go +++ b/challenge/tlsalpn01/tls_alpn_challenge.go @@ -80,6 +80,7 @@ func (c *Challenge) Solve(authz acme.Authorization) error { if err != nil { return fmt.Errorf("[%s] acme: error presenting token: %w", challenge.GetTargetedDomain(authz), err) } + defer func() { err := c.provider.CleanUp(domain, chlng.Token, keyAuth) if err != nil { @@ -92,6 +93,7 @@ func (c *Challenge) Solve(authz acme.Authorization) error { } chlng.KeyAuthorization = keyAuth + return c.validate(c.core, domain, chlng) } diff --git a/challenge/tlsalpn01/tls_alpn_challenge_test.go b/challenge/tlsalpn01/tls_alpn_challenge_test.go index eec2cb152..59c2d61bc 100644 --- a/challenge/tlsalpn01/tls_alpn_challenge_test.go +++ b/challenge/tlsalpn01/tls_alpn_challenge_test.go @@ -42,6 +42,7 @@ func TestChallenge(t *testing.T) { assert.NotEmpty(t, remoteCert.Extensions, "Expected the challenge certificate to contain extensions") idx := -1 + for i, ext := range remoteCert.Extensions { if idPeAcmeIdentifierV1.Equal(ext.Id) { idx = i @@ -145,18 +146,24 @@ func TestChallengeIPaddress(t *testing.T) { assert.True(t, net.ParseIP("127.0.0.1").Equal(remoteCert.IPAddresses[0]), "challenge certificate IPAddress ") assert.NotEmpty(t, remoteCert.Extensions, "Expected the challenge certificate to contain extensions") - var foundAcmeIdentifier bool - var extValue []byte + var ( + foundAcmeIdentifier bool + extValue []byte + ) + for _, ext := range remoteCert.Extensions { if idPeAcmeIdentifierV1.Equal(ext.Id) { assert.True(t, ext.Critical, "Expected the challenge certificate id-pe-acmeIdentifier extension to be marked as critical") + foundAcmeIdentifier = true extValue = ext.Value + break } } require.True(t, foundAcmeIdentifier, "Expected the challenge certificate to contain an extension with the id-pe-acmeIdentifier id,") + zBytes := sha256.Sum256([]byte(chlng.KeyAuthorization)) value, err := asn1.Marshal(zBytes[:sha256.Size]) require.NoError(t, err, "Expected marshaling of the keyAuth to return no error") diff --git a/cmd/accounts_storage.go b/cmd/accounts_storage.go index c234c0060..1dbdfb84b 100644 --- a/cmd/accounts_storage.go +++ b/cmd/accounts_storage.go @@ -96,6 +96,7 @@ func (s *AccountsStorage) ExistsAccountFilePath() bool { } else if err != nil { log.Fatal(err) } + return true } @@ -127,6 +128,7 @@ func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account { } var account Account + err = json.Unmarshal(fileBytes, &account) if err != nil { log.Fatalf("Could not parse file for account %s: %v", s.userID, err) @@ -141,6 +143,7 @@ func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account { } account.Registration = reg + err = s.Save(&account) if err != nil { log.Fatalf("Could not save account for %s. Registration is nil: %#v", s.userID, err) @@ -163,6 +166,7 @@ func (s *AccountsStorage) GetPrivateKey(keyType certcrypto.KeyType) crypto.Priva } log.Printf("Saved key to %s", accKeyPath) + return privateKey } @@ -193,6 +197,7 @@ func generatePrivateKey(file string, keyType certcrypto.KeyType) (crypto.Private defer certOut.Close() pemKey := certcrypto.PEMBlock(privateKey) + err = pem.Encode(certOut, pemKey) if err != nil { return nil, err @@ -211,6 +216,7 @@ func loadPrivateKey(file string) (crypto.PrivateKey, error) { if err != nil { return nil, err } + return privateKey, nil } @@ -229,5 +235,6 @@ func tryRecoverRegistration(ctx *cli.Context, privateKey crypto.PrivateKey) (*re if err != nil { return nil, err } + return reg, nil } diff --git a/cmd/certs_storage.go b/cmd/certs_storage.go index e0b1a387a..25ef58075 100644 --- a/cmd/certs_storage.go +++ b/cmd/certs_storage.go @@ -158,6 +158,7 @@ func (s *CertificatesStorage) ExistsFile(domain, extension string) bool { } else if err != nil { log.Fatal(err) } + return true } @@ -283,6 +284,7 @@ func getCertificateChain(certRes *certificate.Resource) ([]*x509.Certificate, er } var certChain []*x509.Certificate + for chainCertPemBlock != nil { chainCert, err := x509.ParseCertificate(chainCertPemBlock.Bytes) if err != nil { @@ -298,6 +300,7 @@ func getCertificateChain(certRes *certificate.Resource) ([]*x509.Certificate, er func getPFXEncoder(pfxFormat string) (*pkcs12.Encoder, error) { var encoder *pkcs12.Encoder + switch pfxFormat { case "SHA256": encoder = pkcs12.Modern2023 @@ -318,5 +321,6 @@ func sanitizedDomain(domain string) string { if err != nil { log.Fatal(err) } + return safe } diff --git a/cmd/cmd_list.go b/cmd/cmd_list.go index bf7b232da..864b85977 100644 --- a/cmd/cmd_list.go +++ b/cmd/cmd_list.go @@ -67,6 +67,7 @@ func listCertificates(ctx *cli.Context) error { if !names { fmt.Println("No certificates found.") } + return nil } @@ -122,6 +123,7 @@ func listAccount(ctx *cli.Context) error { } fmt.Println("Found the following accounts:") + for _, filename := range matches { data, err := os.ReadFile(filename) if err != nil { @@ -129,6 +131,7 @@ func listAccount(ctx *cli.Context) error { } var account Account + err = json.Unmarshal(data, &account) if err != nil { return err diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index afedee0b6..99bc5ebbd 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -39,16 +39,20 @@ func createRenew() *cli.Command { Before: func(ctx *cli.Context) error { // we require either domains or csr, but not both hasDomains := len(ctx.StringSlice(flgDomains)) > 0 + hasCsr := ctx.String(flgCSR) != "" if hasDomains && hasCsr { log.Fatalf("Please specify either --%s/-d or --%s/-c, but not both", flgDomains, flgCSR) } + if !hasDomains && !hasCsr { log.Fatalf("Please specify --%s/-d (or --%s/-c if you already have a CSR)", flgDomains, flgCSR) } + if ctx.Bool(flgForceCertDomains) && hasCsr { log.Fatalf("--%s only works with --%s/-d, --%s/-c doesn't support this option.", flgForceCertDomains, flgDomains, flgCSR) } + return nil }, Flags: []cli.Flag{ @@ -165,8 +169,10 @@ func renewForDomains(ctx *cli.Context, account *Account, keyType certcrypto.KeyT cert := certificates[0] - var ariRenewalTime *time.Time - var replacesCertID string + var ( + ariRenewalTime *time.Time + replacesCertID string + ) var client *lego.Client @@ -208,6 +214,7 @@ func renewForDomains(ctx *cli.Context, account *Account, keyType certcrypto.KeyT log.Infof("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours())) var privateKey crypto.PrivateKey + if ctx.Bool(flgReuseKey) { keyBytes, errR := certsStorage.ReadFile(domain, keyExt) if errR != nil { @@ -225,6 +232,7 @@ func renewForDomains(ctx *cli.Context, account *Account, keyType certcrypto.KeyT if !isatty.IsTerminal(os.Stdout.Fd()) && !ctx.Bool(flgNoRandomSleep) { // https://github.com/certbot/certbot/blob/284023a1b7672be2bd4018dd7623b3b92197d4b0/certbot/certbot/_internal/renewal.py#L472 const jitter = 8 * time.Minute + rnd := rand.New(rand.NewSource(time.Now().UnixNano())) sleepTime := time.Duration(rnd.Int63n(int64(jitter))) @@ -288,8 +296,10 @@ func renewForCSR(ctx *cli.Context, account *Account, keyType certcrypto.KeyType, cert := certificates[0] - var ariRenewalTime *time.Time - var replacesCertID string + var ( + ariRenewalTime *time.Time + replacesCertID string + ) var client *lego.Client @@ -408,16 +418,20 @@ func getARIRenewalTime(ctx *cli.Context, cert *x509.Certificate, domain string, log.Warnf("[%s] acme: %v", domain, err) return nil } + log.Warnf("[%s] acme: calling renewal info endpoint: %v", domain, err) + return nil } now := time.Now().UTC() + renewalTime := renewalInfo.ShouldRenewAt(now, ctx.Duration(flgARIWaitToRenewDuration)) if renewalTime == nil { log.Infof("[%s] acme: renewalInfo endpoint indicates that renewal is not needed", domain) return nil } + log.Infof("[%s] acme: renewalInfo endpoint indicates that renewal is needed", domain) if renewalInfo.ExplanationURL != "" { diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index 905fc6bb2..16814b4de 100644 --- a/cmd/cmd_run.go +++ b/cmd/cmd_run.go @@ -35,13 +35,16 @@ func createRun() *cli.Command { Before: func(ctx *cli.Context) error { // we require either domains or csr, but not both hasDomains := len(ctx.StringSlice(flgDomains)) > 0 + hasCsr := ctx.String(flgCSR) != "" if hasDomains && hasCsr { log.Fatal("Please specify either --domains/-d or --csr/-c, but not both") } + if !hasDomains && !hasCsr { log.Fatal("Please specify --domains/-d (or --csr/-c if you already have a CSR)") } + return nil }, Action: run, @@ -155,10 +158,12 @@ func handleTOS(ctx *cli.Context, client *lego.Client) bool { } reader := bufio.NewReader(os.Stdin) + log.Printf("Please review the TOS at %s", client.GetToSURL()) for { fmt.Println("Do you accept the TOS? Y/n") + text, err := reader.ReadString('\n') if err != nil { log.Fatalf("Could not read from console: %v", err) @@ -219,6 +224,7 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso if ctx.IsSet(flgPrivateKey) { var err error + request.PrivateKey, err = loadPrivateKey(ctx.String(flgPrivateKey)) if err != nil { return nil, fmt.Errorf("load private key: %w", err) @@ -247,6 +253,7 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso if ctx.IsSet(flgPrivateKey) { var err error + request.PrivateKey, err = loadPrivateKey(ctx.String(flgPrivateKey)) if err != nil { return nil, fmt.Errorf("load private key: %w", err) diff --git a/cmd/flags.go b/cmd/flags.go index 3372de4ff..c7e8371b6 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -258,5 +258,6 @@ func getTime(ctx *cli.Context, name string) time.Time { if value == nil { return time.Time{} } + return *value } diff --git a/cmd/hook.go b/cmd/hook.go index c1de29c58..7883108b6 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -34,6 +34,7 @@ func launchHook(hook string, timeout time.Duration, meta map[string]string) erro parts := strings.Fields(hook) cmd := exec.CommandContext(ctxCmd, parts[0], parts[1:]...) + cmd.Env = append(os.Environ(), metaToEnv(meta)...) stdout, err := cmd.StdoutPipe() @@ -50,6 +51,7 @@ func launchHook(hook string, timeout time.Duration, meta map[string]string) erro go func() { <-ctxCmd.Done() + if ctxCmd.Err() != nil { _ = cmd.Process.Kill() _ = stdout.Close() diff --git a/cmd/lego/main.go b/cmd/lego/main.go index 61a3d532a..c301a51f1 100644 --- a/cmd/lego/main.go +++ b/cmd/lego/main.go @@ -26,6 +26,7 @@ func main() { } var defaultPath string + cwd, err := os.Getwd() if err == nil { defaultPath = filepath.Join(cwd, ".lego") diff --git a/cmd/setup.go b/cmd/setup.go index 72f881755..4d17f2e27 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -114,6 +114,7 @@ func getKeyType(ctx *cli.Context) certcrypto.KeyType { } log.Fatalf("Unsupported KeyType: %s", keyType) + return "" } @@ -122,6 +123,7 @@ func getEmail(ctx *cli.Context) string { if email == "" { log.Fatalf("You have to pass an account (email address) to the program using --%s or -m", flgEmail) } + return email } @@ -135,6 +137,7 @@ func createNonExistingFolder(path string) error { } else if err != nil { return err } + return nil } @@ -143,10 +146,12 @@ func readCSRFile(filename string) (*x509.CertificateRequest, error) { if err != nil { return nil, err } + raw := bytes // see if we can find a PEM-encoded CSR var p *pem.Block + rest := bytes for { // decode a PEM block diff --git a/cmd/setup_challenges.go b/cmd/setup_challenges.go index c923fa004..6968c7ba3 100644 --- a/cmd/setup_challenges.go +++ b/cmd/setup_challenges.go @@ -54,18 +54,21 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider { if err != nil { log.Fatal(err) } + return ps case ctx.IsSet(flgHTTPMemcachedHost): ps, err := memcached.NewMemcachedProvider(ctx.StringSlice(flgHTTPMemcachedHost)) if err != nil { log.Fatal(err) } + return ps case ctx.IsSet(flgHTTPS3Bucket): ps, err := s3.NewHTTPProvider(ctx.String(flgHTTPS3Bucket)) if err != nil { log.Fatal(err) } + return ps case ctx.IsSet(flgHTTPPort): iface := ctx.String(flgHTTPPort) @@ -82,12 +85,14 @@ func setupHTTPProvider(ctx *cli.Context) challenge.Provider { if header := ctx.String(flgHTTPProxyHeader); header != "" { srv.SetProxyHeader(header) } + return srv case ctx.Bool(flgHTTP): srv := http01.NewProviderServer("", "") if header := ctx.String(flgHTTPProxyHeader); header != "" { srv.SetProxyHeader(header) } + return srv default: log.Fatal("Invalid HTTP challenge options.") diff --git a/e2e/challenges_test.go b/e2e/challenges_test.go index 174ac2ed9..be1d23131 100644 --- a/e2e/challenges_test.go +++ b/e2e/challenges_test.go @@ -205,6 +205,7 @@ func TestChallengeTLS_Run_Revoke_Non_ASCII(t *testing.T) { func TestChallengeHTTP_Client_Obtain(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) + defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -222,6 +223,7 @@ func TestChallengeHTTP_Client_Obtain(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) + user.registration = reg request := certificate.ObtainRequest{ @@ -243,6 +245,7 @@ func TestChallengeHTTP_Client_Obtain(t *testing.T) { func TestChallengeHTTP_Client_Obtain_profile(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) + defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -260,6 +263,7 @@ func TestChallengeHTTP_Client_Obtain_profile(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) + user.registration = reg request := certificate.ObtainRequest{ @@ -282,6 +286,7 @@ func TestChallengeHTTP_Client_Obtain_profile(t *testing.T) { func TestChallengeHTTP_Client_Obtain_emails_csr(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) + defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -299,6 +304,7 @@ func TestChallengeHTTP_Client_Obtain_emails_csr(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) + user.registration = reg request := certificate.ObtainRequest{ @@ -321,6 +327,7 @@ func TestChallengeHTTP_Client_Obtain_emails_csr(t *testing.T) { func TestChallengeHTTP_Client_Obtain_notBefore_notAfter(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) + defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -338,6 +345,7 @@ func TestChallengeHTTP_Client_Obtain_notBefore_notAfter(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) + user.registration = reg now := time.Now().UTC() @@ -368,6 +376,7 @@ func TestChallengeHTTP_Client_Obtain_notBefore_notAfter(t *testing.T) { func TestChallengeHTTP_Client_Registration_QueryRegistration(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) + defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -385,6 +394,7 @@ func TestChallengeHTTP_Client_Registration_QueryRegistration(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) + user.registration = reg resource, err := client.Registration.QueryRegistration() @@ -400,6 +410,7 @@ func TestChallengeHTTP_Client_Registration_QueryRegistration(t *testing.T) { func TestChallengeTLS_Client_Obtain(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) + defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -417,6 +428,7 @@ func TestChallengeTLS_Client_Obtain(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) + user.registration = reg // https://github.com/letsencrypt/pebble/issues/285 @@ -443,6 +455,7 @@ func TestChallengeTLS_Client_Obtain(t *testing.T) { func TestChallengeTLS_Client_ObtainForCSR(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) + defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -460,6 +473,7 @@ func TestChallengeTLS_Client_ObtainForCSR(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) + user.registration = reg csr, err := x509.ParseCertificateRequest(createTestCSR(t)) @@ -483,6 +497,7 @@ func TestChallengeTLS_Client_ObtainForCSR(t *testing.T) { func TestChallengeTLS_Client_ObtainForCSR_profile(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) + defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -500,6 +515,7 @@ func TestChallengeTLS_Client_ObtainForCSR_profile(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) + user.registration = reg csr, err := x509.ParseCertificateRequest(createTestCSR(t)) @@ -524,6 +540,7 @@ func TestChallengeTLS_Client_ObtainForCSR_profile(t *testing.T) { func TestRegistrar_UpdateAccount(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "./fixtures/certs/pebble.minica.pem") require.NoError(t, err) + defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) diff --git a/e2e/dnschallenge/dns_challenges_test.go b/e2e/dnschallenge/dns_challenges_test.go index a44c353b3..509b57bb1 100644 --- a/e2e/dnschallenge/dns_challenges_test.go +++ b/e2e/dnschallenge/dns_challenges_test.go @@ -75,10 +75,12 @@ func TestChallengeDNS_Run(t *testing.T) { func TestChallengeDNS_Client_Obtain(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "../fixtures/certs/pebble.minica.pem") require.NoError(t, err) + defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() err = os.Setenv("EXEC_PATH", "../fixtures/update-dns.sh") require.NoError(t, err) + defer func() { _ = os.Unsetenv("EXEC_PATH") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -101,6 +103,7 @@ func TestChallengeDNS_Client_Obtain(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) + user.registration = reg domains := []string{testDomain2, testDomain1} @@ -129,10 +132,12 @@ func TestChallengeDNS_Client_Obtain(t *testing.T) { func TestChallengeDNS_Client_Obtain_profile(t *testing.T) { err := os.Setenv("LEGO_CA_CERTIFICATES", "../fixtures/certs/pebble.minica.pem") require.NoError(t, err) + defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }() err = os.Setenv("EXEC_PATH", "../fixtures/update-dns.sh") require.NoError(t, err) + defer func() { _ = os.Unsetenv("EXEC_PATH") }() privateKey, err := rsa.GenerateKey(rand.Reader, 2048) @@ -155,6 +160,7 @@ func TestChallengeDNS_Client_Obtain_profile(t *testing.T) { reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) require.NoError(t, err) + user.registration = reg domains := []string{testDomain2, testDomain1} diff --git a/e2e/loader/loader.go b/e2e/loader/loader.go index b5ac9cef8..3e63302a3 100644 --- a/e2e/loader/loader.go +++ b/e2e/loader/loader.go @@ -43,12 +43,14 @@ func (l *EnvLoader) MainTest(m *testing.M) int { if _, e2e := os.LookupEnv("LEGO_E2E_TESTS"); !e2e { fmt.Fprintln(os.Stderr, "skipping test: e2e tests are disabled. (no 'LEGO_E2E_TESTS' env var)") fmt.Println("PASS") + return 0 } if _, err := exec.LookPath("git"); err != nil { fmt.Fprintln(os.Stderr, "skipping because git command not found") fmt.Println("PASS") + return 0 } @@ -56,6 +58,7 @@ func (l *EnvLoader) MainTest(m *testing.M) int { if _, err := exec.LookPath(cmdNamePebble); err != nil { fmt.Fprintln(os.Stderr, "skipping because pebble binary not found") fmt.Println("PASS") + return 0 } } @@ -64,6 +67,7 @@ func (l *EnvLoader) MainTest(m *testing.M) int { if _, err := exec.LookPath(cmdNameChallSrv); err != nil { fmt.Fprintln(os.Stderr, "skipping because challtestsrv binary not found") fmt.Println("PASS") + return 0 } } @@ -76,6 +80,7 @@ func (l *EnvLoader) MainTest(m *testing.M) int { legoBinary, tearDown, err := buildLego() defer tearDown() + if err != nil { fmt.Fprintln(os.Stderr, err) return 1 @@ -136,6 +141,7 @@ func (l *EnvLoader) launchPebble() func() { } pebble, outPebble := l.cmdPebble() + go func() { err := pebble.Run() if err != nil { @@ -148,6 +154,7 @@ func (l *EnvLoader) launchPebble() func() { if err != nil { fmt.Println(err) } + fmt.Println(outPebble.String()) } } @@ -160,11 +167,13 @@ func (l *EnvLoader) cmdPebble() (*exec.Cmd, *bytes.Buffer) { if err != nil { panic(err) } + cmd.Dir = dir fmt.Printf("$ %s\n", strings.Join(cmd.Args, " ")) var b bytes.Buffer + cmd.Stdout = &b cmd.Stderr = &b @@ -173,6 +182,7 @@ func (l *EnvLoader) cmdPebble() (*exec.Cmd, *bytes.Buffer) { func pebbleHealthCheck(options *CmdOption) { client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}} + err := wait.For("pebble", 10*time.Second, 500*time.Millisecond, func() (bool, error) { resp, err := client.Get(options.HealthCheckURL) if err != nil { @@ -196,6 +206,7 @@ func (l *EnvLoader) launchChallSrv() func() { } challtestsrv, outChalSrv := l.cmdChallSrv() + go func() { err := challtestsrv.Run() if err != nil { @@ -208,6 +219,7 @@ func (l *EnvLoader) launchChallSrv() func() { if err != nil { fmt.Println(err) } + fmt.Println(outChalSrv.String()) } } @@ -218,6 +230,7 @@ func (l *EnvLoader) cmdChallSrv() (*exec.Cmd, *bytes.Buffer) { fmt.Printf("$ %s\n", strings.Join(cmd.Args, " ")) var b bytes.Buffer + cmd.Stdout = &b cmd.Stderr = &b @@ -229,6 +242,7 @@ func buildLego() (string, func(), error) { if err != nil { return "", func() {}, err } + defer func() { _ = os.Chdir(here) }() buildPath, err := os.MkdirTemp("", "lego_test") @@ -262,6 +276,7 @@ func buildLego() (string, func(), error) { return binary, func() { _ = os.RemoveAll(buildPath) + CleanLegoFiles() }, nil } @@ -283,6 +298,7 @@ func build(binary string) error { if err != nil { return err } + cmd := exec.Command(toolPath, "build", "-o", binary) output, err := cmd.CombinedOutput() @@ -334,6 +350,7 @@ func goTool() (string, error) { func CleanLegoFiles() { cmd := exec.Command("rm", "-rf", ".lego") fmt.Printf("$ %s\n", strings.Join(cmd.Args, " ")) + output, err := cmd.CombinedOutput() if err != nil { fmt.Println(string(output)) diff --git a/internal/clihelp/generator.go b/internal/clihelp/generator.go index 2d256b4d7..fcabde015 100644 --- a/internal/clihelp/generator.go +++ b/internal/clihelp/generator.go @@ -50,6 +50,7 @@ func generate() error { // collect output of various help pages var help []commandHelp + for _, args := range [][]string{ {"lego", "help"}, {"lego", "help", "run"}, @@ -72,7 +73,9 @@ func generate() error { } err = outputTpl.Execute(f, help) + defer func() { _ = f.Close() }() + if err != nil { return fmt.Errorf("failed to write cli_help.toml: %w", err) } @@ -98,9 +101,11 @@ func createStubApp() *cli.App { func run(app *cli.App, args []string) (h commandHelp, err error) { w := app.Writer + defer func() { app.Writer = w }() var buf bytes.Buffer + app.Writer = &buf if err := app.Run(args); err != nil { diff --git a/internal/dns/docs/generator.go b/internal/dns/docs/generator.go index 676f65f5b..c7f9ef8c7 100644 --- a/internal/dns/docs/generator.go +++ b/internal/dns/docs/generator.go @@ -116,6 +116,7 @@ func generateCLIHelp(models *descriptors.Providers) error { defer func() { _ = file.Close() }() b := &bytes.Buffer{} + err = template.Must( template.New(filepath.Base(cliTemplate)).Funcs(map[string]any{ "safe": func(src string) string { @@ -134,6 +135,7 @@ func generateCLIHelp(models *descriptors.Providers) error { } _, err = file.Write(source) + return err } @@ -161,6 +163,7 @@ func generateReadMe(models *descriptors.Providers) error { if err = tpl.Execute(buffer, providers); err != nil { return err } + skip = true } @@ -198,8 +201,10 @@ func orderProviders(models *descriptors.Providers) [][]descriptors.Provider { return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name)) }) - var matrix [][]descriptors.Provider - var row []descriptors.Provider + var ( + matrix [][]descriptors.Provider + row []descriptors.Provider + ) for i, p := range providers { switch { @@ -212,6 +217,7 @@ func orderProviders(models *descriptors.Providers) [][]descriptors.Provider { for j := len(row); j < nbCol; j++ { row = append(row, descriptors.Provider{}) } + matrix = append(matrix, row) default: @@ -223,6 +229,7 @@ func orderProviders(models *descriptors.Providers) [][]descriptors.Provider { for j := len(row); j < nbCol; j++ { row = append(row, descriptors.Provider{}) } + matrix = append(matrix, row) } diff --git a/internal/dns/providers/generator.go b/internal/dns/providers/generator.go index 8f133a765..df3f8a2e6 100644 --- a/internal/dns/providers/generator.go +++ b/internal/dns/providers/generator.go @@ -46,6 +46,7 @@ func generate() error { defer func() { _ = file.Close() }() b := &bytes.Buffer{} + err = template.Must( template.New("").Funcs(map[string]any{ "cleanName": func(src string) string { diff --git a/internal/releaser/releaser.go b/internal/releaser/releaser.go index 6047c427c..57b463933 100644 --- a/internal/releaser/releaser.go +++ b/internal/releaser/releaser.go @@ -108,6 +108,7 @@ func detach(_ *cli.Context) error { func readCurrentVersion(filename string) (*hcversion.Version, error) { fset := token.NewFileSet() + file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) if err != nil { return nil, err @@ -141,6 +142,7 @@ func (v visitor) Visit(n ast.Node) ast.Visitor { if !ok { continue } + if len(valueSpec.Names) != 1 || len(valueSpec.Values) != 1 { continue } @@ -149,6 +151,7 @@ func (v visitor) Visit(n ast.Node) ast.Visitor { if !ok { continue } + if va.Kind != token.STRING { continue } @@ -164,6 +167,7 @@ func (v visitor) Visit(n ast.Node) ast.Visitor { default: // noop } + return v } diff --git a/platform/config/env/env.go b/platform/config/env/env.go index b74a65cd9..33a0d6caa 100644 --- a/platform/config/env/env.go +++ b/platform/config/env/env.go @@ -16,11 +16,13 @@ func Get(names ...string) (map[string]string, error) { values := map[string]string{} var missingEnvVars []string + for _, envVar := range names { value := GetOrFile(envVar) if value == "" { missingEnvVars = append(missingEnvVars, envVar) } + values[envVar] = value } @@ -58,6 +60,7 @@ func GetWithFallback(groups ...[]string) (map[string]string, error) { values := map[string]string{} var missingEnvVars []string + for _, names := range groups { if len(names) == 0 { return nil, errors.New("undefined environment variable names") @@ -68,6 +71,7 @@ func GetWithFallback(groups ...[]string) (map[string]string, error) { missingEnvVars = append(missingEnvVars, envVar) continue } + values[envVar] = value } @@ -148,6 +152,7 @@ func GetOrFile(envVar string) string { } fileVar := envVar + "_FILE" + fileVarValue := os.Getenv(fileVar) if fileVarValue == "" { return envVarValue diff --git a/platform/tester/api.go b/platform/tester/api.go index 410fb1401..8343b487f 100644 --- a/platform/tester/api.go +++ b/platform/tester/api.go @@ -42,6 +42,7 @@ func WriteJSONResponse(w http.ResponseWriter, body any) error { } w.Header().Set("Content-Type", "application/json") + if _, err := w.Write(bs); err != nil { return err } diff --git a/platform/tester/env.go b/platform/tester/env.go index 26788be3b..a12c32ef8 100644 --- a/platform/tester/env.go +++ b/platform/tester/env.go @@ -21,6 +21,7 @@ type EnvTest struct { // NewEnvTest Creates an EnvTest. func NewEnvTest(keys ...string) *EnvTest { values := make(map[string]string) + for _, key := range keys { value := os.Getenv(key) if value != "" { @@ -39,6 +40,7 @@ func NewEnvTest(keys ...string) *EnvTest { func (e *EnvTest) WithDomain(key string) *EnvTest { e.domainKey = key e.domain = os.Getenv(key) + return e } diff --git a/platform/tester/env_test.go b/platform/tester/env_test.go index d5084056f..4d9e4e7d1 100644 --- a/platform/tester/env_test.go +++ b/platform/tester/env_test.go @@ -18,6 +18,7 @@ const ( func TestMain(m *testing.M) { exitCode := m.Run() + clearEnv() os.Exit(exitCode) } @@ -39,6 +40,7 @@ func clearEnv() { os.Unsetenv(strings.Split(key, "=")[0]) } } + os.Unsetenv("EXTRA_LEGO_TEST") } @@ -325,6 +327,7 @@ func TestEnvTest(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer clearEnv() + applyEnv(test.envVars) envTest := test.envTestSetup() diff --git a/platform/tester/servermock/link_form.go b/platform/tester/servermock/link_form.go index e7541cefa..581e27d66 100644 --- a/platform/tester/servermock/link_form.go +++ b/platform/tester/servermock/link_form.go @@ -43,6 +43,7 @@ func (l *FormLink) Bind(next http.Handler) http.Handler { if len(form) != len(l.values)+len(l.regexes) { msg := fmt.Sprintf("invalid query parameters, got %v, want %v", req.Form, l.values) http.Error(rw, msg, l.statusCode) + return } } @@ -52,6 +53,7 @@ func (l *FormLink) Bind(next http.Handler) http.Handler { if !slices.Equal(v, value) { msg := fmt.Sprintf("invalid %q form value, got %q, want %q", k, value, v) http.Error(rw, msg, l.statusCode) + return } } @@ -61,6 +63,7 @@ func (l *FormLink) Bind(next http.Handler) http.Handler { if !exp.MatchString(value) { msg := fmt.Sprintf("invalid %q form value, %q doesn't match to %q", k, value, exp) http.Error(rw, msg, l.statusCode) + return } } diff --git a/platform/tester/servermock/link_headers.go b/platform/tester/servermock/link_headers.go index 821c737fe..0ca519958 100644 --- a/platform/tester/servermock/link_headers.go +++ b/platform/tester/servermock/link_headers.go @@ -55,6 +55,7 @@ func (l *HeaderLink) Bind(next http.Handler) http.Handler { if !exp.MatchString(value) { msg := fmt.Sprintf("invalid %q header value, %q doesn't match to %q", k, value, exp) http.Error(rw, msg, l.statusCode) + return } } diff --git a/platform/tester/servermock/link_query.go b/platform/tester/servermock/link_query.go index 00d7450ae..14f776515 100644 --- a/platform/tester/servermock/link_query.go +++ b/platform/tester/servermock/link_query.go @@ -32,6 +32,7 @@ func (l *QueryParameterLink) Bind(next http.Handler) http.Handler { if len(query) != len(l.values)+len(l.regexes) { msg := fmt.Sprintf("invalid query parameters, got %v, want %v", query, l.values) http.Error(rw, msg, l.statusCode) + return } } @@ -41,6 +42,7 @@ func (l *QueryParameterLink) Bind(next http.Handler) http.Handler { if p != v { msg := fmt.Sprintf("invalid %q query parameter value, got %q, want %q", k, p, v) http.Error(rw, msg, l.statusCode) + return } } @@ -50,6 +52,7 @@ func (l *QueryParameterLink) Bind(next http.Handler) http.Handler { if !exp.MatchString(value) { msg := fmt.Sprintf("invalid %q query parameter value, %q doesn't match to %q", k, value, exp) http.Error(rw, msg, l.statusCode) + return } } diff --git a/platform/tester/servermock/link_request_body.go b/platform/tester/servermock/link_request_body.go index b58c3cc79..d6b2d9efd 100644 --- a/platform/tester/servermock/link_request_body.go +++ b/platform/tester/servermock/link_request_body.go @@ -76,6 +76,7 @@ func (l *RequestBodyLink) Bind(next http.Handler) http.Handler { msg := fmt.Sprintf("%s: request body differences: got: %s, want: %s", req.URL.Path, string(bytes.TrimSpace(body)), string(bytes.TrimSpace(expectedRaw))) http.Error(rw, msg, http.StatusBadRequest) + return } diff --git a/platform/tester/servermock/link_request_body_json.go b/platform/tester/servermock/link_request_body_json.go index 3dc2f0cfa..ed5a117ba 100644 --- a/platform/tester/servermock/link_request_body_json.go +++ b/platform/tester/servermock/link_request_body_json.go @@ -90,6 +90,7 @@ func (l *RequestBodyJSONLink) Bind(next http.Handler) http.Handler { if err != nil { msg := fmt.Sprintf("%s: the expected request body is not valid JSON: %v", req.URL.Path, err) http.Error(rw, msg, http.StatusBadRequest) + return } @@ -97,12 +98,14 @@ func (l *RequestBodyJSONLink) Bind(next http.Handler) http.Handler { if err != nil { msg := fmt.Sprintf("%s: request body is not valid JSON: %v", req.URL.Path, err) http.Error(rw, msg, http.StatusBadRequest) + return } if !cmp.Equal(actual, expected) { msg := fmt.Sprintf("%s: request body differences: %s", req.URL.Path, cmp.Diff(actual, expected)) http.Error(rw, msg, http.StatusBadRequest) + return } diff --git a/platform/wait/wait.go b/platform/wait/wait.go index e6420263d..c66f57446 100644 --- a/platform/wait/wait.go +++ b/platform/wait/wait.go @@ -14,13 +14,16 @@ func For(msg string, timeout, interval time.Duration, f func() (bool, error)) er log.Infof("Wait for %s [timeout: %s, interval: %s]", msg, timeout, interval) var lastErr error + timeUp := time.After(timeout) + for { select { case <-timeUp: if lastErr == nil { return fmt.Errorf("%s: time limit exceeded", msg) } + return fmt.Errorf("%s: time limit exceeded: last error: %w", msg, lastErr) default: } @@ -44,5 +47,6 @@ func Retry(ctx context.Context, operation func() error, opts ...backoff.RetryOpt _, err := backoff.Retry(ctx, func() (any, error) { return nil, operation() }, opts...) + return err } diff --git a/platform/wait/wait_test.go b/platform/wait/wait_test.go index b401c819f..36dbffe69 100644 --- a/platform/wait/wait_test.go +++ b/platform/wait/wait_test.go @@ -19,6 +19,7 @@ func TestFor_timeout(t *testing.T) { go func() { c <- For("test", 3*time.Second, 1*time.Second, func() (bool, error) { io.Add(1) + if io.Load() == 1 { return false, nil } diff --git a/providers/dns/acmedns/acmedns.go b/providers/dns/acmedns/acmedns.go index 9663a656b..8f1f16842 100644 --- a/providers/dns/acmedns/acmedns.go +++ b/providers/dns/acmedns/acmedns.go @@ -114,6 +114,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } // NewDNSProviderClient creates an ACME-DNS DNSProvider with the given acmeDNSClient and [goacmedns.Storage]. +// // Deprecated: use [NewDNSProviderConfig] instead. func NewDNSProviderClient(client acmeDNSClient, store goacmedns.Storage) (*DNSProvider, error) { if client == nil { diff --git a/providers/dns/acmedns/mock_test.go b/providers/dns/acmedns/mock_test.go index 629f25a0c..a09a3ca91 100644 --- a/providers/dns/acmedns/mock_test.go +++ b/providers/dns/acmedns/mock_test.go @@ -107,6 +107,7 @@ func newMockStorage() *mockStorage { if acct, ok := m.accounts[domain]; ok { return acct, nil } + return goacmedns.Account{}, storage.ErrDomainNotFound } diff --git a/providers/dns/active24/active24_test.go b/providers/dns/active24/active24_test.go index d7d2c5535..363e0229a 100644 --- a/providers/dns/active24/active24_test.go +++ b/providers/dns/active24/active24_test.go @@ -50,6 +50,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -124,6 +125,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -137,6 +139,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/alidns/alidns.go b/providers/dns/alidns/alidns.go index eefc1162e..a5c883fcb 100644 --- a/providers/dns/alidns/alidns.go +++ b/providers/dns/alidns/alidns.go @@ -170,6 +170,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("alicloud: API call failed: %w", err) } + return nil } @@ -233,6 +234,7 @@ func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (string, } var hostedZone *alidns.DescribeDomainsResponseBodyDomainsDomain + for _, zone := range domains { if ptr.Deref(zone.DomainName) == dns01.UnFqdn(authZone) || ptr.Deref(zone.PunyCode) == dns01.UnFqdn(authZone) { hostedZone = zone @@ -287,6 +289,7 @@ func (d *DNSProvider) findTxtRecords(ctx context.Context, fqdn string) ([]*alidn records = append(records, record) } } + return records, nil } diff --git a/providers/dns/alidns/alidns_test.go b/providers/dns/alidns/alidns_test.go index 487997813..b1e482d2d 100644 --- a/providers/dns/alidns/alidns_test.go +++ b/providers/dns/alidns/alidns_test.go @@ -64,6 +64,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -142,6 +143,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -155,6 +157,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/allinkl/allinkl.go b/providers/dns/allinkl/allinkl.go index 5be194ed2..a5b27ff59 100644 --- a/providers/dns/allinkl/allinkl.go +++ b/providers/dns/allinkl/allinkl.go @@ -176,6 +176,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("allinkl: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/allinkl/allinkl_test.go b/providers/dns/allinkl/allinkl_test.go index af85f8c54..b42adce5d 100644 --- a/providers/dns/allinkl/allinkl_test.go +++ b/providers/dns/allinkl/allinkl_test.go @@ -53,6 +53,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -121,6 +122,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -134,6 +136,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/allinkl/internal/client.go b/providers/dns/allinkl/internal/client.go index 02a5a2c8f..d747e9b36 100644 --- a/providers/dns/allinkl/internal/client.go +++ b/providers/dns/allinkl/internal/client.go @@ -57,6 +57,7 @@ func (c *Client) GetDNSSettings(ctx context.Context, zone, recordID string) ([]R } var g GetDNSSettingsAPIResponse + err = c.do(req, &g) if err != nil { return nil, err @@ -75,6 +76,7 @@ func (c *Client) AddDNSSettings(ctx context.Context, record DNSRequest) (string, } var g AddDNSSettingsAPIResponse + err = c.do(req, &g) if err != nil { return "", err @@ -95,6 +97,7 @@ func (c *Client) DeleteDNSSettings(ctx context.Context, recordID string) (string } var g DeleteDNSSettingsAPIResponse + err = c.do(req, &g) if err != nil { return "", err diff --git a/providers/dns/allinkl/internal/types.go b/providers/dns/allinkl/internal/types.go index b5c6ba0d1..b0aa9b4ff 100644 --- a/providers/dns/allinkl/internal/types.go +++ b/providers/dns/allinkl/internal/types.go @@ -17,6 +17,7 @@ func (tr Trimmer) Token() (xml.Token, error) { if cd, ok := t.(xml.CharData); ok { t = xml.CharData(bytes.TrimSpace(cd)) } + return t, err } @@ -53,6 +54,7 @@ func decodeXML[T any](reader io.Reader) (*T, error) { } var result T + err = xml.NewTokenDecoder(Trimmer{decoder: xml.NewDecoder(bytes.NewReader(raw))}).Decode(&result) if err != nil { return nil, fmt.Errorf("decode XML response: %w", err) diff --git a/providers/dns/anexia/anexia.go b/providers/dns/anexia/anexia.go index 568ef5263..3ce7e2208 100644 --- a/providers/dns/anexia/anexia.go +++ b/providers/dns/anexia/anexia.go @@ -96,6 +96,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config.APIURL != "" { var err error + client.BaseURL, err = url.Parse(config.APIURL) if err != nil { return nil, fmt.Errorf("anexia: %w", err) diff --git a/providers/dns/anexia/anexia_test.go b/providers/dns/anexia/anexia_test.go index f38bfd7f7..9960c14d1 100644 --- a/providers/dns/anexia/anexia_test.go +++ b/providers/dns/anexia/anexia_test.go @@ -42,6 +42,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -102,6 +103,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -115,6 +117,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/anexia/internal/client.go b/providers/dns/anexia/internal/client.go index 86f3b2697..1a4159be0 100644 --- a/providers/dns/anexia/internal/client.go +++ b/providers/dns/anexia/internal/client.go @@ -49,6 +49,7 @@ func (c *Client) CreateRecord(ctx context.Context, zoneName string, record Recor } var zone Zone + err = c.do(req, &zone) if err != nil { return nil, err @@ -147,6 +148,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/arvancloud/arvancloud.go b/providers/dns/arvancloud/arvancloud.go index 4b5fbab62..ed1d5ff7a 100644 --- a/providers/dns/arvancloud/arvancloud.go +++ b/providers/dns/arvancloud/arvancloud.go @@ -167,6 +167,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("arvancloud: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/arvancloud/arvancloud_test.go b/providers/dns/arvancloud/arvancloud_test.go index c31edf021..24013c437 100644 --- a/providers/dns/arvancloud/arvancloud_test.go +++ b/providers/dns/arvancloud/arvancloud_test.go @@ -37,6 +37,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -104,6 +105,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -117,6 +119,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/arvancloud/internal/client.go b/providers/dns/arvancloud/internal/client.go index 3caff392a..b447d97c4 100644 --- a/providers/dns/arvancloud/internal/client.go +++ b/providers/dns/arvancloud/internal/client.go @@ -70,6 +70,7 @@ func (c *Client) getRecords(ctx context.Context, domain, search string) ([]DNSRe } response := &apiResponse[[]DNSRecord]{} + err = c.do(req, http.StatusOK, response) if err != nil { return nil, fmt.Errorf("could not get records %s: Domain: %s: %w", search, domain, err) @@ -89,6 +90,7 @@ func (c *Client) CreateRecord(ctx context.Context, domain string, record DNSReco } response := &apiResponse[*DNSRecord]{} + err = c.do(req, http.StatusCreated, response) if err != nil { return nil, fmt.Errorf("could not create record; Domain: %s: %w", domain, err) diff --git a/providers/dns/arvancloud/internal/client_test.go b/providers/dns/arvancloud/internal/client_test.go index d13bc6f34..183a8acfd 100644 --- a/providers/dns/arvancloud/internal/client_test.go +++ b/providers/dns/arvancloud/internal/client_test.go @@ -81,8 +81,10 @@ func TestClient_CreateRecord(t *testing.T) { func TestClient_DeleteRecord(t *testing.T) { const apiKey = "myKeyC" - const domain = "example.com" - const recordID = "recordId" + const ( + domain = "example.com" + recordID = "recordId" + ) client := mockBuilder(apiKey). Route("DELETE /cdn/4.0/domains/"+domain+"/dns-records/"+recordID, nil). diff --git a/providers/dns/auroradns/auroradns_test.go b/providers/dns/auroradns/auroradns_test.go index c5a2ca877..8a9835d9c 100644 --- a/providers/dns/auroradns/auroradns_test.go +++ b/providers/dns/auroradns/auroradns_test.go @@ -71,6 +71,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) diff --git a/providers/dns/autodns/autodns_test.go b/providers/dns/autodns/autodns_test.go index bc9f3067e..632d24705 100644 --- a/providers/dns/autodns/autodns_test.go +++ b/providers/dns/autodns/autodns_test.go @@ -57,6 +57,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -131,6 +132,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -144,6 +146,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/autodns/internal/client.go b/providers/dns/autodns/internal/client.go index 1fc9589ea..547596f81 100644 --- a/providers/dns/autodns/internal/client.go +++ b/providers/dns/autodns/internal/client.go @@ -55,6 +55,7 @@ func (c *Client) RemoveTXTRecords(ctx context.Context, domain string, records [] zoneStream := &ZoneStream{Removes: records} _, err := c.updateZone(ctx, domain, zoneStream) + return err } diff --git a/providers/dns/axelname/axelname_test.go b/providers/dns/axelname/axelname_test.go index 52c4f38b9..1a8bac971 100644 --- a/providers/dns/axelname/axelname_test.go +++ b/providers/dns/axelname/axelname_test.go @@ -50,6 +50,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -120,6 +121,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -133,6 +135,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/axelname/internal/client.go b/providers/dns/axelname/internal/client.go index f6cf079e6..f2defec87 100644 --- a/providers/dns/axelname/internal/client.go +++ b/providers/dns/axelname/internal/client.go @@ -174,6 +174,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/azion/azion.go b/providers/dns/azion/azion.go index a257fa0f1..8150d90d5 100644 --- a/providers/dns/azion/azion.go +++ b/providers/dns/azion/azion.go @@ -137,6 +137,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { record.SetTtl(int32(d.config.TTL)) var resp *idns.PostOrPutRecordResponse + if existingRecord != nil { // Update existing record by adding the new value to the existing ones record.SetAnswersList(append(existingRecord.GetAnswersList(), info.Value)) @@ -161,6 +162,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { } results := resp.GetResults() + d.recordIDsMu.Lock() d.recordIDs[token] = results.GetId() d.recordIDsMu.Unlock() @@ -203,6 +205,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { currentAnswers := existingRecord.GetAnswersList() var updatedAnswers []string + for _, answer := range currentAnswers { if answer != info.Value { updatedAnswers = append(updatedAnswers, answer) diff --git a/providers/dns/azion/azion_test.go b/providers/dns/azion/azion_test.go index b3b553114..517594cdc 100644 --- a/providers/dns/azion/azion_test.go +++ b/providers/dns/azion/azion_test.go @@ -40,6 +40,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -99,6 +100,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -112,6 +114,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/azure/azure.go b/providers/dns/azure/azure.go index 5702acd8a..fd00bcbe2 100644 --- a/providers/dns/azure/azure.go +++ b/providers/dns/azure/azure.go @@ -89,6 +89,7 @@ type DNSProvider struct { // If the credentials are _not_ set via the environment, // then it will attempt to get a bearer token via the instance metadata service. // see: https://github.com/Azure/go-autorest/blob/v10.14.0/autorest/azure/auth/auth.go#L38-L42 +// // Deprecated: use azuredns instead. func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() @@ -96,6 +97,7 @@ func NewDNSProvider() (*DNSProvider, error) { environmentName := env.GetOrFile(EnvEnvironment) if environmentName != "" { var environment aazure.Environment + switch environmentName { case "china": environment = aazure.ChinaCloud @@ -124,6 +126,7 @@ func NewDNSProvider() (*DNSProvider, error) { } // NewDNSProviderConfig return a DNSProvider instance configured for Azure. +// // Deprecated: use azuredns instead. func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config == nil { @@ -148,6 +151,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if subsID == "" { return nil, errors.New("azure: SubscriptionID is missing") } + config.SubscriptionID = subsID } @@ -160,6 +164,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if resGroup == "" { return nil, errors.New("azure: ResourceGroup is missing") } + config.ResourceGroup = resGroup } diff --git a/providers/dns/azure/azure_test.go b/providers/dns/azure/azure_test.go index 496168362..44fb81eef 100644 --- a/providers/dns/azure/azure_test.go +++ b/providers/dns/azure/azure_test.go @@ -54,6 +54,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -158,6 +159,7 @@ func TestNewDNSProviderConfig(t *testing.T) { } else { mux.HandleFunc("/", test.handler) } + config.MetadataEndpoint = server.URL p, err := NewDNSProviderConfig(config) @@ -186,6 +188,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -199,6 +202,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/azure/private.go b/providers/dns/azure/private.go index d6c9fc7bd..f7c6a75b7 100644 --- a/providers/dns/azure/private.go +++ b/providers/dns/azure/private.go @@ -54,6 +54,7 @@ func (d *dnsProviderPrivate) Present(domain, token, keyAuth string) error { // Construct unique TXT records using map uniqRecords := map[string]struct{}{info.Value: {}} + if rset.RecordSetProperties != nil && rset.TxtRecords != nil { for _, txtRecord := range *rset.TxtRecords { // Assume Value doesn't contain multiple strings @@ -81,6 +82,7 @@ func (d *dnsProviderPrivate) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("azure: %w", err) } + return nil } @@ -106,6 +108,7 @@ func (d *dnsProviderPrivate) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("azure: %w", err) } + return nil } diff --git a/providers/dns/azure/public.go b/providers/dns/azure/public.go index 8e6fa182a..194956c9c 100644 --- a/providers/dns/azure/public.go +++ b/providers/dns/azure/public.go @@ -54,6 +54,7 @@ func (d *dnsProviderPublic) Present(domain, token, keyAuth string) error { // Construct unique TXT records using map uniqRecords := map[string]struct{}{info.Value: {}} + if rset.RecordSetProperties != nil && rset.TxtRecords != nil { for _, txtRecord := range *rset.TxtRecords { // Assume Value doesn't contain multiple strings @@ -81,6 +82,7 @@ func (d *dnsProviderPublic) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("azure: %w", err) } + return nil } @@ -106,6 +108,7 @@ func (d *dnsProviderPublic) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("azure: %w", err) } + return nil } diff --git a/providers/dns/azuredns/azuredns_test.go b/providers/dns/azuredns/azuredns_test.go index 7ddb4de45..594a0d6a3 100644 --- a/providers/dns/azuredns/azuredns_test.go +++ b/providers/dns/azuredns/azuredns_test.go @@ -35,6 +35,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -61,6 +62,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -74,6 +76,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/azuredns/credentials.go b/providers/dns/azuredns/credentials.go index efca10e59..a38b3f7dd 100644 --- a/providers/dns/azuredns/credentials.go +++ b/providers/dns/azuredns/credentials.go @@ -51,6 +51,7 @@ func getCredentials(config *Config) (azcore.TokenCredential, error) { if config.TenantID != "" { credOptions = &azidentity.AzureCLICredentialOptions{TenantID: config.TenantID} } + return azidentity.NewAzureCLICredential(credOptions) case authMethodOIDC: diff --git a/providers/dns/azuredns/private.go b/providers/dns/azuredns/private.go index 24fb3d5ee..43b39ed14 100644 --- a/providers/dns/azuredns/private.go +++ b/providers/dns/azuredns/private.go @@ -181,6 +181,7 @@ func (c privateZoneClient) Delete(ctx context.Context, subDomain string) (armpri func privateUniqueRecords(recordSet armprivatedns.RecordSet, value string) map[string]struct{} { uniqRecords := map[string]struct{}{value: {}} + if recordSet.Properties != nil && recordSet.Properties.TxtRecords != nil { for _, txtRecord := range recordSet.Properties.TxtRecords { // Assume Value doesn't contain multiple strings diff --git a/providers/dns/azuredns/public.go b/providers/dns/azuredns/public.go index f7e46150d..79b6e783f 100644 --- a/providers/dns/azuredns/public.go +++ b/providers/dns/azuredns/public.go @@ -179,6 +179,7 @@ func (c publicZoneClient) Delete(ctx context.Context, subDomain string) (armdns. func publicUniqueRecords(recordSet armdns.RecordSet, value string) map[string]struct{} { uniqRecords := map[string]struct{}{value: {}} + if recordSet.Properties != nil && recordSet.Properties.TxtRecords != nil { for _, txtRecord := range recordSet.Properties.TxtRecords { // Assume Value doesn't contain multiple strings diff --git a/providers/dns/azuredns/servicediscovery.go b/providers/dns/azuredns/servicediscovery.go index 882e19241..50a41da37 100644 --- a/providers/dns/azuredns/servicediscovery.go +++ b/providers/dns/azuredns/servicediscovery.go @@ -46,6 +46,7 @@ func discoverDNSZones(ctx context.Context, config *Config, credentials azcore.To } zones := map[string]ServiceDiscoveryZone{} + for { // create the query request request := armresourcegraph.QueryRequest{ diff --git a/providers/dns/baiducloud/baiducloud_test.go b/providers/dns/baiducloud/baiducloud_test.go index 3cc411323..483bfaf5e 100644 --- a/providers/dns/baiducloud/baiducloud_test.go +++ b/providers/dns/baiducloud/baiducloud_test.go @@ -48,6 +48,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -122,6 +123,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,6 +137,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/beget/beget.go b/providers/dns/beget/beget.go index e0d67572f..d4449deb8 100644 --- a/providers/dns/beget/beget.go +++ b/providers/dns/beget/beget.go @@ -145,6 +145,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var updatedRecords []internal.Record + for _, record := range records { if record.Data == info.Value { continue diff --git a/providers/dns/beget/beget_test.go b/providers/dns/beget/beget_test.go index e89b626b6..3cfb3c0b4 100644 --- a/providers/dns/beget/beget_test.go +++ b/providers/dns/beget/beget_test.go @@ -58,6 +58,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -132,6 +133,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() assert.NoError(t, err) @@ -145,6 +147,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() assert.NoError(t, err) diff --git a/providers/dns/beget/internal/client.go b/providers/dns/beget/internal/client.go index d8d300606..9b9746ba2 100644 --- a/providers/dns/beget/internal/client.go +++ b/providers/dns/beget/internal/client.go @@ -114,6 +114,7 @@ func (c *Client) doRequest(ctx context.Context, data any, fragments ...string) ( } var apiResp APIResponse + err = json.Unmarshal(raw, &apiResp) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -126,6 +127,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var apiResp APIResponse + err := json.Unmarshal(raw, &apiResp) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/beget/internal/types.go b/providers/dns/beget/internal/types.go index 90766da79..f453bf628 100644 --- a/providers/dns/beget/internal/types.go +++ b/providers/dns/beget/internal/types.go @@ -78,7 +78,7 @@ type GetRecordsRequest struct { // ChangeRecordsRequest data representation for data change request. type ChangeRecordsRequest struct { Fqdn string `json:"fqdn,omitempty"` - Records RecordList `json:"records,omitempty"` + Records RecordList `json:"records"` } // RecordList List of entries (in this case only described TXT). diff --git a/providers/dns/binarylane/binarylane.go b/providers/dns/binarylane/binarylane.go index 83016fff7..9ff80d698 100644 --- a/providers/dns/binarylane/binarylane.go +++ b/providers/dns/binarylane/binarylane.go @@ -141,6 +141,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("binarylane: unknown record ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/binarylane/binarylane_test.go b/providers/dns/binarylane/binarylane_test.go index 9519fe0f2..4f2cfd230 100644 --- a/providers/dns/binarylane/binarylane_test.go +++ b/providers/dns/binarylane/binarylane_test.go @@ -35,6 +35,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -94,6 +95,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -107,6 +109,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/binarylane/internal/client.go b/providers/dns/binarylane/internal/client.go index 45518cb41..3f10e9f8b 100644 --- a/providers/dns/binarylane/internal/client.go +++ b/providers/dns/binarylane/internal/client.go @@ -57,6 +57,7 @@ func (c *Client) CreateRecord(ctx context.Context, domain string, record Record) } var result APIResponse + err = c.do(req, &result) if err != nil { return nil, err @@ -137,6 +138,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/binarylane/internal/types.go b/providers/dns/binarylane/internal/types.go index e6c27410a..987e5c356 100644 --- a/providers/dns/binarylane/internal/types.go +++ b/providers/dns/binarylane/internal/types.go @@ -15,13 +15,15 @@ type APIError struct { } func (a *APIError) Error() string { - msg := fmt.Sprintf("%d: %s: %s: %s: %s", a.Status, a.Type, a.Title, a.Detail, a.Instance) + var msg strings.Builder + + msg.WriteString(fmt.Sprintf("%d: %s: %s: %s: %s", a.Status, a.Type, a.Title, a.Detail, a.Instance)) for s, values := range a.Errors { - msg += fmt.Sprintf(": %s: %s", s, strings.Join(values, ", ")) + msg.WriteString(fmt.Sprintf(": %s: %s", s, strings.Join(values, ", "))) } - return msg + return msg.String() } type Record struct { diff --git a/providers/dns/bindman/bindman.go b/providers/dns/bindman/bindman.go index bd026bf74..c529cb63c 100644 --- a/providers/dns/bindman/bindman.go +++ b/providers/dns/bindman/bindman.go @@ -98,6 +98,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err := d.client.AddRecord(info.EffectiveFQDN, "TXT", info.Value); err != nil { return fmt.Errorf("bindman: %w", err) } + return nil } @@ -108,6 +109,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err := d.client.RemoveRecord(info.EffectiveFQDN, "TXT"); err != nil { return fmt.Errorf("bindman: %w", err) } + return nil } diff --git a/providers/dns/bindman/bindman_test.go b/providers/dns/bindman/bindman_test.go index 15db9c424..978a1d006 100644 --- a/providers/dns/bindman/bindman_test.go +++ b/providers/dns/bindman/bindman_test.go @@ -46,6 +46,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -215,6 +216,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -228,6 +230,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/bluecat/bluecat_test.go b/providers/dns/bluecat/bluecat_test.go index 5a3670e3a..38b110470 100644 --- a/providers/dns/bluecat/bluecat_test.go +++ b/providers/dns/bluecat/bluecat_test.go @@ -105,6 +105,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -219,6 +220,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -232,6 +234,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/bluecat/internal/client.go b/providers/dns/bluecat/internal/client.go index de31579ea..d517ea857 100644 --- a/providers/dns/bluecat/internal/client.go +++ b/providers/dns/bluecat/internal/client.go @@ -106,6 +106,7 @@ func (c *Client) AddEntity(ctx context.Context, parentID uint, entity Entity) (u // addEntity responds only with body text containing the ID of the created record addTxtResp := string(raw) + id, err := strconv.ParseUint(addTxtResp, 10, 64) if err != nil { return 0, fmt.Errorf("addEntity request failed: %s", addTxtResp) @@ -147,6 +148,7 @@ func (c *Client) GetEntityByName(ctx context.Context, parentID uint, name, objTy } var entity EntityResponse + err = json.Unmarshal(raw, &entity) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/bluecat/internal/client_test.go b/providers/dns/bluecat/internal/client_test.go index 9d79f46b3..d4776b8a1 100644 --- a/providers/dns/bluecat/internal/client_test.go +++ b/providers/dns/bluecat/internal/client_test.go @@ -31,6 +31,7 @@ func TestClient_LookupParentZoneID(t *testing.T) { Type: ZoneType, Properties: "test", }) + return } diff --git a/providers/dns/bookmyname/bookmyname_test.go b/providers/dns/bookmyname/bookmyname_test.go index dd02d63d7..8b3fa21e6 100644 --- a/providers/dns/bookmyname/bookmyname_test.go +++ b/providers/dns/bookmyname/bookmyname_test.go @@ -50,6 +50,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -122,6 +123,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,6 +137,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/brandit/brandit.go b/providers/dns/brandit/brandit.go index 012e5ad15..fe3b52239 100644 --- a/providers/dns/brandit/brandit.go +++ b/providers/dns/brandit/brandit.go @@ -168,6 +168,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordsMu.Lock() dnsRecord, ok := d.records[token] d.recordsMu.Unlock() + if !ok { return fmt.Errorf("brandit: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } @@ -186,6 +187,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var recordID int + for i, r := range records.RR { if r == dnsRecord { recordID = i diff --git a/providers/dns/brandit/brandit_test.go b/providers/dns/brandit/brandit_test.go index 156e7c3f4..40abdd3d0 100644 --- a/providers/dns/brandit/brandit_test.go +++ b/providers/dns/brandit/brandit_test.go @@ -48,6 +48,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -120,6 +121,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -133,6 +135,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/brandit/internal/client.go b/providers/dns/brandit/internal/client.go index 59c57419a..cda3be5a2 100644 --- a/providers/dns/brandit/internal/client.go +++ b/providers/dns/brandit/internal/client.go @@ -62,6 +62,7 @@ func (c *Client) ListRecords(ctx context.Context, account, dnsZone string) (*Lis query.Add("first", strconv.Itoa(result.Response.Last[0]+1)) tmp := &Response[*ListRecordsResponse]{} + err := c.do(ctx, query, tmp) if err != nil { return nil, err @@ -156,6 +157,7 @@ func (c *Client) do(ctx context.Context, query url.Values, result any) error { // Unmarshal the error response, because the API returns a 200 OK even if there is an error. var apiError APIError + err = json.Unmarshal(raw, &apiError) if err != nil { return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -183,6 +185,7 @@ func sign(apiUsername, apiKey string, query url.Values) (url.Values, error) { canonicalRequest := fmt.Sprintf("%s%s%s", apiUsername, timestamp, defaultBaseURL) mac := hmac.New(sha256.New, []byte(apiKey)) + _, err := mac.Write([]byte(canonicalRequest)) if err != nil { return nil, err diff --git a/providers/dns/bunny/bunny.go b/providers/dns/bunny/bunny.go index 67febeca6..29949608b 100644 --- a/providers/dns/bunny/bunny.go +++ b/providers/dns/bunny/bunny.go @@ -159,10 +159,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var record *bunny.DNSRecord + for _, r := range zone.Records { if ptr.Deref(r.Name) == subDomain && ptr.Deref(r.Type) == bunny.DNSRecordTypeTXT { r := r record = &r + break } } @@ -198,6 +200,7 @@ func findZone(zones *bunny.DNSZones, domain string) *bunny.DNSZone { var domainLength int var zone *bunny.DNSZone + for _, item := range zones.Items { if item == nil { continue diff --git a/providers/dns/bunny/bunny_test.go b/providers/dns/bunny/bunny_test.go index 4cf0f6b01..ca4e821e0 100644 --- a/providers/dns/bunny/bunny_test.go +++ b/providers/dns/bunny/bunny_test.go @@ -40,6 +40,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -107,6 +108,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -120,6 +122,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/checkdomain/checkdomain.go b/providers/dns/checkdomain/checkdomain.go index c615f5733..4bc926ed9 100644 --- a/providers/dns/checkdomain/checkdomain.go +++ b/providers/dns/checkdomain/checkdomain.go @@ -73,6 +73,7 @@ func NewDNSProvider() (*DNSProvider, error) { if err != nil { return nil, fmt.Errorf("checkdomain: invalid %s: %w", EnvEndpoint, err) } + config.Endpoint = endpoint return NewDNSProviderConfig(config) diff --git a/providers/dns/checkdomain/checkdomain_test.go b/providers/dns/checkdomain/checkdomain_test.go index d9d0b62a6..b2c940f7a 100644 --- a/providers/dns/checkdomain/checkdomain_test.go +++ b/providers/dns/checkdomain/checkdomain_test.go @@ -46,6 +46,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -108,6 +109,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -121,6 +123,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/checkdomain/internal/client.go b/providers/dns/checkdomain/internal/client.go index 74189dee4..d626275ab 100644 --- a/providers/dns/checkdomain/internal/client.go +++ b/providers/dns/checkdomain/internal/client.go @@ -63,6 +63,7 @@ func (c *Client) GetDomainIDByName(ctx context.Context, name string) (int, error c.domainIDMu.Lock() id, ok := c.domainIDMapping[name] c.domainIDMu.Unlock() + if ok { return id, nil } @@ -100,6 +101,7 @@ func (c *Client) listDomains(ctx context.Context) ([]*Domain, error) { totalPages := maxInt var domainList []*Domain + for currentPage <= totalPages { q.Set("page", strconv.Itoa(currentPage)) endpoint.RawQuery = q.Encode() @@ -151,6 +153,7 @@ func (c *Client) CheckNameservers(ctx context.Context, domainID int) error { } var found1, found2 bool + for _, item := range info.Nameservers { switch item.Name { case ns1: @@ -229,6 +232,7 @@ func (c *Client) getDomainInfo(ctx context.Context, domainID int) (*DomainRespon } var res DomainResponse + err = c.do(req, &res) if err != nil { return nil, err @@ -242,6 +246,7 @@ func (c *Client) listRecords(ctx context.Context, domainID int, recordType strin q := endpoint.Query() q.Set("limit", strconv.Itoa(maxLimit)) + if recordType != "" { q.Set("type", recordType) } @@ -250,6 +255,7 @@ func (c *Client) listRecords(ctx context.Context, domainID int, recordType strin totalPages := maxInt var recordList []*Record + for currentPage <= totalPages { q.Set("page", strconv.Itoa(currentPage)) endpoint.RawQuery = q.Encode() diff --git a/providers/dns/civo/civo.go b/providers/dns/civo/civo.go index a6af01e8a..dfb7c307f 100644 --- a/providers/dns/civo/civo.go +++ b/providers/dns/civo/civo.go @@ -169,6 +169,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var dnsRecord internal.Record + for _, entry := range dnsRecords { if entry.Name == subDomain && entry.Value == info.Value { dnsRecord = entry diff --git a/providers/dns/civo/civo_test.go b/providers/dns/civo/civo_test.go index 9b9e98b38..416dbac1d 100644 --- a/providers/dns/civo/civo_test.go +++ b/providers/dns/civo/civo_test.go @@ -42,6 +42,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -106,6 +107,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -119,6 +121,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/civo/internal/client.go b/providers/dns/civo/internal/client.go index 25a11ef60..dc1d57793 100644 --- a/providers/dns/civo/internal/client.go +++ b/providers/dns/civo/internal/client.go @@ -188,6 +188,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/civo/internal/client_test.go b/providers/dns/civo/internal/client_test.go index 5b47c185e..ad56b75de 100644 --- a/providers/dns/civo/internal/client_test.go +++ b/providers/dns/civo/internal/client_test.go @@ -93,7 +93,6 @@ func TestClient_ListDNSRecords_error_raw(t *testing.T) { // > So, for example, 404 Not Found pages are a standard page of text // > but 403 Unauthorized requests may have a reason attribute available in the JSON object. // https://www.civo.com/api#parameters-and-responses - client := mockBuilder(). Route("GET /dns/7088fcea-7658-43e6-97fa-273f901978fd/records", servermock.RawStringResponse(http.StatusText(http.StatusNotFound)). diff --git a/providers/dns/clouddns/clouddns_test.go b/providers/dns/clouddns/clouddns_test.go index d7bfc4a1f..f1e2a196e 100644 --- a/providers/dns/clouddns/clouddns_test.go +++ b/providers/dns/clouddns/clouddns_test.go @@ -63,6 +63,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -148,6 +149,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -161,6 +163,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/clouddns/internal/client.go b/providers/dns/clouddns/internal/client.go index cd3da50c7..9fb6902de 100644 --- a/providers/dns/clouddns/internal/client.go +++ b/providers/dns/clouddns/internal/client.go @@ -122,6 +122,7 @@ func (c *Client) getDomain(ctx context.Context, zone string) (Domain, error) { } var result SearchResponse + err = c.do(req, &result) if err != nil { return Domain{}, err @@ -143,6 +144,7 @@ func (c *Client) getRecord(ctx context.Context, domainID, recordName string) (Re } var result DomainInfo + err = c.do(req, &result) if err != nil { return Record{}, err @@ -232,6 +234,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response APIError + err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/clouddns/internal/identity.go b/providers/dns/clouddns/internal/identity.go index 4ea5c5049..6b20ad814 100644 --- a/providers/dns/clouddns/internal/identity.go +++ b/providers/dns/clouddns/internal/identity.go @@ -20,6 +20,7 @@ func (c *Client) login(ctx context.Context) (*AuthResponse, error) { } var result AuthResponse + err = c.do(req, &result) if err != nil { return nil, err diff --git a/providers/dns/clouddns/internal/types.go b/providers/dns/clouddns/internal/types.go index a53c958a7..9de11d848 100644 --- a/providers/dns/clouddns/internal/types.go +++ b/providers/dns/clouddns/internal/types.go @@ -21,7 +21,7 @@ type Authorization struct { } type AuthResponse struct { - Auth Auth `json:"auth,omitempty"` + Auth Auth `json:"auth"` } type Auth struct { diff --git a/providers/dns/cloudflare/cloudflare.go b/providers/dns/cloudflare/cloudflare.go index 081025f34..98b3495bb 100644 --- a/providers/dns/cloudflare/cloudflare.go +++ b/providers/dns/cloudflare/cloudflare.go @@ -104,6 +104,7 @@ func NewDNSProvider() (*DNSProvider, error) { ) if err != nil { var errT error + values, errT = env.GetWithFallback( []string{EnvDNSAPIToken, altEnvName(EnvDNSAPIToken)}, []string{EnvZoneAPIToken, altEnvName(EnvZoneAPIToken), EnvDNSAPIToken, altEnvName(EnvDNSAPIToken)}, @@ -209,6 +210,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("cloudflare: unknown record ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/cloudflare/cloudflare_test.go b/providers/dns/cloudflare/cloudflare_test.go index 14d20ef2d..8de9dd848 100644 --- a/providers/dns/cloudflare/cloudflare_test.go +++ b/providers/dns/cloudflare/cloudflare_test.go @@ -81,6 +81,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -177,15 +178,18 @@ func TestNewDNSProviderWithToken(t *testing.T) { } defer envTest.RestoreEnv() + localEnvTest := tester.NewEnvTest( EnvDNSAPIToken, altEnvName(EnvDNSAPIToken), EnvZoneAPIToken, altEnvName(EnvZoneAPIToken), ).WithDomain(envDomain) + envTest.ClearEnv() for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer localEnvTest.RestoreEnv() + localEnvTest.ClearEnv() localEnvTest.Apply(test.envVars) @@ -200,6 +204,7 @@ func TestNewDNSProviderWithToken(t *testing.T) { require.NotNil(t, p) assert.Equal(t, test.expected.dnsToken, p.config.AuthToken) assert.Equal(t, test.expected.zoneToken, p.config.ZoneToken) + if test.expected.sameClient { assert.Equal(t, p.client.clientRead, p.client.clientEdit) } else { @@ -275,6 +280,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -288,6 +294,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/cloudflare/internal/client.go b/providers/dns/cloudflare/internal/client.go index 33b7b1ba8..b63612ce2 100644 --- a/providers/dns/cloudflare/internal/client.go +++ b/providers/dns/cloudflare/internal/client.go @@ -192,6 +192,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response APIResponse[any] + err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/cloudflare/internal/types.go b/providers/dns/cloudflare/internal/types.go index 2b6d2e2b6..4a7f9e031 100644 --- a/providers/dns/cloudflare/internal/types.go +++ b/providers/dns/cloudflare/internal/types.go @@ -1,6 +1,9 @@ package internal -import "fmt" +import ( + "fmt" + "strings" +) type Record struct { ID string `json:"id,omitempty"` @@ -39,17 +42,17 @@ type ErrorChain struct { type Errors []Message func (e Errors) Error() string { - var msg string + var msg strings.Builder for _, item := range e { - msg = fmt.Sprintf("%d: %s", item.Code, item.Message) + msg.WriteString(fmt.Sprintf("%d: %s", item.Code, item.Message)) for _, link := range item.ErrorChain { - msg += fmt.Sprintf("; %d: %s", link.Code, link.Message) + msg.WriteString(fmt.Sprintf("; %d: %s", link.Code, link.Message)) } } - return msg + return msg.String() } type ResultInfo struct { diff --git a/providers/dns/cloudflare/wrapper.go b/providers/dns/cloudflare/wrapper.go index 1ab36800d..286c20ecd 100644 --- a/providers/dns/cloudflare/wrapper.go +++ b/providers/dns/cloudflare/wrapper.go @@ -99,6 +99,7 @@ func (m *metaClient) ZoneIDByName(ctx context.Context, fdqn string) (string, err m.zonesMu.Lock() m.zones[fdqn] = id m.zonesMu.Unlock() + return id, nil } diff --git a/providers/dns/cloudns/cloudns.go b/providers/dns/cloudns/cloudns.go index 39a4d45cd..916d73bde 100644 --- a/providers/dns/cloudns/cloudns.go +++ b/providers/dns/cloudns/cloudns.go @@ -68,6 +68,7 @@ type DNSProvider struct { // CLOUDNS_AUTH_ID and CLOUDNS_AUTH_PASSWORD. func NewDNSProvider() (*DNSProvider, error) { var subAuthID string + authID := env.GetOrFile(EnvAuthID) if authID == "" { subAuthID = env.GetOrFile(EnvSubAuthID) diff --git a/providers/dns/cloudns/cloudns_test.go b/providers/dns/cloudns/cloudns_test.go index ea4f25c95..024bd93d8 100644 --- a/providers/dns/cloudns/cloudns_test.go +++ b/providers/dns/cloudns/cloudns_test.go @@ -79,6 +79,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -169,6 +170,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -182,6 +184,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/cloudns/internal/client.go b/providers/dns/cloudns/internal/client.go index 60d7e6bbe..278b8de49 100644 --- a/providers/dns/cloudns/internal/client.go +++ b/providers/dns/cloudns/internal/client.go @@ -171,6 +171,7 @@ func (c *Client) ListTxtRecords(ctx context.Context, zoneName, fqdn string) ([]T } var records []TXTRecord + for _, record := range raw { if record.Host == subDomain && record.Type == "TXT" { records = append(records, record) @@ -279,6 +280,7 @@ func (c *Client) GetUpdateStatus(ctx context.Context, zoneName string) (*SyncPro } updatedCount := 0 + for _, record := range records { if record.Updated { updatedCount++ diff --git a/providers/dns/cloudns/internal/client_test.go b/providers/dns/cloudns/internal/client_test.go index dbfa32aee..b9f6c5431 100644 --- a/providers/dns/cloudns/internal/client_test.go +++ b/providers/dns/cloudns/internal/client_test.go @@ -19,6 +19,7 @@ func setupClient(subAuthID string) func(server *httptest.Server) (*Client, error client.BaseURL, _ = url.Parse(server.URL) client.HTTPClient = server.Client() + return client, nil } } diff --git a/providers/dns/cloudru/cloudru_test.go b/providers/dns/cloudru/cloudru_test.go index 88addde93..3e506cb1c 100644 --- a/providers/dns/cloudru/cloudru_test.go +++ b/providers/dns/cloudru/cloudru_test.go @@ -67,6 +67,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -153,6 +154,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -166,6 +168,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/cloudru/internal/client.go b/providers/dns/cloudru/internal/client.go index cb62c5bca..a00ae6ea8 100644 --- a/providers/dns/cloudru/internal/client.go +++ b/providers/dns/cloudru/internal/client.go @@ -61,6 +61,7 @@ func (c *Client) GetZones(ctx context.Context, parentID string) ([]Zone, error) } var zones APIResponse[Zone] + err = c.do(req, &zones) if err != nil { return nil, err @@ -78,6 +79,7 @@ func (c *Client) GetRecords(ctx context.Context, zoneID string) ([]Record, error } var records APIResponse[Record] + err = c.do(req, &records) if err != nil { return nil, err @@ -95,6 +97,7 @@ func (c *Client) CreateRecord(ctx context.Context, zoneID string, record Record) } var result Record + err = c.do(req, &result) if err != nil { return nil, err diff --git a/providers/dns/cloudru/internal/identity.go b/providers/dns/cloudru/internal/identity.go index 79df3c297..3bb09f3fa 100644 --- a/providers/dns/cloudru/internal/identity.go +++ b/providers/dns/cloudru/internal/identity.go @@ -49,6 +49,7 @@ func (c *Client) obtainToken(ctx context.Context) (*Token, error) { } tok := Token{} + err = json.Unmarshal(raw, &tok) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -88,6 +89,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errResp := &authResponseError{} + err := json.Unmarshal(raw, errResp) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/cloudru/internal/types.go b/providers/dns/cloudru/internal/types.go index d233c73bc..713fd459a 100644 --- a/providers/dns/cloudru/internal/types.go +++ b/providers/dns/cloudru/internal/types.go @@ -38,9 +38,9 @@ type Zone struct { Valid bool `json:"valid,omitempty"` ValidationText string `json:"validationText,omitempty"` Delegated bool `json:"delegated,omitempty"` - LastCheck time.Time `json:"lastCheck,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` + LastCheck time.Time `json:"lastCheck,omitzero"` + CreatedAt time.Time `json:"created_at,omitzero"` + UpdatedAt time.Time `json:"updated_at,omitzero"` } type Record struct { diff --git a/providers/dns/conoha/conoha_test.go b/providers/dns/conoha/conoha_test.go index 9db5ba79f..c1c445d48 100644 --- a/providers/dns/conoha/conoha_test.go +++ b/providers/dns/conoha/conoha_test.go @@ -72,6 +72,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -155,6 +156,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -168,6 +170,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/conoha/internal/client.go b/providers/dns/conoha/internal/client.go index 60d7fd6dc..2f039489b 100644 --- a/providers/dns/conoha/internal/client.go +++ b/providers/dns/conoha/internal/client.go @@ -124,6 +124,7 @@ func (c *Client) createRecord(ctx context.Context, domainID string, record Recor } newRecord := &Record{} + err = c.do(req, newRecord) if err != nil { return nil, err diff --git a/providers/dns/conoha/internal/client_test.go b/providers/dns/conoha/internal/client_test.go index 0b9242c08..5e06ffc1d 100644 --- a/providers/dns/conoha/internal/client_test.go +++ b/providers/dns/conoha/internal/client_test.go @@ -97,6 +97,7 @@ func TestClient_CreateRecord(t *testing.T) { http.Error(rw, err.Error(), http.StatusBadRequest) return } + defer func() { _ = req.Body.Close() }() if string(bytes.TrimSpace(raw)) != `{"name":"lego.com.","type":"TXT","data":"txtTXTtxt","ttl":300}` { @@ -109,6 +110,7 @@ func TestClient_CreateRecord(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) return } + defer func() { _ = file.Close() }() _, _ = io.Copy(rw, file) diff --git a/providers/dns/conohav3/conohav3_test.go b/providers/dns/conohav3/conohav3_test.go index 7bba8f0b5..d68ea3ebb 100644 --- a/providers/dns/conohav3/conohav3_test.go +++ b/providers/dns/conohav3/conohav3_test.go @@ -72,6 +72,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -155,6 +156,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -168,6 +170,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/conohav3/internal/client.go b/providers/dns/conohav3/internal/client.go index fcbd7f5ac..2a9e7c2bc 100644 --- a/providers/dns/conohav3/internal/client.go +++ b/providers/dns/conohav3/internal/client.go @@ -124,6 +124,7 @@ func (c *Client) createRecord(ctx context.Context, domainID string, record Recor } newRecord := &Record{} + err = c.do(req, newRecord) if err != nil { return nil, err diff --git a/providers/dns/conohav3/internal/client_test.go b/providers/dns/conohav3/internal/client_test.go index babdadf7e..66babae49 100644 --- a/providers/dns/conohav3/internal/client_test.go +++ b/providers/dns/conohav3/internal/client_test.go @@ -98,6 +98,7 @@ func TestClient_CreateRecord(t *testing.T) { http.Error(rw, err.Error(), http.StatusBadRequest) return } + defer func() { _ = req.Body.Close() }() if string(bytes.TrimSpace(raw)) != `{"name":"lego.com.","type":"TXT","data":"txtTXTtxt","ttl":300}` { @@ -110,6 +111,7 @@ func TestClient_CreateRecord(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) return } + defer func() { _ = file.Close() }() _, _ = io.Copy(rw, file) diff --git a/providers/dns/conohav3/internal/identity.go b/providers/dns/conohav3/internal/identity.go index 3bb7355ae..6a9ad7f1e 100644 --- a/providers/dns/conohav3/internal/identity.go +++ b/providers/dns/conohav3/internal/identity.go @@ -53,6 +53,7 @@ func (c *Identifier) do(req *http.Request) (string, error) { if err != nil { return "", errutils.NewHTTPDoError(req, err) } + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusCreated { diff --git a/providers/dns/constellix/constellix.go b/providers/dns/constellix/constellix.go index 66543903a..777e93308 100644 --- a/providers/dns/constellix/constellix.go +++ b/providers/dns/constellix/constellix.go @@ -200,6 +200,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("constellix: failed to delete TXT records: %w", err) } + return nil } diff --git a/providers/dns/constellix/constellix_test.go b/providers/dns/constellix/constellix_test.go index e3a30caca..e38258292 100644 --- a/providers/dns/constellix/constellix_test.go +++ b/providers/dns/constellix/constellix_test.go @@ -57,6 +57,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -129,6 +130,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -142,6 +144,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/constellix/internal/auth.go b/providers/dns/constellix/internal/auth.go index 1a136012d..9193572eb 100644 --- a/providers/dns/constellix/internal/auth.go +++ b/providers/dns/constellix/internal/auth.go @@ -28,6 +28,7 @@ func NewTokenTransport(apiKey, secretKey string) (*TokenTransport, error) { if apiKey == "" { return nil, errors.New("credentials missing: API key") } + if secretKey == "" { return nil, errors.New("credentials missing: secret key") } @@ -57,6 +58,7 @@ func (t *TokenTransport) transport() http.RoundTripper { if t.Transport != nil { return t.Transport } + return http.DefaultTransport } diff --git a/providers/dns/constellix/internal/domains.go b/providers/dns/constellix/internal/domains.go index 485f0d537..fa7027f55 100644 --- a/providers/dns/constellix/internal/domains.go +++ b/providers/dns/constellix/internal/domains.go @@ -30,10 +30,12 @@ func (s *DomainService) GetAll(ctx context.Context, params *PaginationParameters if errQ != nil { return nil, errQ } + req.URL.RawQuery = v.Encode() } var domains []Domain + err = s.client.do(req, &domains) if err != nil { return nil, err @@ -78,6 +80,7 @@ func (s *DomainService) Search(ctx context.Context, filter searchFilter, value s req.URL.RawQuery = query.Encode() var domains []Domain + err = s.client.do(req, &domains) if err != nil { var nf *NotFound diff --git a/providers/dns/constellix/internal/txtrecords.go b/providers/dns/constellix/internal/txtrecords.go index 7880da4d2..bd00d84b7 100644 --- a/providers/dns/constellix/internal/txtrecords.go +++ b/providers/dns/constellix/internal/txtrecords.go @@ -32,6 +32,7 @@ func (s *TxtRecordService) Create(ctx context.Context, domainID int64, record Re } var records []Record + err = s.client.do(req, &records) if err != nil { return nil, err @@ -54,6 +55,7 @@ func (s *TxtRecordService) GetAll(ctx context.Context, domainID int64) ([]Record } var records []Record + err = s.client.do(req, &records) if err != nil { return nil, err @@ -76,6 +78,7 @@ func (s *TxtRecordService) Get(ctx context.Context, domainID, recordID int64) (* } var records Record + err = s.client.do(req, &records) if err != nil { return nil, err @@ -103,6 +106,7 @@ func (s *TxtRecordService) Update(ctx context.Context, domainID, recordID int64, } var msg SuccessMessage + err = s.client.do(req, &msg) if err != nil { return nil, err @@ -125,6 +129,7 @@ func (s *TxtRecordService) Delete(ctx context.Context, domainID, recordID int64) } var msg *SuccessMessage + err = s.client.do(req, &msg) if err != nil { return nil, err diff --git a/providers/dns/corenetworks/corenetworks_test.go b/providers/dns/corenetworks/corenetworks_test.go index 3cd80f88d..911693468 100644 --- a/providers/dns/corenetworks/corenetworks_test.go +++ b/providers/dns/corenetworks/corenetworks_test.go @@ -43,6 +43,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -111,6 +112,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -124,6 +126,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/corenetworks/internal/client.go b/providers/dns/corenetworks/internal/client.go index ea2d7efa2..bdc17f2c1 100644 --- a/providers/dns/corenetworks/internal/client.go +++ b/providers/dns/corenetworks/internal/client.go @@ -47,6 +47,7 @@ func (c *Client) ListZone(ctx context.Context) ([]Zone, error) { } var zones []Zone + err = c.do(req, &zones) if err != nil { return nil, err @@ -66,6 +67,7 @@ func (c *Client) GetZoneDetails(ctx context.Context, zone string) (*ZoneDetails, } var details ZoneDetails + err = c.do(req, &details) if err != nil { return nil, err @@ -85,6 +87,7 @@ func (c *Client) ListRecords(ctx context.Context, zone string) ([]Record, error) } var records []Record + err = c.do(req, &records) if err != nil { return nil, err diff --git a/providers/dns/corenetworks/internal/identity.go b/providers/dns/corenetworks/internal/identity.go index 8f5a2ee9a..a7e7448c0 100644 --- a/providers/dns/corenetworks/internal/identity.go +++ b/providers/dns/corenetworks/internal/identity.go @@ -22,6 +22,7 @@ func (c *Client) CreateAuthenticationToken(ctx context.Context) (*Token, error) } var token Token + err = c.do(req, &token) if err != nil { return nil, err diff --git a/providers/dns/cpanel/cpanel.go b/providers/dns/cpanel/cpanel.go index a61a05c81..f335c0a8c 100644 --- a/providers/dns/cpanel/cpanel.go +++ b/providers/dns/cpanel/cpanel.go @@ -147,12 +147,16 @@ func (d *DNSProvider) Present(domain, _, keyAuth string) error { valueB64 := base64.StdEncoding.EncodeToString([]byte(info.Value)) - var found bool - var existingRecord shared.ZoneRecord + var ( + found bool + existingRecord shared.ZoneRecord + ) + for _, record := range zoneInfo { if slices.Contains(record.DataB64, valueB64) { existingRecord = record found = true + break } } @@ -221,12 +225,16 @@ func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { valueB64 := base64.StdEncoding.EncodeToString([]byte(info.Value)) - var found bool - var existingRecord shared.ZoneRecord + var ( + found bool + existingRecord shared.ZoneRecord + ) + for _, record := range zoneInfo { if slices.Contains(record.DataB64, valueB64) { existingRecord = record found = true + break } } @@ -236,6 +244,7 @@ func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { } var newData []string + for _, dataB64 := range existingRecord.DataB64 { if dataB64 == valueB64 { continue @@ -292,6 +301,7 @@ func getZoneSerial(zoneFqdn string, zoneInfo []shared.ZoneRecord) (uint32, error } var newSerial uint32 + _, err = fmt.Sscan(string(data), &newSerial) if err != nil { return 0, fmt.Errorf("decode serial DNameB64, invalid serial value %q: %w", string(data), err) diff --git a/providers/dns/cpanel/cpanel_test.go b/providers/dns/cpanel/cpanel_test.go index 614b9e1c7..5d85b8b5b 100644 --- a/providers/dns/cpanel/cpanel_test.go +++ b/providers/dns/cpanel/cpanel_test.go @@ -75,6 +75,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -282,6 +283,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -295,6 +297,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/cpanel/internal/cpanel/types.go b/providers/dns/cpanel/internal/cpanel/types.go index cb4dbd535..0a3053647 100644 --- a/providers/dns/cpanel/internal/cpanel/types.go +++ b/providers/dns/cpanel/internal/cpanel/types.go @@ -6,7 +6,7 @@ import ( ) type APIResponse[T any] struct { - Metadata Metadata `json:"metadata,omitempty"` + Metadata Metadata `json:"metadata"` Data T `json:"data,omitempty"` Status int `json:"status,omitempty"` diff --git a/providers/dns/cpanel/internal/whm/types.go b/providers/dns/cpanel/internal/whm/types.go index f1884a04d..d0604a565 100644 --- a/providers/dns/cpanel/internal/whm/types.go +++ b/providers/dns/cpanel/internal/whm/types.go @@ -7,7 +7,7 @@ import ( ) type APIResponse[T any] struct { - Metadata Metadata `json:"metadata,omitempty"` + Metadata Metadata `json:"metadata"` Data T `json:"data,omitempty"` } diff --git a/providers/dns/derak/derak.go b/providers/dns/derak/derak.go index 8a05d7608..78165b936 100644 --- a/providers/dns/derak/derak.go +++ b/providers/dns/derak/derak.go @@ -163,6 +163,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("derak: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/derak/derak_test.go b/providers/dns/derak/derak_test.go index e58cfb6c1..b83eb2c8c 100644 --- a/providers/dns/derak/derak_test.go +++ b/providers/dns/derak/derak_test.go @@ -33,6 +33,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -92,6 +93,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -105,6 +107,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/derak/internal/client.go b/providers/dns/derak/internal/client.go index a7c11f895..4352e198b 100644 --- a/providers/dns/derak/internal/client.go +++ b/providers/dns/derak/internal/client.go @@ -44,6 +44,7 @@ func (c *Client) GetRecords(ctx context.Context, zoneID string, params *GetRecor if err != nil { return nil, err } + endpoint.RawQuery = v.Encode() req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -52,6 +53,7 @@ func (c *Client) GetRecords(ctx context.Context, zoneID string, params *GetRecor } response := &GetRecordsResponse{} + err = c.do(req, response) if err != nil { return nil, err @@ -70,6 +72,7 @@ func (c *Client) GetRecord(ctx context.Context, zoneID, recordID string) (*Recor } response := &Record{} + err = c.do(req, response) if err != nil { return nil, err @@ -88,6 +91,7 @@ func (c *Client) CreateRecord(ctx context.Context, zoneID string, record Record) } response := &Record{} + err = c.do(req, response) if err != nil { return nil, err @@ -106,6 +110,7 @@ func (c *Client) EditRecord(ctx context.Context, zoneID, recordID string, record } response := &Record{} + err = c.do(req, response) if err != nil { return nil, err @@ -147,6 +152,7 @@ func (c *Client) GetZones(ctx context.Context) ([]Zone, error) { } response := &APIResponse[[]Zone]{} + err = c.do(req, response) if err != nil { return nil, err @@ -221,6 +227,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response APIResponse[any] + err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/derak/internal/types.go b/providers/dns/derak/internal/types.go index 15ed00617..02116314f 100644 --- a/providers/dns/derak/internal/types.go +++ b/providers/dns/derak/internal/types.go @@ -46,7 +46,7 @@ type Zone struct { HumanReadable string `json:"humanReadable,omitempty"` Serial string `json:"serial,omitempty"` CreationTime int64 `json:"creationTime,omitempty"` - CreationTimeDate time.Time `json:"creationTimeDate,omitempty"` + CreationTimeDate time.Time `json:"creationTimeDate,omitzero"` Status string `json:"status,omitempty"` IsMoved bool `json:"is_moved,omitempty"` Paused bool `json:"paused,omitempty"` diff --git a/providers/dns/desec/desec.go b/providers/dns/desec/desec.go index 08aebc2b4..75618b943 100644 --- a/providers/dns/desec/desec.go +++ b/providers/dns/desec/desec.go @@ -184,6 +184,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } records := make([]string, 0) + for _, record := range rrSet.Records { if record != fmt.Sprintf(`%q`, info.Value) { records = append(records, record) diff --git a/providers/dns/desec/desec_test.go b/providers/dns/desec/desec_test.go index f91f9e82a..93d9bd010 100644 --- a/providers/dns/desec/desec_test.go +++ b/providers/dns/desec/desec_test.go @@ -36,6 +36,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -93,6 +94,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -106,6 +108,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/designate/designate.go b/providers/dns/designate/designate.go index c58baaace..47c8ad8f1 100644 --- a/providers/dns/designate/designate.go +++ b/providers/dns/designate/designate.go @@ -85,7 +85,6 @@ func NewDNSProvider() (*DNSProvider, error) { opts, erro := clientconfig.AuthOptions(&clientconfig.ClientOpts{ Cloud: val[EnvCloud], }) - if erro != nil { return nil, fmt.Errorf("designate: %w", erro) } @@ -202,6 +201,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("designate: error for %s in CleanUp: %w", info.EffectiveFQDN, err) } + return nil } @@ -241,6 +241,7 @@ func (d *DNSProvider) updateRecord(record *recordsets.RecordSet, value string) e } result := recordsets.Update(d.client, record.ZoneID, record.ID, updateOpts) + return result.Err } diff --git a/providers/dns/designate/designate_test.go b/providers/dns/designate/designate_test.go index 1045baa95..e5edf81f8 100644 --- a/providers/dns/designate/designate_test.go +++ b/providers/dns/designate/designate_test.go @@ -105,6 +105,7 @@ func TestNewDNSProvider_fromEnv(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -192,6 +193,7 @@ func TestNewDNSProvider_fromCloud(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(map[string]string{ @@ -331,6 +333,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -344,6 +347,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/digitalocean/digitalocean.go b/providers/dns/digitalocean/digitalocean.go index f7ae68d60..26c6fb9d4 100644 --- a/providers/dns/digitalocean/digitalocean.go +++ b/providers/dns/digitalocean/digitalocean.go @@ -97,6 +97,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config.BaseURL != "" { var err error + client.BaseURL, err = url.Parse(config.BaseURL) if err != nil { return nil, fmt.Errorf("digitalocean: %w", err) @@ -152,6 +153,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("digitalocean: unknown record ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/digitalocean/digitalocean_test.go b/providers/dns/digitalocean/digitalocean_test.go index 13f8b522d..d066e12db 100644 --- a/providers/dns/digitalocean/digitalocean_test.go +++ b/providers/dns/digitalocean/digitalocean_test.go @@ -51,6 +51,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) diff --git a/providers/dns/digitalocean/internal/client.go b/providers/dns/digitalocean/internal/client.go index e7dd181b2..395de478c 100644 --- a/providers/dns/digitalocean/internal/client.go +++ b/providers/dns/digitalocean/internal/client.go @@ -45,6 +45,7 @@ func (c *Client) AddTxtRecord(ctx context.Context, zone string, record Record) ( } respData := &TxtRecordResponse{} + err = c.do(req, respData) if err != nil { return nil, err @@ -120,6 +121,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errInfo APIError + err := json.Unmarshal(raw, &errInfo) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/directadmin/directadmin_test.go b/providers/dns/directadmin/directadmin_test.go index 10c079f73..aed3ba505 100644 --- a/providers/dns/directadmin/directadmin_test.go +++ b/providers/dns/directadmin/directadmin_test.go @@ -59,6 +59,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -135,6 +136,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -148,6 +150,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/directadmin/internal/client.go b/providers/dns/directadmin/internal/client.go index bf6d64371..64409a79d 100644 --- a/providers/dns/directadmin/internal/client.go +++ b/providers/dns/directadmin/internal/client.go @@ -94,6 +94,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errInfo APIError + err := json.Unmarshal(raw, &errInfo) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/dns_providers_test.go b/providers/dns/dns_providers_test.go index 1f39e2bdd..3b82784b4 100644 --- a/providers/dns/dns_providers_test.go +++ b/providers/dns/dns_providers_test.go @@ -13,6 +13,7 @@ var envTest = tester.NewEnvTest("EXEC_PATH") func TestKnownDNSProviderSuccess(t *testing.T) { defer envTest.RestoreEnv() + envTest.Apply(map[string]string{ "EXEC_PATH": "abc", }) @@ -26,6 +27,7 @@ func TestKnownDNSProviderSuccess(t *testing.T) { func TestKnownDNSProviderError(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() provider, err := NewDNSChallengeProviderByName("exec") diff --git a/providers/dns/dnshomede/dnshomede.go b/providers/dns/dnshomede/dnshomede.go index e3d56f098..c76ed6de2 100644 --- a/providers/dns/dnshomede/dnshomede.go +++ b/providers/dns/dnshomede/dnshomede.go @@ -57,6 +57,7 @@ type DNSProvider struct { // Credentials must be passed in the environment variable: DNSHOMEDE_CREDENTIALS. func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() + values, err := env.Get(EnvCredentials) if err != nil { return nil, fmt.Errorf("dnshomede: %w", err) diff --git a/providers/dns/dnshomede/dnshomede_test.go b/providers/dns/dnshomede/dnshomede_test.go index bdb42f172..5035a7837 100644 --- a/providers/dns/dnshomede/dnshomede_test.go +++ b/providers/dns/dnshomede/dnshomede_test.go @@ -69,6 +69,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -144,6 +145,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -157,6 +159,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dnsimple/dnsimple.go b/providers/dns/dnsimple/dnsimple.go index 4b7df0943..adf7d48e2 100644 --- a/providers/dns/dnsimple/dnsimple.go +++ b/providers/dns/dnsimple/dnsimple.go @@ -145,6 +145,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var lastErr error + for _, rec := range records { _, err := d.client.Zones.DeleteRecord(ctx, accountID, rec.ZoneID, rec.ID) if err != nil { diff --git a/providers/dns/dnsimple/dnsimple_test.go b/providers/dns/dnsimple/dnsimple_test.go index c07f965b4..2a52dd2de 100644 --- a/providers/dns/dnsimple/dnsimple_test.go +++ b/providers/dns/dnsimple/dnsimple_test.go @@ -51,6 +51,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) diff --git a/providers/dns/dnsmadeeasy/dnsmadeeasy.go b/providers/dns/dnsmadeeasy/dnsmadeeasy.go index 7d2f92726..69f2096fb 100644 --- a/providers/dns/dnsmadeeasy/dnsmadeeasy.go +++ b/providers/dns/dnsmadeeasy/dnsmadeeasy.go @@ -155,6 +155,7 @@ func (d *DNSProvider) Present(domainName, token, keyAuth string) error { if err != nil { return fmt.Errorf("dnsmadeeasy: unable to create record for %s: %w", name, err) } + return nil } @@ -177,6 +178,7 @@ func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error { // find matching records name := strings.Replace(info.EffectiveFQDN, "."+authZone, "", 1) + records, err := d.client.GetRecords(ctx, domain, name, "TXT") if err != nil { return fmt.Errorf("dnsmadeeasy: unable to get records for domain %s: %w", domain.Name, err) @@ -184,6 +186,7 @@ func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error { // delete records var lastError error + for _, record := range *records { err = d.client.DeleteRecord(ctx, record) if err != nil { diff --git a/providers/dns/dnsmadeeasy/dnsmadeeasy_test.go b/providers/dns/dnsmadeeasy/dnsmadeeasy_test.go index 5c508e60d..f6fc2e273 100644 --- a/providers/dns/dnsmadeeasy/dnsmadeeasy_test.go +++ b/providers/dns/dnsmadeeasy/dnsmadeeasy_test.go @@ -59,6 +59,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -135,6 +136,7 @@ func TestLivePresentAndCleanup(t *testing.T) { os.Setenv(EnvSandbox, "true") envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dnsmadeeasy/internal/client.go b/providers/dns/dnsmadeeasy/internal/client.go index cb6f9d2cb..7963ad614 100644 --- a/providers/dns/dnsmadeeasy/internal/client.go +++ b/providers/dns/dnsmadeeasy/internal/client.go @@ -68,6 +68,7 @@ func (c *Client) GetDomain(ctx context.Context, authZone string) (*Domain, error } domain := &Domain{} + err = c.do(req, domain) if err != nil { return nil, err @@ -91,6 +92,7 @@ func (c *Client) GetRecords(ctx context.Context, domain *Domain, recordName, rec } records := &recordsResponse{} + err = c.do(req, records) if err != nil { return nil, err @@ -172,10 +174,12 @@ func (c *Client) sign(req *http.Request, timestamp string) error { func computeHMAC(message, secret string) (string, error) { key := []byte(secret) h := hmac.New(sha1.New, key) + _, err := h.Write([]byte(message)) if err != nil { return "", err } + return hex.EncodeToString(h.Sum(nil)), nil } diff --git a/providers/dns/dnspod/dnspod.go b/providers/dns/dnspod/dnspod.go index 46893fe5a..c9376b956 100644 --- a/providers/dns/dnspod/dnspod.go +++ b/providers/dns/dnspod/dnspod.go @@ -135,6 +135,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return err } } + return nil } @@ -156,6 +157,7 @@ func (d *DNSProvider) getHostedZone(domain string) (string, string, error) { } var hostedZone dnspod.Domain + for _, zone := range zones { if zone.Name == dns01.UnFqdn(authZone) { hostedZone = zone @@ -191,6 +193,7 @@ func (d *DNSProvider) findTxtRecords(fqdn, zoneID, zoneName string) ([]dnspod.Re } var records []dnspod.Record + result, _, err := d.client.Records.List(zoneID, subDomain) if err != nil { return records, fmt.Errorf("API call has failed: %w", err) diff --git a/providers/dns/dnspod/dnspod_test.go b/providers/dns/dnspod/dnspod_test.go index 640ec34c6..5d339353a 100644 --- a/providers/dns/dnspod/dnspod_test.go +++ b/providers/dns/dnspod/dnspod_test.go @@ -37,6 +37,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -96,6 +97,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -109,6 +111,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dode/dode_test.go b/providers/dns/dode/dode_test.go index 3d8e9395a..fefcc79b1 100644 --- a/providers/dns/dode/dode_test.go +++ b/providers/dns/dode/dode_test.go @@ -36,6 +36,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -93,6 +94,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -106,6 +108,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dode/internal/client.go b/providers/dns/dode/internal/client.go index bbd98b399..6824e7c48 100644 --- a/providers/dns/dode/internal/client.go +++ b/providers/dns/dode/internal/client.go @@ -70,6 +70,7 @@ func (c *Client) UpdateTxtRecord(ctx context.Context, fqdn, txt string, clearRec } var response apiResponse + err = json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/domeneshop/domeneshop_test.go b/providers/dns/domeneshop/domeneshop_test.go index 389975bca..086efd44a 100644 --- a/providers/dns/domeneshop/domeneshop_test.go +++ b/providers/dns/domeneshop/domeneshop_test.go @@ -57,6 +57,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -130,6 +131,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -143,6 +145,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dreamhost/dreamhost.go b/providers/dns/dreamhost/dreamhost.go index 5ad2611d9..8663e18ce 100644 --- a/providers/dns/dreamhost/dreamhost.go +++ b/providers/dns/dreamhost/dreamhost.go @@ -99,6 +99,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) + err := d.client.AddRecord(context.Background(), dns01.UnFqdn(info.EffectiveFQDN), info.Value) if err != nil { return fmt.Errorf("dreamhost: %w", err) diff --git a/providers/dns/dreamhost/dreamhost_test.go b/providers/dns/dreamhost/dreamhost_test.go index 39b092c6f..5af0b892d 100644 --- a/providers/dns/dreamhost/dreamhost_test.go +++ b/providers/dns/dreamhost/dreamhost_test.go @@ -57,6 +57,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -160,6 +161,7 @@ func TestLivePresentAndCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dreamhost/internal/client.go b/providers/dns/dreamhost/internal/client.go index dee808ac8..02b33ad57 100644 --- a/providers/dns/dreamhost/internal/client.go +++ b/providers/dns/dreamhost/internal/client.go @@ -101,6 +101,7 @@ func (c *Client) updateTxtRecord(ctx context.Context, endpoint *url.URL) error { } var response apiResponse + err = json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/duckdns/duckdns_test.go b/providers/dns/duckdns/duckdns_test.go index b89966a36..769513fbf 100644 --- a/providers/dns/duckdns/duckdns_test.go +++ b/providers/dns/duckdns/duckdns_test.go @@ -37,6 +37,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -94,6 +95,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -107,6 +109,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/duckdns/internal/client.go b/providers/dns/duckdns/internal/client.go index ced5bf187..c5d7ef97c 100644 --- a/providers/dns/duckdns/internal/client.go +++ b/providers/dns/duckdns/internal/client.go @@ -81,6 +81,7 @@ func (c *Client) UpdateTxtRecord(ctx context.Context, domain, txt string, clearR if body != "OK" { return fmt.Errorf("request to change TXT record for DuckDNS returned the following result (%s) this does not match expectation (OK) used url [%s]", body, endpoint) } + return nil } @@ -98,6 +99,7 @@ func getMainDomain(domain string) string { } firstSubDomainIndex := split[len(split)-3] + return domain[firstSubDomainIndex:] } diff --git a/providers/dns/dyn/dyn_test.go b/providers/dns/dyn/dyn_test.go index 25f1f5614..5b4d1c6b6 100644 --- a/providers/dns/dyn/dyn_test.go +++ b/providers/dns/dyn/dyn_test.go @@ -71,6 +71,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -155,6 +156,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -168,6 +170,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dyn/internal/client.go b/providers/dns/dyn/internal/client.go index 83a1bfc0f..a54113eec 100644 --- a/providers/dns/dyn/internal/client.go +++ b/providers/dns/dyn/internal/client.go @@ -127,6 +127,7 @@ func (c *Client) do(req *http.Request) (*APIResponse, error) { } var response APIResponse + err = json.Unmarshal(raw, &response) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/dyn/internal/session.go b/providers/dns/dyn/internal/session.go index 647080fa8..088510152 100644 --- a/providers/dns/dyn/internal/session.go +++ b/providers/dns/dyn/internal/session.go @@ -33,6 +33,7 @@ func (c *Client) login(ctx context.Context) (session, error) { } var s session + err = json.Unmarshal(dynRes.Data, &s) if err != nil { return session{}, errutils.NewUnmarshalError(req, http.StatusOK, dynRes.Data, err) diff --git a/providers/dns/dyndnsfree/dyndnsfree.go b/providers/dns/dyndnsfree/dyndnsfree.go index 13a192793..09be2bfbd 100644 --- a/providers/dns/dyndnsfree/dyndnsfree.go +++ b/providers/dns/dyndnsfree/dyndnsfree.go @@ -110,7 +110,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // Records are deleted automatically. - return nil } diff --git a/providers/dns/dyndnsfree/dyndnsfree_test.go b/providers/dns/dyndnsfree/dyndnsfree_test.go index cb063a029..0b03bd27f 100644 --- a/providers/dns/dyndnsfree/dyndnsfree_test.go +++ b/providers/dns/dyndnsfree/dyndnsfree_test.go @@ -50,6 +50,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -122,6 +123,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,6 +137,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dynu/dynu_test.go b/providers/dns/dynu/dynu_test.go index fe2c22dfb..ffc7c3a00 100644 --- a/providers/dns/dynu/dynu_test.go +++ b/providers/dns/dynu/dynu_test.go @@ -38,6 +38,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -96,6 +97,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -109,6 +111,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/dynu/internal/auth.go b/providers/dns/dynu/internal/auth.go index 7a21a10e8..0a91445d2 100644 --- a/providers/dns/dynu/internal/auth.go +++ b/providers/dns/dynu/internal/auth.go @@ -46,6 +46,7 @@ func (t *TokenTransport) transport() http.RoundTripper { if t.Transport != nil { return t.Transport } + return http.DefaultTransport } diff --git a/providers/dns/dynu/internal/client.go b/providers/dns/dynu/internal/client.go index f7e90c489..59e90d592 100644 --- a/providers/dns/dynu/internal/client.go +++ b/providers/dns/dynu/internal/client.go @@ -43,6 +43,7 @@ func (c *Client) GetRecords(ctx context.Context, hostname, recordType string) ([ endpoint.RawQuery = query.Encode() apiResp := RecordsResponse{} + err := c.doRetry(ctx, http.MethodGet, endpoint.String(), nil, &apiResp) if err != nil { return nil, err @@ -65,6 +66,7 @@ func (c *Client) AddNewRecord(ctx context.Context, domainID int64, record DNSRec } apiResp := RecordResponse{} + err = c.doRetry(ctx, http.MethodPost, endpoint.String(), reqBody, &apiResp) if err != nil { return err @@ -82,6 +84,7 @@ func (c *Client) DeleteRecord(ctx context.Context, domainID, recordID int64) err endpoint := c.baseURL.JoinPath("dns", strconv.FormatInt(domainID, 10), "record", strconv.FormatInt(recordID, 10)) apiResp := APIException{} + err := c.doRetry(ctx, http.MethodDelete, endpoint.String(), nil, &apiResp) if err != nil { return err @@ -99,6 +102,7 @@ func (c *Client) GetRootDomain(ctx context.Context, hostname string) (*DNSHostna endpoint := c.baseURL.JoinPath("dns", "getroot", hostname) apiResp := DNSHostname{} + err := c.doRetry(ctx, http.MethodGet, endpoint.String(), nil, &apiResp) if err != nil { return nil, err diff --git a/providers/dns/easydns/easydns.go b/providers/dns/easydns/easydns.go index c1119f3cc..ae0a0c3b8 100644 --- a/providers/dns/easydns/easydns.go +++ b/providers/dns/easydns/easydns.go @@ -78,6 +78,7 @@ func NewDNSProvider() (*DNSProvider, error) { if err != nil { return nil, fmt.Errorf("easydns: %w", err) } + config.Endpoint = endpoint values, err := env.Get(EnvToken, EnvKey) @@ -192,6 +193,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() defer delete(d.recordIDs, key) + d.recordIDsMu.Unlock() if err != nil { diff --git a/providers/dns/easydns/easydns_test.go b/providers/dns/easydns/easydns_test.go index 9a11ef6cc..5517928d7 100644 --- a/providers/dns/easydns/easydns_test.go +++ b/providers/dns/easydns/easydns_test.go @@ -76,6 +76,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -314,6 +315,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -327,6 +329,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/easydns/internal/client.go b/providers/dns/easydns/internal/client.go index c044d7e7f..33d7c724e 100644 --- a/providers/dns/easydns/internal/client.go +++ b/providers/dns/easydns/internal/client.go @@ -46,6 +46,7 @@ func (c *Client) ListZones(ctx context.Context, domain string) ([]ZoneRecord, er } response := &apiResponse[[]ZoneRecord]{} + err = c.do(req, response) if err != nil { return nil, err @@ -67,6 +68,7 @@ func (c *Client) AddRecord(ctx context.Context, domain string, record ZoneRecord } response := &apiResponse[*ZoneRecord]{} + err = c.do(req, response) if err != nil { return "", err diff --git a/providers/dns/edgedns/edgedns.go b/providers/dns/edgedns/edgedns.go index 1a92a967f..b5f4b99c9 100644 --- a/providers/dns/edgedns/edgedns.go +++ b/providers/dns/edgedns/edgedns.go @@ -53,6 +53,7 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { *edgegrid.Config + PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -228,6 +229,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if isNotFound(err) { return nil } + return fmt.Errorf("edgedns: %w", err) } @@ -299,16 +301,19 @@ func isNotFound(err error) bool { } var e *edgegriddns.Error + return errors.As(err, &e) && e.StatusCode == http.StatusNotFound } func filterRData(existingRec *edgegriddns.GetRecordResponse, info dns01.ChallengeInfo) []string { var newRData []string + for _, val := range existingRec.Target { val = strings.Trim(val, `"`) if val == info.Value { continue } + newRData = append(newRData, val) } diff --git a/providers/dns/edgedns/edgedns_integration_test.go b/providers/dns/edgedns/edgedns_integration_test.go index c740aba4b..d20b8e5aa 100644 --- a/providers/dns/edgedns/edgedns_integration_test.go +++ b/providers/dns/edgedns/edgedns_integration_test.go @@ -19,6 +19,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -36,6 +37,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/edgedns/edgedns_test.go b/providers/dns/edgedns/edgedns_test.go index 2dafe9c82..a64efd6e2 100644 --- a/providers/dns/edgedns/edgedns_test.go +++ b/providers/dns/edgedns/edgedns_test.go @@ -143,6 +143,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() if test.envVars == nil { @@ -209,6 +210,7 @@ func TestNewDefaultConfig(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) diff --git a/providers/dns/edgeone/edgeone.go b/providers/dns/edgeone/edgeone.go index 88a795c69..3402122bb 100644 --- a/providers/dns/edgeone/edgeone.go +++ b/providers/dns/edgeone/edgeone.go @@ -165,6 +165,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("edgeone: unknown record ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/edgeone/edgeone_test.go b/providers/dns/edgeone/edgeone_test.go index e1c004d67..1c92118dc 100644 --- a/providers/dns/edgeone/edgeone_test.go +++ b/providers/dns/edgeone/edgeone_test.go @@ -54,6 +54,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -126,6 +127,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -139,6 +141,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/edgeone/wrapper.go b/providers/dns/edgeone/wrapper.go index 0d207f030..c3e9d965b 100644 --- a/providers/dns/edgeone/wrapper.go +++ b/providers/dns/edgeone/wrapper.go @@ -35,6 +35,7 @@ func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*teo.Zo } var hostedZone *teo.Zone + for _, zone := range domains { unfqdn := dns01.UnFqdn(authZone) if ptr.Deref(zone.ZoneName) == unfqdn { diff --git a/providers/dns/efficientip/efficientip.go b/providers/dns/efficientip/efficientip.go index d99710920..81b4530b7 100644 --- a/providers/dns/efficientip/efficientip.go +++ b/providers/dns/efficientip/efficientip.go @@ -92,12 +92,15 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config.Username == "" { return nil, errors.New("efficientip: missing username") } + if config.Password == "" { return nil, errors.New("efficientip: missing password") } + if config.Hostname == "" { return nil, errors.New("efficientip: missing hostname") } + if config.DNSName == "" { return nil, errors.New("efficientip: missing dnsname") } diff --git a/providers/dns/efficientip/efficientip_test.go b/providers/dns/efficientip/efficientip_test.go index 3ee2da777..c2751a79b 100644 --- a/providers/dns/efficientip/efficientip_test.go +++ b/providers/dns/efficientip/efficientip_test.go @@ -83,6 +83,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -178,6 +179,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -191,6 +193,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/efficientip/internal/client.go b/providers/dns/efficientip/internal/client.go index cc26c5412..5ccdf3973 100644 --- a/providers/dns/efficientip/internal/client.go +++ b/providers/dns/efficientip/internal/client.go @@ -108,6 +108,7 @@ func (c *Client) DeleteRecord(ctx context.Context, params DeleteInputParameters) if err != nil { return nil, fmt.Errorf("query parameters: %w", err) } + endpoint.RawQuery = v.Encode() req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) @@ -200,6 +201,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response APIError + err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/epik/epik_test.go b/providers/dns/epik/epik_test.go index c0cd3d43b..b8b3c5c43 100644 --- a/providers/dns/epik/epik_test.go +++ b/providers/dns/epik/epik_test.go @@ -33,6 +33,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -92,6 +93,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -105,6 +107,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/epik/internal/client.go b/providers/dns/epik/internal/client.go index 70fb42fa9..2c3373953 100644 --- a/providers/dns/epik/internal/client.go +++ b/providers/dns/epik/internal/client.go @@ -46,6 +46,7 @@ func (c *Client) GetDNSRecords(ctx context.Context, domain string) ([]Record, er } var data GetDNSRecordResponse + err = c.do(req, &data) if err != nil { return nil, err @@ -67,6 +68,7 @@ func (c *Client) CreateHostRecord(ctx context.Context, domain string, record Rec } var data Data + err = c.do(req, &data) if err != nil { return nil, err @@ -89,6 +91,7 @@ func (c *Client) RemoveHostRecord(ctx context.Context, domain, recordID string) } var data Data + err = c.do(req, &data) if err != nil { return nil, err @@ -165,6 +168,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var apiErr APIError + err := json.Unmarshal(raw, &apiErr) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/exec/exec_test.go b/providers/dns/exec/exec_test.go index 3a2edbbf4..c1b6da55e 100644 --- a/providers/dns/exec/exec_test.go +++ b/providers/dns/exec/exec_test.go @@ -14,6 +14,7 @@ import ( func TestDNSProvider_Present(t *testing.T) { backupLogger := log.Logger + defer func() { log.Logger = backupLogger }() @@ -62,6 +63,7 @@ func TestDNSProvider_Present(t *testing.T) { } var message string + logRecorder.On("Println", mock.Anything).Run(func(args mock.Arguments) { message = args.String(0) fmt.Fprintln(os.Stdout, "XXX", message) @@ -87,6 +89,7 @@ func TestDNSProvider_Present(t *testing.T) { func TestDNSProvider_CleanUp(t *testing.T) { backupLogger := log.Logger + defer func() { log.Logger = backupLogger }() @@ -135,6 +138,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { } var message string + logRecorder.On("Println", mock.Anything).Run(func(args mock.Arguments) { message = args.String(0) fmt.Fprintln(os.Stdout, "XXX", message) diff --git a/providers/dns/exoscale/exoscale.go b/providers/dns/exoscale/exoscale.go index 83baa9ade..05fcb6a6f 100644 --- a/providers/dns/exoscale/exoscale.go +++ b/providers/dns/exoscale/exoscale.go @@ -118,6 +118,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("exoscale: %w", err) } + if zone == nil { return fmt.Errorf("exoscale: zone %q not found", zoneName) } @@ -157,6 +158,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("exoscale: %w", err) } + if zone == nil { return fmt.Errorf("exoscale: zone %q not found", zoneName) } diff --git a/providers/dns/exoscale/exoscale_test.go b/providers/dns/exoscale/exoscale_test.go index fa58216a5..e9f6be602 100644 --- a/providers/dns/exoscale/exoscale_test.go +++ b/providers/dns/exoscale/exoscale_test.go @@ -58,6 +58,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -178,6 +179,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -195,6 +197,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/f5xc/f5xc_test.go b/providers/dns/f5xc/f5xc_test.go index a298b9e51..98f7484e7 100644 --- a/providers/dns/f5xc/f5xc_test.go +++ b/providers/dns/f5xc/f5xc_test.go @@ -62,6 +62,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -145,6 +146,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -158,6 +160,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/f5xc/internal/client.go b/providers/dns/f5xc/internal/client.go index 26eb03db5..b0b5d0468 100644 --- a/providers/dns/f5xc/internal/client.go +++ b/providers/dns/f5xc/internal/client.go @@ -201,6 +201,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) apiErr := APIError{StatusCode: resp.StatusCode} + err := json.Unmarshal(raw, &apiErr) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/f5xc/internal/types.go b/providers/dns/f5xc/internal/types.go index 3122ca4ef..346283fb7 100644 --- a/providers/dns/f5xc/internal/types.go +++ b/providers/dns/f5xc/internal/types.go @@ -27,13 +27,13 @@ type APIRRSet struct { Namespace string `json:"namespace,omitempty"` RecordName string `json:"record_name,omitempty"` Type string `json:"type,omitempty"` - RRSet RRSet `json:"rrset,omitempty"` + RRSet RRSet `json:"rrset"` } type RRSetRequest struct { DNSZoneName string `json:"dns_zone_name,omitempty"` GroupName string `json:"group_name,omitempty"` - RRSet RRSet `json:"rrset,omitempty"` + RRSet RRSet `json:"rrset"` } type RRSet struct { diff --git a/providers/dns/freemyip/freemyip_test.go b/providers/dns/freemyip/freemyip_test.go index dcf74dd6c..24d1b98f7 100644 --- a/providers/dns/freemyip/freemyip_test.go +++ b/providers/dns/freemyip/freemyip_test.go @@ -37,6 +37,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -94,6 +95,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -107,6 +109,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/gandi/gandi_test.go b/providers/dns/gandi/gandi_test.go index 46697190a..58c25d0db 100644 --- a/providers/dns/gandi/gandi_test.go +++ b/providers/dns/gandi/gandi_test.go @@ -39,6 +39,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -157,9 +158,11 @@ func TestDNSProvider(t *testing.T) { // override findZoneByFqdn function savedFindZoneByFqdn := provider.findZoneByFqdn + t.Cleanup(func() { provider.findZoneByFqdn = savedFindZoneByFqdn }) + provider.findZoneByFqdn = fakeFindZoneByFqdn // run Present diff --git a/providers/dns/gandi/internal/client.go b/providers/dns/gandi/internal/client.go index 6dc09648c..6ca46d072 100644 --- a/providers/dns/gandi/internal/client.go +++ b/providers/dns/gandi/internal/client.go @@ -50,6 +50,7 @@ func (c *Client) GetZoneID(ctx context.Context, domain string) (int, error) { } var zoneID int + for _, member := range resp.StructMembers { if member.Name == "zone_id" { zoneID = member.ValueInt @@ -59,6 +60,7 @@ func (c *Client) GetZoneID(ctx context.Context, domain string) (int, error) { if zoneID == 0 { return 0, fmt.Errorf("could not find zone_id for %s", domain) } + return zoneID, nil } @@ -88,6 +90,7 @@ func (c *Client) CloneZone(ctx context.Context, zoneID int, name string) (int, e } var newZoneID int + for _, member := range resp.StructMembers { if member.Name == "id" { newZoneID = member.ValueInt @@ -97,6 +100,7 @@ func (c *Client) CloneZone(ctx context.Context, zoneID int, name string) (int, e if newZoneID == 0 { return 0, errors.New("could not determine cloned zone_id") } + return newZoneID, nil } @@ -119,6 +123,7 @@ func (c *Client) NewZoneVersion(ctx context.Context, zoneID int) (int, error) { if resp.Value == 0 { return 0, errors.New("could not create new zone version") } + return resp.Value, nil } @@ -174,6 +179,7 @@ func (c *Client) SetZoneVersion(ctx context.Context, zoneID, version int) error if !resp.Value { return errors.New("could not set zone version") } + return nil } @@ -195,6 +201,7 @@ func (c *Client) SetZone(ctx context.Context, domain string, zoneID int) error { } var respZoneID int + for _, member := range resp.StructMembers { if member.Name == "zone_id" { respZoneID = member.ValueInt @@ -204,6 +211,7 @@ func (c *Client) SetZone(ctx context.Context, domain string, zoneID int) error { if respZoneID != zoneID { return fmt.Errorf("could not set new zone_id for %s", domain) } + return nil } diff --git a/providers/dns/gandi/internal/types.go b/providers/dns/gandi/internal/types.go index cdcd0a658..2cde62b53 100644 --- a/providers/dns/gandi/internal/types.go +++ b/providers/dns/gandi/internal/types.go @@ -69,6 +69,7 @@ func (r responseFault) faultString() string { return r.FaultString } type responseStruct struct { responseFault + StructMembers []struct { Name string `xml:"name"` ValueInt int `xml:"value>int"` @@ -77,11 +78,13 @@ type responseStruct struct { type responseInt struct { responseFault + Value int `xml:"params>param>value>int"` } type responseBool struct { responseFault + Value bool `xml:"params>param>value>boolean"` } diff --git a/providers/dns/gandiv5/gandiv5.go b/providers/dns/gandiv5/gandiv5.go index cd236631c..15014e207 100644 --- a/providers/dns/gandiv5/gandiv5.go +++ b/providers/dns/gandiv5/gandiv5.go @@ -114,6 +114,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if err != nil { return nil, fmt.Errorf("gandiv5: %w", err) } + client.BaseURL = baseURL } @@ -163,6 +164,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { authZone: authZone, fieldName: subDomain, } + return nil } @@ -173,6 +175,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // acquire lock and retrieve authZone d.inProgressMu.Lock() defer d.inProgressMu.Unlock() + if _, ok := d.inProgressFQDNs[info.EffectiveFQDN]; !ok { // if there is no cleanup information then just return return nil @@ -187,6 +190,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("gandiv5: %w", err) } + return nil } diff --git a/providers/dns/gandiv5/gandiv5_test.go b/providers/dns/gandiv5/gandiv5_test.go index e0cdf86ff..d6f077243 100644 --- a/providers/dns/gandiv5/gandiv5_test.go +++ b/providers/dns/gandiv5/gandiv5_test.go @@ -35,6 +35,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -120,9 +121,11 @@ func TestDNSProvider(t *testing.T) { // override findZoneByFqdn function savedFindZoneByFqdn := provider.findZoneByFqdn + defer func() { provider.findZoneByFqdn = savedFindZoneByFqdn }() + provider.findZoneByFqdn = fakeFindZoneByFqdn // run Present diff --git a/providers/dns/gandiv5/internal/client.go b/providers/dns/gandiv5/internal/client.go index 57de9d615..018a05799 100644 --- a/providers/dns/gandiv5/internal/client.go +++ b/providers/dns/gandiv5/internal/client.go @@ -78,6 +78,7 @@ func (c *Client) getTXTRecord(ctx context.Context, domain, name string) (*Record } txtRecord := &Record{} + err = c.do(req, txtRecord) if err != nil { return nil, fmt.Errorf("unable to get TXT records for domain %s and name %s: %w", domain, name, err) @@ -95,6 +96,7 @@ func (c *Client) addTXTRecord(ctx context.Context, domain, name string, newRecor } message := apiResponse{} + err = c.do(req, &message) if err != nil { return fmt.Errorf("unable to create TXT record for domain %s and name %s: %w", domain, name, err) @@ -116,6 +118,7 @@ func (c *Client) DeleteTXTRecord(ctx context.Context, domain, name string) error } message := apiResponse{} + err = c.do(req, &message) if err != nil { return fmt.Errorf("unable to delete TXT record for domain %s and name %s: %w", domain, name, err) @@ -208,6 +211,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) response := apiResponse{} + err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/gcloud/googlecloud.go b/providers/dns/gcloud/googlecloud.go index abf40ebd4..ff317946d 100644 --- a/providers/dns/gcloud/googlecloud.go +++ b/providers/dns/gcloud/googlecloud.go @@ -93,6 +93,7 @@ func NewDNSProvider() (*DNSProvider, error) { // Use default credentials. project := env.GetOrDefaultString(EnvProject, autodetectProjectID(context.Background())) + return NewDNSProviderCredentials(project) } @@ -107,6 +108,7 @@ func NewDNSProviderCredentials(project string) (*DNSProvider, error) { config.Project = project var err error + config.HTTPClient, err = newClientFromCredentials(context.Background(), config) if err != nil { return nil, fmt.Errorf("googlecloud: %w", err) @@ -130,10 +132,12 @@ func NewDNSProviderServiceAccountKey(saKey []byte) (*DNSProvider, error) { var datJSON struct { ProjectID string `json:"project_id"` } + err := json.Unmarshal(saKey, &datJSON) if err != nil || datJSON.ProjectID == "" { return nil, errors.New("googlecloud: project ID not found in Google Cloud Service Account file") } + project = datJSON.ProjectID } @@ -141,6 +145,7 @@ func NewDNSProviderServiceAccountKey(saKey []byte) (*DNSProvider, error) { config.Project = project var err error + config.HTTPClient, err = newClientFromServiceAccountKey(context.Background(), config, saKey) if err != nil { return nil, fmt.Errorf("googlecloud: %w", err) @@ -169,6 +174,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config == nil { return nil, errors.New("googlecloud: the configuration of the DNS provider is nil") } + if config.HTTPClient == nil { return nil, errors.New("googlecloud: unable to create Google Cloud DNS service: client is nil") } @@ -200,6 +206,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { for _, rrSet := range existingRrSet { var rrd []string + for _, rr := range rrSet.Rrdatas { data := mustUnquote(rr) rrd = append(rrd, data) @@ -209,6 +216,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return nil } } + rrSet.Rrdatas = rrd } @@ -260,6 +268,7 @@ func (d *DNSProvider) applyChanges(ctx context.Context, zone string, change *gdn } data, _ := json.Marshal(change) + return fmt.Errorf("failed to perform changes [zone %s, change %s]: %w", zone, string(data), err) } @@ -316,6 +325,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("googlecloud: %w", err) } + return nil } @@ -450,6 +460,7 @@ func mustUnquote(raw string) string { if err != nil { return raw } + return clean } diff --git a/providers/dns/gcloud/googlecloud_test.go b/providers/dns/gcloud/googlecloud_test.go index 00d6c6c9a..28b08a2f9 100644 --- a/providers/dns/gcloud/googlecloud_test.go +++ b/providers/dns/gcloud/googlecloud_test.go @@ -86,6 +86,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -125,6 +126,7 @@ func TestNewDNSProviderConfig(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() config := NewDefaultConfig() @@ -237,12 +239,14 @@ func TestPresentWithExistingRR(t *testing.T) { } var prevVal string + for _, addition := range chgReq.Additions { for _, value := range addition.Rrdatas { if prevVal == value { http.Error(rw, fmt.Sprintf("The resource %s already exists", value), http.StatusConflict) return } + prevVal = value } } diff --git a/providers/dns/gcore/gcore_test.go b/providers/dns/gcore/gcore_test.go index a5eddee7c..88769df21 100644 --- a/providers/dns/gcore/gcore_test.go +++ b/providers/dns/gcore/gcore_test.go @@ -34,6 +34,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -93,6 +94,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -106,6 +108,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/gcore/internal/client.go b/providers/dns/gcore/internal/client.go index b76da4388..638aaf0d7 100644 --- a/providers/dns/gcore/internal/client.go +++ b/providers/dns/gcore/internal/client.go @@ -48,6 +48,7 @@ func (c *Client) GetZone(ctx context.Context, name string) (Zone, error) { endpoint := c.baseURL.JoinPath("v2", "zones", name) zone := Zone{} + err := c.doRequest(ctx, http.MethodGet, endpoint, nil, &zone) if err != nil { return Zone{}, fmt.Errorf("get zone %s: %w", name, err) @@ -62,6 +63,7 @@ func (c *Client) GetRRSet(ctx context.Context, zone, name string) (RRSet, error) endpoint := c.baseURL.JoinPath("v2", "zones", zone, name, txtRecordType) var result RRSet + err := c.doRequest(ctx, http.MethodGet, endpoint, nil, &result) if err != nil { return RRSet{}, fmt.Errorf("get txt records %s -> %s: %w", zone, name, err) @@ -180,6 +182,7 @@ func parseError(resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errAPI := APIError{StatusCode: resp.StatusCode} + err := json.Unmarshal(raw, &errAPI) if err != nil { errAPI.Message = string(raw) diff --git a/providers/dns/glesys/glesys.go b/providers/dns/glesys/glesys.go index 4fa689e28..729756235 100644 --- a/providers/dns/glesys/glesys.go +++ b/providers/dns/glesys/glesys.go @@ -136,6 +136,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // save data necessary for CleanUp d.activeRecords[info.EffectiveFQDN] = recordID + return nil } @@ -146,6 +147,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // acquire lock and retrieve authZone d.inProgressMu.Lock() defer d.inProgressMu.Unlock() + if _, ok := d.activeRecords[info.EffectiveFQDN]; !ok { // if there is no cleanup information then just return return nil diff --git a/providers/dns/glesys/glesys_test.go b/providers/dns/glesys/glesys_test.go index d5fdf36da..f2d65e514 100644 --- a/providers/dns/glesys/glesys_test.go +++ b/providers/dns/glesys/glesys_test.go @@ -56,6 +56,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -130,6 +131,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -143,6 +145,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/glesys/internal/client.go b/providers/dns/glesys/internal/client.go index 20bc363ba..ee6ebc058 100644 --- a/providers/dns/glesys/internal/client.go +++ b/providers/dns/glesys/internal/client.go @@ -102,6 +102,7 @@ func (c *Client) do(req *http.Request) (*apiResponse, error) { } var response apiResponse + err = json.Unmarshal(raw, &response) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/godaddy/godaddy.go b/providers/dns/godaddy/godaddy.go index 7c323ce0b..1603bb57e 100644 --- a/providers/dns/godaddy/godaddy.go +++ b/providers/dns/godaddy/godaddy.go @@ -131,6 +131,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { } var newRecords []internal.DNSRecord + for _, record := range existingRecords { if record.Data != "" { newRecords = append(newRecords, record) @@ -177,6 +178,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var recordsToKeep []internal.DNSRecord + for _, record := range existingRecords { if record.Data != info.Value && record.Data != "" { recordsToKeep = append(recordsToKeep, record) diff --git a/providers/dns/godaddy/godaddy_test.go b/providers/dns/godaddy/godaddy_test.go index 4cb5f2721..38b39672e 100644 --- a/providers/dns/godaddy/godaddy_test.go +++ b/providers/dns/godaddy/godaddy_test.go @@ -56,6 +56,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -126,6 +127,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -139,6 +141,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/godaddy/internal/client.go b/providers/dns/godaddy/internal/client.go index bf30d437f..9dd337ddc 100644 --- a/providers/dns/godaddy/internal/client.go +++ b/providers/dns/godaddy/internal/client.go @@ -48,6 +48,7 @@ func (c *Client) GetRecords(ctx context.Context, domainZone, rType, recordName s } var records []DNSRecord + err = c.do(req, &records) if err != nil { return nil, err @@ -141,6 +142,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/godaddy/internal/types.go b/providers/dns/godaddy/internal/types.go index a97a97896..c1e6d6638 100644 --- a/providers/dns/godaddy/internal/types.go +++ b/providers/dns/godaddy/internal/types.go @@ -1,6 +1,9 @@ package internal -import "fmt" +import ( + "fmt" + "strings" +) // DNSRecord a DNS record. type DNSRecord struct { @@ -23,13 +26,16 @@ type APIError struct { } func (a APIError) Error() string { - msg := fmt.Sprintf("%s: %s", a.Code, a.Message) + var msg strings.Builder + + msg.WriteString(fmt.Sprintf("%s: %s", a.Code, a.Message)) for _, field := range a.Fields { - msg += " " + field.String() + msg.WriteString(" ") + msg.WriteString(field.String()) } - return msg + return msg.String() } type Field struct { diff --git a/providers/dns/hetzner/hetzner_test.go b/providers/dns/hetzner/hetzner_test.go index 7f59e6323..430f0270b 100644 --- a/providers/dns/hetzner/hetzner_test.go +++ b/providers/dns/hetzner/hetzner_test.go @@ -56,6 +56,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) diff --git a/providers/dns/hetzner/internal/hetznerv1/hetznerv1_test.go b/providers/dns/hetzner/internal/hetznerv1/hetznerv1_test.go index e43dce068..bf52baa35 100644 --- a/providers/dns/hetzner/internal/hetznerv1/hetznerv1_test.go +++ b/providers/dns/hetzner/internal/hetznerv1/hetznerv1_test.go @@ -37,6 +37,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -96,6 +97,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -109,6 +111,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/client.go b/providers/dns/hetzner/internal/hetznerv1/internal/client.go index 2f87eb6a5..35c3d461b 100644 --- a/providers/dns/hetzner/internal/hetznerv1/internal/client.go +++ b/providers/dns/hetzner/internal/hetznerv1/internal/client.go @@ -54,6 +54,7 @@ func (c *Client) AddRRSetRecords(ctx context.Context, zoneIDName, recordType, re } var result ActionResponse + err = c.do(req, &result) if err != nil { return nil, err @@ -73,6 +74,7 @@ func (c *Client) RemoveRRSetRecords(ctx context.Context, zoneIDName, recordType, } var result ActionResponse + err = c.do(req, &result) if err != nil { return nil, err @@ -92,6 +94,7 @@ func (c *Client) GetAction(ctx context.Context, id int) (*Action, error) { } var result ActionResponse + err = c.do(req, &result) if err != nil { return nil, err @@ -157,6 +160,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/types.go b/providers/dns/hetzner/internal/hetznerv1/internal/types.go index 0dd9fb9a1..08d1684c0 100644 --- a/providers/dns/hetzner/internal/hetznerv1/internal/types.go +++ b/providers/dns/hetzner/internal/hetznerv1/internal/types.go @@ -12,25 +12,27 @@ type APIError struct { type ErrorInfo struct { Code string `json:"code,omitempty"` Message string `json:"message,omitempty"` - Details ErrorDetails `json:"details,omitempty"` + Details ErrorDetails `json:"details"` } func (i *ErrorInfo) Error() string { - msg := fmt.Sprintf("%s: %s", i.Code, i.Message) + var msg strings.Builder + + msg.WriteString(fmt.Sprintf("%s: %s", i.Code, i.Message)) if i.Details.Announcement != "" { - msg += fmt.Sprintf(": %s", i.Details.Announcement) + msg.WriteString(fmt.Sprintf(": %s", i.Details.Announcement)) } for _, limit := range i.Details.Limits { - msg += fmt.Sprintf("limit: %s", limit.Name) + msg.WriteString(fmt.Sprintf("limit: %s", limit.Name)) } for _, field := range i.Details.Fields { - msg += fmt.Sprintf("field: %s: %s", field.Name, strings.Join(field.Messages, ", ")) + msg.WriteString(fmt.Sprintf("field: %s: %s", field.Name, strings.Join(field.Messages, ", "))) } - return msg + return msg.String() } type ErrorDetails struct { diff --git a/providers/dns/hetzner/internal/legacy/hetzner_test.go b/providers/dns/hetzner/internal/legacy/hetzner_test.go index 07eae8149..c9258ecf8 100644 --- a/providers/dns/hetzner/internal/legacy/hetzner_test.go +++ b/providers/dns/hetzner/internal/legacy/hetzner_test.go @@ -37,6 +37,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -104,6 +105,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -117,6 +119,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/hetzner/internal/legacy/internal/client.go b/providers/dns/hetzner/internal/legacy/internal/client.go index 381922264..cd187f6e5 100644 --- a/providers/dns/hetzner/internal/legacy/internal/client.go +++ b/providers/dns/hetzner/internal/legacy/internal/client.go @@ -83,6 +83,7 @@ func (c *Client) getRecords(ctx context.Context, zoneID string) (*DNSRecords, er } records := &DNSRecords{} + err = json.Unmarshal(raw, records) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -190,6 +191,7 @@ func (c *Client) getZones(ctx context.Context, name string) (*Zones, error) { } zones := &Zones{} + err = json.Unmarshal(raw, zones) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/hetzner/internal/legacy/internal/types.go b/providers/dns/hetzner/internal/legacy/internal/types.go index d0e284511..3b332cc8f 100644 --- a/providers/dns/hetzner/internal/legacy/internal/types.go +++ b/providers/dns/hetzner/internal/legacy/internal/types.go @@ -25,12 +25,12 @@ type Zone struct { // Zones a set of DNS zones. type Zones struct { Zones []Zone `json:"zones"` - Meta Meta `json:"meta,omitempty"` + Meta Meta `json:"meta"` } // Meta response metadata. type Meta struct { - Pagination Pagination `json:"pagination,omitempty"` + Pagination Pagination `json:"pagination"` } // Pagination information about pagination. diff --git a/providers/dns/hostingde/hostingde.go b/providers/dns/hostingde/hostingde.go index 3ad6e4a61..48c44998f 100644 --- a/providers/dns/hostingde/hostingde.go +++ b/providers/dns/hostingde/hostingde.go @@ -188,6 +188,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("hostingde: %w", err) } + zoneConfig.Name = zoneName rec := []hostingde.DNSRecord{{ @@ -210,6 +211,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("hostingde: %w", err) } + return nil } diff --git a/providers/dns/hostingde/hostingde_test.go b/providers/dns/hostingde/hostingde_test.go index d7681f953..1611cb51b 100644 --- a/providers/dns/hostingde/hostingde_test.go +++ b/providers/dns/hostingde/hostingde_test.go @@ -49,6 +49,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -116,6 +117,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -129,6 +131,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/hostinger/hostinger_test.go b/providers/dns/hostinger/hostinger_test.go index 1315cee97..90ecba529 100644 --- a/providers/dns/hostinger/hostinger_test.go +++ b/providers/dns/hostinger/hostinger_test.go @@ -38,6 +38,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -156,6 +157,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -169,6 +171,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/hostinger/internal/client.go b/providers/dns/hostinger/internal/client.go index 0fbf8acf6..9da712d61 100644 --- a/providers/dns/hostinger/internal/client.go +++ b/providers/dns/hostinger/internal/client.go @@ -52,6 +52,7 @@ func (c *Client) GetDNSRecords(ctx context.Context, domain string) ([]RecordSet, } var result []RecordSet + err = c.do(req, &result) if err != nil { return nil, err @@ -145,6 +146,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/hostinger/internal/types.go b/providers/dns/hostinger/internal/types.go index ba0970dfb..534dfa5e5 100644 --- a/providers/dns/hostinger/internal/types.go +++ b/providers/dns/hostinger/internal/types.go @@ -12,13 +12,15 @@ type APIError struct { } func (a *APIError) Error() string { - msg := fmt.Sprintf("%s: %s", a.CorrelationID, a.Message) + var msg strings.Builder + + msg.WriteString(fmt.Sprintf("%s: %s", a.CorrelationID, a.Message)) for field, values := range a.Errors { - msg += fmt.Sprintf(": %s: %s", field, strings.Join(values, ", ")) + msg.WriteString(fmt.Sprintf(": %s: %s", field, strings.Join(values, ", "))) } - return msg + return msg.String() } type ZoneRequest struct { diff --git a/providers/dns/hosttech/hosttech.go b/providers/dns/hosttech/hosttech.go index 20fa1d710..fac64f054 100644 --- a/providers/dns/hosttech/hosttech.go +++ b/providers/dns/hosttech/hosttech.go @@ -164,6 +164,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("hosttech: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/hosttech/hosttech_test.go b/providers/dns/hosttech/hosttech_test.go index 6f0d0bd3e..042b73353 100644 --- a/providers/dns/hosttech/hosttech_test.go +++ b/providers/dns/hosttech/hosttech_test.go @@ -33,6 +33,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -92,6 +93,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -105,6 +107,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/hosttech/internal/client.go b/providers/dns/hosttech/internal/client.go index 399b18d0e..557d54298 100644 --- a/providers/dns/hosttech/internal/client.go +++ b/providers/dns/hosttech/internal/client.go @@ -58,6 +58,7 @@ func (c *Client) GetZones(ctx context.Context, query string, limit, offset int) } result := apiResponse[[]Zone]{} + err = c.do(req, &result) if err != nil { return nil, err @@ -77,6 +78,7 @@ func (c *Client) GetZone(ctx context.Context, zoneID string) (*Zone, error) { } result := apiResponse[*Zone]{} + err = c.do(req, &result) if err != nil { return nil, err @@ -104,6 +106,7 @@ func (c *Client) GetRecords(ctx context.Context, zoneID, recordType string) ([]R } result := apiResponse[[]Record]{} + err = c.do(req, &result) if err != nil { return nil, err @@ -123,6 +126,7 @@ func (c *Client) AddRecord(ctx context.Context, zoneID string, record Record) (* } result := apiResponse[*Record]{} + err = c.do(req, &result) if err != nil { return nil, err @@ -202,6 +206,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errAPI := &APIError{StatusCode: resp.StatusCode} + err := json.Unmarshal(raw, errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/hosttech/internal/types.go b/providers/dns/hosttech/internal/types.go index bf86964f7..854fc4883 100644 --- a/providers/dns/hosttech/internal/types.go +++ b/providers/dns/hosttech/internal/types.go @@ -2,6 +2,7 @@ package internal import ( "fmt" + "strings" ) type apiResponse[T any] struct { @@ -15,11 +16,15 @@ type APIError struct { } func (a APIError) Error() string { - msg := fmt.Sprintf("%d: %s", a.StatusCode, a.Message) + var msg strings.Builder + + msg.WriteString(fmt.Sprintf("%d: %s", a.StatusCode, a.Message)) + for k, v := range a.Errors { - msg += fmt.Sprintf(" %s: %v", k, v) + msg.WriteString(fmt.Sprintf(" %s: %v", k, v)) } - return msg + + return msg.String() } type Zone struct { diff --git a/providers/dns/httpnet/httpnet.go b/providers/dns/httpnet/httpnet.go index e69c43e6d..f18eefd97 100644 --- a/providers/dns/httpnet/httpnet.go +++ b/providers/dns/httpnet/httpnet.go @@ -190,6 +190,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("httpnet: %w", err) } + zoneConfig.Name = zoneName rec := []hostingde.DNSRecord{{ @@ -212,6 +213,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("httpnet: %w", err) } + return nil } diff --git a/providers/dns/httpnet/httpnet_test.go b/providers/dns/httpnet/httpnet_test.go index a9bc527ad..64a94f80c 100644 --- a/providers/dns/httpnet/httpnet_test.go +++ b/providers/dns/httpnet/httpnet_test.go @@ -49,6 +49,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -116,6 +117,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -129,6 +131,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/httpreq/httpreq.go b/providers/dns/httpreq/httpreq.go index 12eef7b8e..591e9b5e1 100644 --- a/providers/dns/httpreq/httpreq.go +++ b/providers/dns/httpreq/httpreq.go @@ -129,6 +129,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("httpreq: %w", err) } + return nil } @@ -142,6 +143,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("httpreq: %w", err) } + return nil } @@ -160,6 +162,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("httpreq: %w", err) } + return nil } @@ -173,11 +176,13 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("httpreq: %w", err) } + return nil } func (d *DNSProvider) doPost(ctx context.Context, uri string, msg any) error { reqBody := new(bytes.Buffer) + err := json.NewEncoder(reqBody).Encode(msg) if err != nil { return fmt.Errorf("failed to create request JSON body: %w", err) diff --git a/providers/dns/httpreq/httpreq_test.go b/providers/dns/httpreq/httpreq_test.go index a7571de22..108d6a565 100644 --- a/providers/dns/httpreq/httpreq_test.go +++ b/providers/dns/httpreq/httpreq_test.go @@ -43,6 +43,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -266,5 +267,6 @@ func mustParse(rawURL string) *url.URL { if err != nil { panic(err) } + return uri } diff --git a/providers/dns/huaweicloud/huaweicloud.go b/providers/dns/huaweicloud/huaweicloud.go index 430abce74..5a2773ab2 100644 --- a/providers/dns/huaweicloud/huaweicloud.go +++ b/providers/dns/huaweicloud/huaweicloud.go @@ -184,6 +184,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("huaweicloud: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/huaweicloud/huaweicloud_test.go b/providers/dns/huaweicloud/huaweicloud_test.go index 6787650ca..25e295da7 100644 --- a/providers/dns/huaweicloud/huaweicloud_test.go +++ b/providers/dns/huaweicloud/huaweicloud_test.go @@ -62,6 +62,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -140,6 +141,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -153,6 +155,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/hurricane/hurricane.go b/providers/dns/hurricane/hurricane.go index c8b05731a..b23528bb0 100644 --- a/providers/dns/hurricane/hurricane.go +++ b/providers/dns/hurricane/hurricane.go @@ -58,6 +58,7 @@ type DNSProvider struct { // NewDNSProvider returns a DNSProvider instance configured for Hurricane Electric. func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() + values, err := env.Get(EnvTokens) if err != nil { return nil, fmt.Errorf("hurricane: %w", err) diff --git a/providers/dns/hurricane/hurricane_test.go b/providers/dns/hurricane/hurricane_test.go index f8a1f185c..2bbd638fa 100644 --- a/providers/dns/hurricane/hurricane_test.go +++ b/providers/dns/hurricane/hurricane_test.go @@ -55,6 +55,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -120,6 +121,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -133,6 +135,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/hyperone/hyperone.go b/providers/dns/hyperone/hyperone.go index 33716cfdb..3cdad8e68 100644 --- a/providers/dns/hyperone/hyperone.go +++ b/providers/dns/hyperone/hyperone.go @@ -77,6 +77,7 @@ func NewDNSProvider() (*DNSProvider, error) { func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config.PassportLocation == "" { var err error + config.PassportLocation, err = GetDefaultPassportLocation() if err != nil { return nil, fmt.Errorf("hyperone: %w", err) @@ -166,6 +167,7 @@ func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { if err != nil { return fmt.Errorf("hyperone: %w", err) } + if len(records) == 1 { if records[0].Content != info.Value { return fmt.Errorf("hyperone: record with content %s not found: fqdn=%s", info.Value, info.EffectiveFQDN) diff --git a/providers/dns/hyperone/hyperone_test.go b/providers/dns/hyperone/hyperone_test.go index 1222d1c74..675a1fe19 100644 --- a/providers/dns/hyperone/hyperone_test.go +++ b/providers/dns/hyperone/hyperone_test.go @@ -49,6 +49,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -124,6 +125,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -137,6 +139,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/hyperone/internal/passport.go b/providers/dns/hyperone/internal/passport.go index b63236c3b..d1503d893 100644 --- a/providers/dns/hyperone/internal/passport.go +++ b/providers/dns/hyperone/internal/passport.go @@ -25,6 +25,7 @@ func LoadPassportFile(location string) (*Passport, error) { defer func() { _ = file.Close() }() var passport Passport + err = json.NewDecoder(file).Decode(&passport) if err != nil { return nil, fmt.Errorf("failed to parse passport file: %w", err) diff --git a/providers/dns/hyperone/internal/token_test.go b/providers/dns/hyperone/internal/token_test.go index 315d0896f..34b4cc573 100644 --- a/providers/dns/hyperone/internal/token_test.go +++ b/providers/dns/hyperone/internal/token_test.go @@ -38,6 +38,7 @@ func TestPayload_buildToken(t *testing.T) { require.NoError(t, err) var headerStruct Header + err = json.Unmarshal(headerString, &headerStruct) require.NoError(t, err) @@ -45,6 +46,7 @@ func TestPayload_buildToken(t *testing.T) { require.NoError(t, err) var payloadStruct Payload + err = json.Unmarshal(payloadString, &payloadStruct) require.NoError(t, err) diff --git a/providers/dns/ibmcloud/ibmcloud_test.go b/providers/dns/ibmcloud/ibmcloud_test.go index a000e3e59..6ca7cd81b 100644 --- a/providers/dns/ibmcloud/ibmcloud_test.go +++ b/providers/dns/ibmcloud/ibmcloud_test.go @@ -55,6 +55,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -127,6 +128,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -140,6 +142,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/iij/iij.go b/providers/dns/iij/iij.go index 6bc7db21a..1d098bde2 100644 --- a/providers/dns/iij/iij.go +++ b/providers/dns/iij/iij.go @@ -98,6 +98,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("iij: %w", err) } + return nil } @@ -110,6 +111,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("iij: %w", err) } + return nil } diff --git a/providers/dns/iij/iij_test.go b/providers/dns/iij/iij_test.go index 2c7ec4217..bd8140532 100644 --- a/providers/dns/iij/iij_test.go +++ b/providers/dns/iij/iij_test.go @@ -71,6 +71,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -238,6 +239,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -251,6 +253,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/iijdpf/iijdpf_test.go b/providers/dns/iijdpf/iijdpf_test.go index a4fa8b8f6..fbcf3e1f5 100644 --- a/providers/dns/iijdpf/iijdpf_test.go +++ b/providers/dns/iijdpf/iijdpf_test.go @@ -43,6 +43,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -115,6 +116,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -128,6 +130,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/iijdpf/wrapper.go b/providers/dns/iijdpf/wrapper.go index 12b09a30c..0ab26cdcd 100644 --- a/providers/dns/iijdpf/wrapper.go +++ b/providers/dns/iijdpf/wrapper.go @@ -51,6 +51,7 @@ func (d *DNSProvider) deleteTxtRecord(ctx context.Context, zoneID, fqdn, rdata s // empty target rrset return nil } + return err } @@ -66,11 +67,13 @@ func (d *DNSProvider) deleteTxtRecord(ctx context.Context, zoneID, fqdn, rdata s // delete rdata rdataSlice := dpfzones.RecordRDATASlice{} + for _, v := range r.RData { if v.Value != rdata { rdataSlice = append(rdataSlice, v) } } + r.RData = rdataSlice _, _, err = dpfapiutils.SyncUpdate(ctx, d.client, r, nil) diff --git a/providers/dns/infoblox/infoblox.go b/providers/dns/infoblox/infoblox.go index 37e119e85..054f13679 100644 --- a/providers/dns/infoblox/infoblox.go +++ b/providers/dns/infoblox/infoblox.go @@ -198,6 +198,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordRefsMu.Lock() recordRef, ok := d.recordRefs[token] d.recordRefsMu.Unlock() + if !ok { return fmt.Errorf("infoblox: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/infoblox/infoblox_test.go b/providers/dns/infoblox/infoblox_test.go index 45434e0e3..68158cb0d 100644 --- a/providers/dns/infoblox/infoblox_test.go +++ b/providers/dns/infoblox/infoblox_test.go @@ -68,6 +68,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -149,6 +150,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -162,6 +164,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/infomaniak/infomaniak_test.go b/providers/dns/infomaniak/infomaniak_test.go index bc8fb7b58..980f3b959 100644 --- a/providers/dns/infomaniak/infomaniak_test.go +++ b/providers/dns/infomaniak/infomaniak_test.go @@ -39,6 +39,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -101,6 +102,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -114,6 +116,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/infomaniak/internal/client.go b/providers/dns/infomaniak/internal/client.go index 886a8966f..40b56c707 100644 --- a/providers/dns/infomaniak/internal/client.go +++ b/providers/dns/infomaniak/internal/client.go @@ -50,6 +50,7 @@ func (c *Client) CreateDNSRecord(ctx context.Context, domain *DNSDomain, record } result := APIResponse[string]{} + err = c.do(req, &result) if err != nil { return "", err @@ -112,6 +113,7 @@ func (c *Client) getDomainByName(ctx context.Context, name string) (*DNSDomain, } result := APIResponse[[]DNSDomain]{} + err = c.do(req, &result) if err != nil { return nil, err diff --git a/providers/dns/internal/active24/client.go b/providers/dns/internal/active24/client.go index 32ecc2186..10aaa4666 100644 --- a/providers/dns/internal/active24/client.go +++ b/providers/dns/internal/active24/client.go @@ -55,6 +55,7 @@ func (c *Client) GetServices(ctx context.Context) ([]Service, error) { } var result OldAPIResponse + err = c.do(req, &result) if err != nil { return nil, err @@ -82,6 +83,7 @@ func (c *Client) GetRecords(ctx context.Context, service string, filter RecordFi } var result APIResponse + err = c.do(req, &result) if err != nil { return nil, err @@ -180,6 +182,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) @@ -198,6 +201,7 @@ func (c *Client) sign(req *http.Request, now time.Time) error { canonicalRequest := fmt.Sprintf("%s %s %d", req.Method, req.URL.Path, now.Unix()) mac := hmac.New(sha1.New, []byte(c.secret)) + _, err := mac.Write([]byte(canonicalRequest)) if err != nil { return err diff --git a/providers/dns/internal/hostingde/types.go b/providers/dns/internal/hostingde/types.go index 4f3347190..86b69ec42 100644 --- a/providers/dns/internal/hostingde/types.go +++ b/providers/dns/internal/hostingde/types.go @@ -88,7 +88,8 @@ type Zone struct { // https://www.hosting.de/api/?json#updating-zones type ZoneUpdateRequest struct { BaseRequest - ZoneConfig `json:"zoneConfig"` + ZoneConfig `json:"zoneConfig"` + RecordsToAdd []DNSRecord `json:"recordsToAdd"` RecordsToDelete []DNSRecord `json:"recordsToDelete"` } @@ -97,6 +98,7 @@ type ZoneUpdateRequest struct { // https://www.hosting.de/api/?json#list-zoneconfigs type ZoneConfigsFindRequest struct { BaseRequest + Filter Filter `json:"filter"` Limit int `json:"limit"` Page int `json:"page"` diff --git a/providers/dns/internal/rimuhosting/client.go b/providers/dns/internal/rimuhosting/client.go index 06292453a..c46afc544 100644 --- a/providers/dns/internal/rimuhosting/client.go +++ b/providers/dns/internal/rimuhosting/client.go @@ -82,14 +82,17 @@ func (c *Client) DoActions(ctx context.Context, actions ...ActionParameter) (*DN if err != nil { return nil, err } + return resp, nil } multi := c.toMultiParameters(actions) + err := c.do(ctx, multi, resp) if err != nil { return nil, err } + return resp, nil } @@ -160,6 +163,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errAPI := APIError{} + err := xml.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/internal/selectel/client.go b/providers/dns/internal/selectel/client.go index 1e1e4a215..fe810ebc5 100644 --- a/providers/dns/internal/selectel/client.go +++ b/providers/dns/internal/selectel/client.go @@ -52,6 +52,7 @@ func (c *Client) GetDomainByName(ctx context.Context, domainName string) (*Domai } domain := &Domain{} + statusCode, err := c.do(req, domain) if err != nil { if statusCode == http.StatusNotFound && strings.Count(domainName, ".") > 1 { @@ -74,6 +75,7 @@ func (c *Client) AddRecord(ctx context.Context, domainID int, body Record) (*Rec } record := &Record{} + _, err = c.do(req, record) if err != nil { return nil, err @@ -90,6 +92,7 @@ func (c *Client) ListRecords(ctx context.Context, domainID int) ([]Record, error } var records []Record + _, err = c.do(req, &records) if err != nil { return nil, err @@ -108,6 +111,7 @@ func (c *Client) DeleteRecord(ctx context.Context, domainID, recordID int) error } _, err = c.do(req, nil) + return err } @@ -170,6 +174,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errAPI := &APIError{} + err := json.Unmarshal(raw, errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/internetbs/internal/client.go b/providers/dns/internetbs/internal/client.go index 4de57dc9a..cf9e90dc5 100644 --- a/providers/dns/internetbs/internal/client.go +++ b/providers/dns/internetbs/internal/client.go @@ -48,6 +48,7 @@ func NewClient(apiKey, password string) *Client { // AddRecord The command is intended to add a new DNS record to a specific zone (domain). func (c *Client) AddRecord(ctx context.Context, query RecordQuery) error { var r APIResponse + err := c.doRequest(ctx, "Add", query, &r) if err != nil { return err @@ -63,6 +64,7 @@ func (c *Client) AddRecord(ctx context.Context, query RecordQuery) error { // RemoveRecord The command is intended to remove a DNS record from a specific zone. func (c *Client) RemoveRecord(ctx context.Context, query RecordQuery) error { var r APIResponse + err := c.doRequest(ctx, "Remove", query, &r) if err != nil { return err @@ -78,6 +80,7 @@ func (c *Client) RemoveRecord(ctx context.Context, query RecordQuery) error { // ListRecords The command is intended to retrieve the list of DNS records for a specific domain. func (c *Client) ListRecords(ctx context.Context, query ListRecordQuery) ([]Record, error) { var l ListResponse + err := c.doRequest(ctx, "List", query, &l) if err != nil { return nil, err diff --git a/providers/dns/internetbs/internetbs_test.go b/providers/dns/internetbs/internetbs_test.go index ea328d506..be436d6e7 100644 --- a/providers/dns/internetbs/internetbs_test.go +++ b/providers/dns/internetbs/internetbs_test.go @@ -49,6 +49,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -121,6 +122,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -134,6 +136,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/inwx/inwx.go b/providers/dns/inwx/inwx.go index 9945d904c..794db84b3 100644 --- a/providers/dns/inwx/inwx.go +++ b/providers/dns/inwx/inwx.go @@ -178,12 +178,14 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var recordID int + for _, record := range response.Records { if record.Content != info.Value { continue } recordID = record.ID + break } diff --git a/providers/dns/inwx/inwx_test.go b/providers/dns/inwx/inwx_test.go index 39ce7d70e..47b12e228 100644 --- a/providers/dns/inwx/inwx_test.go +++ b/providers/dns/inwx/inwx_test.go @@ -62,6 +62,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -124,6 +125,7 @@ func TestLivePresentAndCleanup(t *testing.T) { } envTest.RestoreEnv() + envTest.Apply(map[string]string{ EnvSandbox: "true", EnvTTL: "3600", // In sandbox mode, the minimum allowed TTL is 3600 diff --git a/providers/dns/ionos/internal/client.go b/providers/dns/ionos/internal/client.go index b51e003f7..935b6bbad 100644 --- a/providers/dns/ionos/internal/client.go +++ b/providers/dns/ionos/internal/client.go @@ -52,6 +52,7 @@ func (c *Client) ListZones(ctx context.Context) ([]Zone, error) { } var zones []Zone + err = c.do(req, &zones) if err != nil { return nil, fmt.Errorf("failed to call API: %w", err) @@ -96,6 +97,7 @@ func (c *Client) GetRecords(ctx context.Context, zoneID string, filter *RecordsF } var zone CustomerZone + err = c.do(req, &zone) if err != nil { return nil, fmt.Errorf("failed to call API: %w", err) @@ -180,6 +182,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errClient := &ClientError{StatusCode: resp.StatusCode} + err := json.Unmarshal(raw, &errClient.errors) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/ionos/internal/types.go b/providers/dns/ionos/internal/types.go index 3b7acbec2..35bfe0966 100644 --- a/providers/dns/ionos/internal/types.go +++ b/providers/dns/ionos/internal/types.go @@ -3,6 +3,7 @@ package internal import ( "fmt" "strconv" + "strings" ) // ClientError a detailed error. @@ -13,21 +14,23 @@ type ClientError struct { } func (f ClientError) Error() string { - msg := strconv.Itoa(f.StatusCode) + ": " + var msg strings.Builder + + msg.WriteString(strconv.Itoa(f.StatusCode) + ": ") if f.message != "" { - msg += f.message + ": " + msg.WriteString(f.message + ": ") } for i, e := range f.errors { if i != 0 { - msg += ", " + msg.WriteString(", ") } - msg += e.Error() + msg.WriteString(e.Error()) } - return msg + return msg.String() } func (f ClientError) Unwrap() error { diff --git a/providers/dns/ionos/ionos.go b/providers/dns/ionos/ionos.go index 1c2bf118d..a512e8bfd 100644 --- a/providers/dns/ionos/ionos.go +++ b/providers/dns/ionos/ionos.go @@ -185,6 +185,7 @@ func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { if err != nil { return fmt.Errorf("ionos: failed to remove record (zone=%s, record=%s): %w", zone.ID, record.ID, err) } + return nil } } diff --git a/providers/dns/ionos/ionos_test.go b/providers/dns/ionos/ionos_test.go index 5aef6ad14..7b1f5af11 100644 --- a/providers/dns/ionos/ionos_test.go +++ b/providers/dns/ionos/ionos_test.go @@ -37,6 +37,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -106,6 +107,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -119,6 +121,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/ipv64/internal/client.go b/providers/dns/ipv64/internal/client.go index 1cb9c532f..0dfd94374 100644 --- a/providers/dns/ipv64/internal/client.go +++ b/providers/dns/ipv64/internal/client.go @@ -131,6 +131,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errAPI := &APIError{} + err := json.Unmarshal(raw, errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/ipv64/internal/types.go b/providers/dns/ipv64/internal/types.go index e9e357ecc..6ef31a3cc 100644 --- a/providers/dns/ipv64/internal/types.go +++ b/providers/dns/ipv64/internal/types.go @@ -11,6 +11,7 @@ type APIResponse struct { type APIError struct { APIResponse + AddRecordMessage string `json:"add_record"` DelRecordMessage string `json:"del_record"` AddDomainMessage string `json:"add_domain"` @@ -41,6 +42,7 @@ func (a APIError) Error() string { type Domains struct { APIResponse + APICall string `json:"add_domain"` Subdomains map[string]Subdomain `json:"subdomains"` } diff --git a/providers/dns/ipv64/ipv64_test.go b/providers/dns/ipv64/ipv64_test.go index b3fe142e9..6dc7d1cfc 100644 --- a/providers/dns/ipv64/ipv64_test.go +++ b/providers/dns/ipv64/ipv64_test.go @@ -114,6 +114,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -171,6 +172,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -184,6 +186,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/joker/internal/dmapi/client.go b/providers/dns/joker/internal/dmapi/client.go index 6496abe2e..576410723 100644 --- a/providers/dns/joker/internal/dmapi/client.go +++ b/providers/dns/joker/internal/dmapi/client.go @@ -176,12 +176,15 @@ func RemoveTxtEntryFromZone(zone, relative string) (string, bool) { prefix := fmt.Sprintf("%s TXT 0 ", relative) modified := false + var zoneEntries []string + for line := range strings.Lines(zone) { if strings.HasPrefix(line, prefix) { modified = true continue } + zoneEntries = append(zoneEntries, line) } diff --git a/providers/dns/joker/internal/dmapi/identity.go b/providers/dns/joker/internal/dmapi/identity.go index 351d987e9..63c0b2ea1 100644 --- a/providers/dns/joker/internal/dmapi/identity.go +++ b/providers/dns/joker/internal/dmapi/identity.go @@ -24,6 +24,7 @@ type Token struct { // login performs a log in to Joker's DMAPI. func (c *Client) login(ctx context.Context) (*Response, error) { var values url.Values + switch { case c.username != "" && c.password != "": values = url.Values{ @@ -106,5 +107,6 @@ func formatResponseError(response *Response, err error) error { if response != nil { return fmt.Errorf("joker: DMAPI error: %w Response: %v", err, response.Headers) } + return fmt.Errorf("joker: DMAPI error: %w", err) } diff --git a/providers/dns/joker/joker_test.go b/providers/dns/joker/joker_test.go index 20e3fc7a5..bc21ccbbc 100644 --- a/providers/dns/joker/joker_test.go +++ b/providers/dns/joker/joker_test.go @@ -53,6 +53,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -112,6 +113,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -125,6 +127,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/joker/provider_dmapi.go b/providers/dns/joker/provider_dmapi.go index 7b32ce804..11f850136 100644 --- a/providers/dns/joker/provider_dmapi.go +++ b/providers/dns/joker/provider_dmapi.go @@ -28,6 +28,7 @@ func newDmapiProvider() (*dmapiProvider, error) { values, err := env.Get(EnvAPIKey) if err != nil { var errU error + values, errU = env.Get(EnvUsername, EnvPassword) if errU != nil { //nolint:errorlint // false-positive @@ -161,6 +162,7 @@ func (d *dmapiProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return formatResponseError(response, err) } + return nil } @@ -169,5 +171,6 @@ func formatResponseError(response *dmapi.Response, err error) error { if response != nil { return fmt.Errorf("joker: DMAPI error: %w Response: %v", err, response.Headers) } + return fmt.Errorf("joker: DMAPI error: %w", err) } diff --git a/providers/dns/joker/provider_dmapi_test.go b/providers/dns/joker/provider_dmapi_test.go index 4704f2b80..06f283872 100644 --- a/providers/dns/joker/provider_dmapi_test.go +++ b/providers/dns/joker/provider_dmapi_test.go @@ -58,6 +58,7 @@ func Test_newDmapiProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) diff --git a/providers/dns/joker/provider_svc_test.go b/providers/dns/joker/provider_svc_test.go index ad6c74c87..dc981b6b4 100644 --- a/providers/dns/joker/provider_svc_test.go +++ b/providers/dns/joker/provider_svc_test.go @@ -49,6 +49,7 @@ func Test_newSvcProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) diff --git a/providers/dns/keyhelp/internal/client.go b/providers/dns/keyhelp/internal/client.go index 3c731fc49..a5a80db5c 100644 --- a/providers/dns/keyhelp/internal/client.go +++ b/providers/dns/keyhelp/internal/client.go @@ -165,6 +165,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/keyhelp/keyhelp.go b/providers/dns/keyhelp/keyhelp.go index cbf641cfe..67ceaaa63 100644 --- a/providers/dns/keyhelp/keyhelp.go +++ b/providers/dns/keyhelp/keyhelp.go @@ -162,6 +162,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.domainIDsMu.Lock() domainID, ok := d.domainIDs[token] d.domainIDsMu.Unlock() + if !ok { return fmt.Errorf("keyhelp: unknown record ID for '%s'", info.EffectiveFQDN) } @@ -172,6 +173,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var records []internal.Record + for _, record := range domainRecords.Records.Other { if record.Type == "TXT" && record.Value == info.Value { continue diff --git a/providers/dns/keyhelp/keyhelp_test.go b/providers/dns/keyhelp/keyhelp_test.go index bdcf26ad4..8d8ac821d 100644 --- a/providers/dns/keyhelp/keyhelp_test.go +++ b/providers/dns/keyhelp/keyhelp_test.go @@ -53,6 +53,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -125,6 +126,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -138,6 +140,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/liara/internal/client.go b/providers/dns/liara/internal/client.go index 3d4af1d3b..93cdcf7c8 100644 --- a/providers/dns/liara/internal/client.go +++ b/providers/dns/liara/internal/client.go @@ -60,6 +60,7 @@ func (c *Client) GetRecords(ctx context.Context, domainName string) ([]Record, e } var response Response[[]Record] + err = json.Unmarshal(raw, &response) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -94,6 +95,7 @@ func (c *Client) CreateRecord(ctx context.Context, domainName string, record Rec } var response Response[*Record] + err = json.Unmarshal(raw, &response) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -128,6 +130,7 @@ func (c *Client) GetRecord(ctx context.Context, domainName, recordID string) (*R } var response Response[*Record] + err = json.Unmarshal(raw, &response) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -187,6 +190,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/liara/liara.go b/providers/dns/liara/liara.go index 7894afc98..b91b004cc 100644 --- a/providers/dns/liara/liara.go +++ b/providers/dns/liara/liara.go @@ -100,10 +100,12 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } retryClient := retryablehttp.NewClient() + retryClient.RetryMax = 5 if config.HTTPClient != nil { retryClient.HTTPClient = config.HTTPClient } + retryClient.Logger = log.Logger client := internal.NewClient( @@ -145,6 +147,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Contents: []internal.Content{{Text: info.Value}}, TTL: d.config.TTL, } + newRecord, err := d.client.CreateRecord(context.Background(), dns01.UnFqdn(authZone), record) if err != nil { return fmt.Errorf("liara: failed to create TXT record, fqdn=%s: %w", info.EffectiveFQDN, err) @@ -170,6 +173,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("liara: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/liara/liara_test.go b/providers/dns/liara/liara_test.go index 4256be55e..b1f3f77c9 100644 --- a/providers/dns/liara/liara_test.go +++ b/providers/dns/liara/liara_test.go @@ -38,6 +38,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -113,6 +114,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -126,6 +128,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/lightsail/lightsail.go b/providers/dns/lightsail/lightsail.go index ddaf7baca..95b07c503 100644 --- a/providers/dns/lightsail/lightsail.go +++ b/providers/dns/lightsail/lightsail.go @@ -99,6 +99,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { retryCount := min(attempt, 7) delay := (1 << uint(retryCount)) * (rand.Intn(50) + 200) + return time.Duration(delay) * time.Millisecond, nil }) }) diff --git a/providers/dns/lightsail/lightsail_integration_test.go b/providers/dns/lightsail/lightsail_integration_test.go index 718e57460..dc86bf079 100644 --- a/providers/dns/lightsail/lightsail_integration_test.go +++ b/providers/dns/lightsail/lightsail_integration_test.go @@ -34,6 +34,7 @@ func TestLiveTTL(t *testing.T) { require.NoError(t, err) svc := lightsail.NewFromConfig(cfg) + require.NoError(t, err) defer func() { diff --git a/providers/dns/lightsail/lightsail_test.go b/providers/dns/lightsail/lightsail_test.go index db69738fd..a6b46045e 100644 --- a/providers/dns/lightsail/lightsail_test.go +++ b/providers/dns/lightsail/lightsail_test.go @@ -34,6 +34,7 @@ var envTest = tester.NewEnvTest( func TestCredentialsFromEnv(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() _ = os.Setenv(envAwsAccessKeyID, "123") diff --git a/providers/dns/limacity/internal/client.go b/providers/dns/limacity/internal/client.go index 07622e121..ae6ab87eb 100644 --- a/providers/dns/limacity/internal/client.go +++ b/providers/dns/limacity/internal/client.go @@ -41,6 +41,7 @@ func (c *Client) GetDomains(ctx context.Context) ([]Domain, error) { } var results DomainsResponse + err = c.do(req, &results) if err != nil { return nil, err @@ -58,6 +59,7 @@ func (c *Client) GetRecords(ctx context.Context, domainID int) ([]Record, error) } var results RecordsResponse + err = c.do(req, &results) if err != nil { return nil, err @@ -75,6 +77,7 @@ func (c *Client) AddRecord(ctx context.Context, domainID int, record Record) err } var results APIResponse + err = c.do(req, &results) if err != nil { return err @@ -92,6 +95,7 @@ func (c *Client) UpdateRecord(ctx context.Context, domainID, recordID int, recor } var results APIResponse + err = c.do(req, &results) if err != nil { return err @@ -110,6 +114,7 @@ func (c *Client) DeleteRecord(ctx context.Context, domainID, recordID int) error } var results APIResponse + err = c.do(req, &results) if err != nil { return err @@ -177,6 +182,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIResponse + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/limacity/internal/types.go b/providers/dns/limacity/internal/types.go index 5fdbacef9..7411632ea 100644 --- a/providers/dns/limacity/internal/types.go +++ b/providers/dns/limacity/internal/types.go @@ -10,7 +10,7 @@ type RecordsResponse struct { } type NameserverRecordPayload struct { - Data Record `json:"nameserver_record,omitempty"` + Data Record `json:"nameserver_record"` } type DomainsResponse struct { diff --git a/providers/dns/limacity/limacity.go b/providers/dns/limacity/limacity.go index 502208f2a..9e1f58f1a 100644 --- a/providers/dns/limacity/limacity.go +++ b/providers/dns/limacity/limacity.go @@ -165,6 +165,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.domainIDsMu.Lock() domainID, ok := d.domainIDs[token] d.domainIDsMu.Unlock() + if !ok { return fmt.Errorf("limacity: unknown domain ID for '%s' '%s'", info.EffectiveFQDN, token) } @@ -175,6 +176,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var recordID int + for _, record := range records { if record.Type == "TXT" && record.Content == strconv.Quote(info.Value) { recordID = record.ID diff --git a/providers/dns/limacity/limacity_test.go b/providers/dns/limacity/limacity_test.go index 2834a5f1f..3301fcb2e 100644 --- a/providers/dns/limacity/limacity_test.go +++ b/providers/dns/limacity/limacity_test.go @@ -33,6 +33,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -92,6 +93,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -105,6 +107,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/linode/linode.go b/providers/dns/linode/linode.go index 449c84a1a..b03dee4f5 100644 --- a/providers/dns/linode/linode.go +++ b/providers/dns/linode/linode.go @@ -148,6 +148,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { } _, err = d.client.CreateDomainRecord(ctx, zone.domainID, createOpts) + return err } @@ -164,6 +165,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // Get all TXT records for the specified domain. listOpts := linodego.NewListOptions(0, `{"type":"TXT"}`) + resources, err := d.client.ListDomainRecords(ctx, zone.domainID, listOpts) if err != nil { return err @@ -196,6 +198,7 @@ func (d *DNSProvider) getHostedZoneInfo(ctx context.Context, fqdn string) (*host } listOpts := linodego.NewListOptions(0, string(filter)) + domains, err := d.client.ListDomains(ctx, listOpts) if err != nil { return nil, err diff --git a/providers/dns/linode/linode_test.go b/providers/dns/linode/linode_test.go index 08549ab7e..1c4903aca 100644 --- a/providers/dns/linode/linode_test.go +++ b/providers/dns/linode/linode_test.go @@ -39,6 +39,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -94,6 +95,7 @@ func TestNewDNSProviderConfig(t *testing.T) { func TestDNSProvider_Present(t *testing.T) { defer envTest.RestoreEnv() + os.Setenv(EnvToken, "testing") domain := "example.com" @@ -178,6 +180,7 @@ func TestDNSProvider_Present(t *testing.T) { func TestDNSProvider_CleanUp(t *testing.T) { defer envTest.RestoreEnv() + os.Setenv(EnvToken, "testing") domain := "example.com" diff --git a/providers/dns/liquidweb/liquidweb.go b/providers/dns/liquidweb/liquidweb.go index 2d0a46142..b56968fe3 100644 --- a/providers/dns/liquidweb/liquidweb.go +++ b/providers/dns/liquidweb/liquidweb.go @@ -159,6 +159,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } params := &network.DNSRecordParams{ID: recordID} + _, err := d.client.NetworkDNS.Delete(params) if err != nil { return fmt.Errorf("liquidweb: could not remove TXT record: %w", err) @@ -179,6 +180,7 @@ func (d *DNSProvider) findZone(domain string) (string, error) { // filter the zones on the account to only ones that match var zs []network.DNSZone + for _, item := range zones.Items { if strings.HasSuffix(domain, item.Name) { zs = append(zs, item) diff --git a/providers/dns/liquidweb/liquidweb_test.go b/providers/dns/liquidweb/liquidweb_test.go index b0788c7f5..26dc5bdc0 100644 --- a/providers/dns/liquidweb/liquidweb_test.go +++ b/providers/dns/liquidweb/liquidweb_test.go @@ -66,6 +66,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -248,6 +249,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/liquidweb/servermock_test.go b/providers/dns/liquidweb/servermock_test.go index 9cb434761..f211e7253 100644 --- a/providers/dns/liquidweb/servermock_test.go +++ b/providers/dns/liquidweb/servermock_test.go @@ -62,6 +62,7 @@ func mockAPICreate(recs map[int]network.DNSRecord) http.HandlerFunc { http.Error(rw, makeEncodingError(body), http.StatusBadRequest) return } + payload.Params.ID = types.FlexInt(rand.Intn(10000000)) payload.Params.ZoneID = types.FlexInt(mockAPIServerZones[payload.Params.Name]) @@ -69,6 +70,7 @@ func mockAPICreate(recs map[int]network.DNSRecord) http.HandlerFunc { http.Error(rw, "dns record already exists", http.StatusTeapot) return } + recs[int(payload.Params.ID)] = payload.Params resp, err := json.Marshal(payload.Params) @@ -76,6 +78,7 @@ func mockAPICreate(recs map[int]network.DNSRecord) http.HandlerFunc { http.Error(rw, "", http.StatusInternalServerError) return } + http.Error(rw, string(resp), http.StatusOK) } } @@ -109,6 +112,7 @@ func mockAPIDelete(recs map[int]network.DNSRecord) http.HandlerFunc { http.Error(rw, fmt.Sprintf(`{"error":"","error_class":"LW::Exception::RecordNotFound","field":"network_dns_rr","full_message":"Record 'network_dns_rr: %d' not found","input":"%d","public_message":null}`, payload.Params.ID, payload.Params.ID), http.StatusOK) return } + delete(recs, payload.Params.ID) http.Error(rw, fmt.Sprintf("{\"deleted\":%d}", payload.Params.ID), http.StatusOK) } @@ -141,6 +145,7 @@ func mockAPIListZones() http.HandlerFunc { case payload.Params.PageNum > len(mockZones): payload.Params.PageNum = len(mockZones) } + resp := mockZones[payload.Params.PageNum] resp.ItemTotal = types.FlexInt(len(mockAPIServerZones)) resp.PageNum = types.FlexInt(payload.Params.PageNum) @@ -276,10 +281,12 @@ func makeMockZones() (map[int]network.DNSZoneList, map[string]int) { } mockAPIServerZones := make(map[string]int) + for _, page := range mockZones { for _, zone := range page.Items { mockAPIServerZones[zone.Name] = int(zone.ID) } } + return mockZones, mockAPIServerZones } diff --git a/providers/dns/loopia/internal/types.go b/providers/dns/loopia/internal/types.go index c286c01fd..c3425c8b1 100644 --- a/providers/dns/loopia/internal/types.go +++ b/providers/dns/loopia/internal/types.go @@ -66,6 +66,7 @@ type response interface { type responseString struct { responseFault + Value string `xml:"params>param>value>string"` } @@ -88,6 +89,7 @@ func (e RPCError) Error() string { type recordObjectsResponse struct { responseFault + XMLName xml.Name `xml:"methodResponse"` Params []RecordObj `xml:"params>param>value>array>data>value>struct"` } @@ -102,6 +104,7 @@ type RecordObj struct { func (r *RecordObj) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { var name string + for { t, err := d.Token() if err != nil { @@ -144,6 +147,7 @@ func (r *RecordObj) decodeValueString(name string, d *xml.Decoder, start xml.Sta } s = strings.TrimSpace(s) + switch name { case "type": r.Type = s diff --git a/providers/dns/loopia/loopia_test.go b/providers/dns/loopia/loopia_test.go index e397c9639..b3163fc77 100644 --- a/providers/dns/loopia/loopia_test.go +++ b/providers/dns/loopia/loopia_test.go @@ -103,6 +103,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -192,6 +193,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -205,6 +207,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/luadns/internal/client.go b/providers/dns/luadns/internal/client.go index 8e46418f2..5ce9cca86 100644 --- a/providers/dns/luadns/internal/client.go +++ b/providers/dns/luadns/internal/client.go @@ -49,6 +49,7 @@ func (c *Client) ListZones(ctx context.Context) ([]DNSZone, error) { } var zones []DNSZone + err = c.do(req, &zones) if err != nil { return nil, fmt.Errorf("could not list zones: %w", err) @@ -68,6 +69,7 @@ func (c *Client) CreateRecord(ctx context.Context, zone DNSZone, newRecord DNSRe } var record *DNSRecord + err = c.do(req, &record) if err != nil { return nil, fmt.Errorf("could not create record %#v: %w", record, err) @@ -153,6 +155,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errResp errorResponse + err := json.Unmarshal(raw, &errResp) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/luadns/luadns_test.go b/providers/dns/luadns/luadns_test.go index ea4d06ae1..a1aa36872 100644 --- a/providers/dns/luadns/luadns_test.go +++ b/providers/dns/luadns/luadns_test.go @@ -58,6 +58,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -199,6 +200,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -212,6 +214,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/mailinabox/mailinabox_test.go b/providers/dns/mailinabox/mailinabox_test.go index 1b95c220d..11143a11f 100644 --- a/providers/dns/mailinabox/mailinabox_test.go +++ b/providers/dns/mailinabox/mailinabox_test.go @@ -59,6 +59,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -136,6 +137,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -149,6 +151,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/manageengine/internal/client.go b/providers/dns/manageengine/internal/client.go index debb62812..b5a7dbae7 100644 --- a/providers/dns/manageengine/internal/client.go +++ b/providers/dns/manageengine/internal/client.go @@ -109,6 +109,7 @@ func (c *Client) UpdateZoneRecord(ctx context.Context, record ZoneRecord) error if record.SpfTxtDomainID == 0 { return errors.New("SpfTxtDomainID is empty") } + if record.ZoneID == 0 { return errors.New("ZoneID is empty") } @@ -188,6 +189,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/manageengine/manageengine.go b/providers/dns/manageengine/manageengine.go index 3863a6597..76b6644c0 100644 --- a/providers/dns/manageengine/manageengine.go +++ b/providers/dns/manageengine/manageengine.go @@ -195,6 +195,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // Update the zone record. var values []string + for _, value := range record.Values { if value != info.Value { values = append(values, value) diff --git a/providers/dns/manageengine/manageengine_test.go b/providers/dns/manageengine/manageengine_test.go index 624459be9..215de68dd 100644 --- a/providers/dns/manageengine/manageengine_test.go +++ b/providers/dns/manageengine/manageengine_test.go @@ -50,6 +50,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -122,6 +123,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,6 +137,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/metaname/metaname.go b/providers/dns/metaname/metaname.go index 9b8c41def..d5d87dc4d 100644 --- a/providers/dns/metaname/metaname.go +++ b/providers/dns/metaname/metaname.go @@ -79,6 +79,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config.AccountReference == "" { return nil, errors.New("metaname: missing account reference") } + if config.APIKey == "" { return nil, errors.New("metaname: missing api key") } diff --git a/providers/dns/metaname/metaname_test.go b/providers/dns/metaname/metaname_test.go index 174af4014..855fc493d 100644 --- a/providers/dns/metaname/metaname_test.go +++ b/providers/dns/metaname/metaname_test.go @@ -51,6 +51,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -122,6 +123,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,6 +137,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/metaregistrar/internal/client.go b/providers/dns/metaregistrar/internal/client.go index 711a1e94c..df99d81ba 100644 --- a/providers/dns/metaregistrar/internal/client.go +++ b/providers/dns/metaregistrar/internal/client.go @@ -121,6 +121,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/metaregistrar/metaregistrar_test.go b/providers/dns/metaregistrar/metaregistrar_test.go index ffd7965d9..aa9bbbb58 100644 --- a/providers/dns/metaregistrar/metaregistrar_test.go +++ b/providers/dns/metaregistrar/metaregistrar_test.go @@ -33,6 +33,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -92,6 +93,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -105,6 +107,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/mijnhost/internal/client.go b/providers/dns/mijnhost/internal/client.go index 1a67a73b1..a51233211 100644 --- a/providers/dns/mijnhost/internal/client.go +++ b/providers/dns/mijnhost/internal/client.go @@ -47,6 +47,7 @@ func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) { } var results Response[DomainData] + err = c.do(req, &results) if err != nil { return nil, err @@ -66,6 +67,7 @@ func (c *Client) GetRecords(ctx context.Context, domain string) ([]Record, error } var results Response[RecordData] + err = c.do(req, &results) if err != nil { return nil, err @@ -151,6 +153,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/mijnhost/mijnhost_test.go b/providers/dns/mijnhost/mijnhost_test.go index a48f84ca8..c87ae0a40 100644 --- a/providers/dns/mijnhost/mijnhost_test.go +++ b/providers/dns/mijnhost/mijnhost_test.go @@ -33,6 +33,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -94,6 +95,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -107,6 +109,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/mittwald/internal/client.go b/providers/dns/mittwald/internal/client.go index 69222903d..2b1564dc1 100644 --- a/providers/dns/mittwald/internal/client.go +++ b/providers/dns/mittwald/internal/client.go @@ -47,6 +47,7 @@ func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) { } var result []Domain + err = c.do(req, &result) if err != nil { return nil, err @@ -66,6 +67,7 @@ func (c *Client) GetDNSZone(ctx context.Context, zoneID string) (*DNSZone, error } result := &DNSZone{} + err = c.do(req, result) if err != nil { return nil, err @@ -85,6 +87,7 @@ func (c *Client) ListDNSZones(ctx context.Context, projectID string) ([]DNSZone, } var result []DNSZone + err = c.do(req, &result) if err != nil { return nil, err @@ -104,6 +107,7 @@ func (c *Client) CreateDNSZone(ctx context.Context, zone CreateDNSZoneRequest) ( } result := &DNSZone{} + err = c.do(req, result) if err != nil { return nil, err @@ -197,6 +201,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response APIError + err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/mittwald/internal/types.go b/providers/dns/mittwald/internal/types.go index df10ab293..ce49cb820 100644 --- a/providers/dns/mittwald/internal/types.go +++ b/providers/dns/mittwald/internal/types.go @@ -1,6 +1,9 @@ package internal -import "fmt" +import ( + "fmt" + "strings" +) // https://api.mittwald.de/v2/docs/#/Domain/domain-list-domains @@ -36,7 +39,7 @@ type NewDNSZone struct { // https://api.mittwald.de/v2/docs/#/Domain/dns-update-record-set type TXTRecord struct { - Settings Settings `json:"settings,omitempty"` + Settings Settings `json:"settings"` Entries []string `json:"entries,omitempty"` } @@ -58,23 +61,25 @@ type APIError struct { } func (a APIError) Error() string { - msg := fmt.Sprintf("%s: %s", a.Type, a.Message) + var msg strings.Builder + + msg.WriteString(fmt.Sprintf("%s: %s", a.Type, a.Message)) if len(a.ValidationErrors) > 0 { for _, validationError := range a.ValidationErrors { - msg += fmt.Sprintf(" [%s: %s (%s, %s)]", - validationError.Type, validationError.Message, validationError.Path, validationError.Context.Format) + msg.WriteString(fmt.Sprintf(" [%s: %s (%s, %s)]", + validationError.Type, validationError.Message, validationError.Path, validationError.Context.Format)) } } - return msg + return msg.String() } type ValidationError struct { Message string `json:"message,omitempty"` Path string `json:"path,omitempty"` Type string `json:"type,omitempty"` - Context ValidationErrorContext `json:"context,omitempty"` + Context ValidationErrorContext `json:"context"` } type ValidationErrorContext struct { diff --git a/providers/dns/mittwald/mittwald.go b/providers/dns/mittwald/mittwald.go index f60745659..6292dd787 100644 --- a/providers/dns/mittwald/mittwald.go +++ b/providers/dns/mittwald/mittwald.go @@ -158,6 +158,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.zoneIDsMu.Lock() zoneID, ok := d.zoneIDs[token] d.zoneIDsMu.Unlock() + if !ok { return fmt.Errorf("mittwald: unknown zone ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/mittwald/mittwald_test.go b/providers/dns/mittwald/mittwald_test.go index d8cbdb263..6a6599536 100644 --- a/providers/dns/mittwald/mittwald_test.go +++ b/providers/dns/mittwald/mittwald_test.go @@ -38,6 +38,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -104,6 +105,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -117,6 +119,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/myaddr/myaddr_test.go b/providers/dns/myaddr/myaddr_test.go index d95a0cf5c..8e555ecfd 100644 --- a/providers/dns/myaddr/myaddr_test.go +++ b/providers/dns/myaddr/myaddr_test.go @@ -33,6 +33,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -92,6 +93,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -105,6 +107,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/mydnsjp/mydnsjp.go b/providers/dns/mydnsjp/mydnsjp.go index 934fe764a..8a790c88e 100644 --- a/providers/dns/mydnsjp/mydnsjp.go +++ b/providers/dns/mydnsjp/mydnsjp.go @@ -109,6 +109,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("mydnsjp: %w", err) } + return nil } @@ -121,5 +122,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("mydnsjp: %w", err) } + return nil } diff --git a/providers/dns/mydnsjp/mydnsjp_test.go b/providers/dns/mydnsjp/mydnsjp_test.go index 96eb95865..c82bd2264 100644 --- a/providers/dns/mydnsjp/mydnsjp_test.go +++ b/providers/dns/mydnsjp/mydnsjp_test.go @@ -56,6 +56,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -124,6 +125,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -137,6 +139,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/mythicbeasts/internal/client.go b/providers/dns/mythicbeasts/internal/client.go index 87464553c..82c51dbf3 100644 --- a/providers/dns/mythicbeasts/internal/client.go +++ b/providers/dns/mythicbeasts/internal/client.go @@ -99,6 +99,7 @@ func (c *Client) createTXTRecord(ctx context.Context, zone, leaf, recordType, va } resp := &createTXTResponse{} + err = c.do(req, resp) if err != nil { return nil, err diff --git a/providers/dns/mythicbeasts/internal/identity.go b/providers/dns/mythicbeasts/internal/identity.go index 417f1c759..15e35ba69 100644 --- a/providers/dns/mythicbeasts/internal/identity.go +++ b/providers/dns/mythicbeasts/internal/identity.go @@ -44,6 +44,7 @@ func (c *Client) obtainToken(ctx context.Context) (*Token, error) { } tok := Token{} + err = json.Unmarshal(raw, &tok) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -83,6 +84,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errResp := &authResponseError{} + err := json.Unmarshal(raw, errResp) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/mythicbeasts/mythicbeasts.go b/providers/dns/mythicbeasts/mythicbeasts.go index 30d1eaa3e..e8f5081f7 100644 --- a/providers/dns/mythicbeasts/mythicbeasts.go +++ b/providers/dns/mythicbeasts/mythicbeasts.go @@ -88,6 +88,7 @@ func NewDNSProvider() (*DNSProvider, error) { if err != nil { return nil, fmt.Errorf("mythicbeasts: %w", err) } + config.UserName = values[EnvUserName] config.Password = values[EnvPassword] diff --git a/providers/dns/mythicbeasts/mythicbeasts_test.go b/providers/dns/mythicbeasts/mythicbeasts_test.go index 5a8a9d4bb..c684725b7 100644 --- a/providers/dns/mythicbeasts/mythicbeasts_test.go +++ b/providers/dns/mythicbeasts/mythicbeasts_test.go @@ -57,6 +57,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -108,6 +109,7 @@ func TestNewDNSProviderConfig(t *testing.T) { t.Run(test.desc, func(t *testing.T) { config, err := NewDefaultConfig() require.NoError(t, err) + config.UserName = test.username config.Password = test.password @@ -130,6 +132,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -143,6 +146,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/namecheap/internal/client.go b/providers/dns/namecheap/internal/client.go index 0fb32b1be..6fb737b95 100644 --- a/providers/dns/namecheap/internal/client.go +++ b/providers/dns/namecheap/internal/client.go @@ -54,6 +54,7 @@ func (c *Client) GetHosts(ctx context.Context, sld, tld string) ([]Record, error } var ghr getHostsResponse + err = c.do(request, &ghr) if err != nil { return nil, err @@ -88,6 +89,7 @@ func (c *Client) SetHosts(ctx context.Context, sld, tld string, hosts []Record) } var shr setHostsResponse + err = c.do(req, &shr) if err != nil { return err @@ -96,6 +98,7 @@ func (c *Client) SetHosts(ctx context.Context, sld, tld string, hosts []Record) if len(shr.Errors) > 0 { return shr.Errors[0] } + if shr.Result.IsSuccess != "true" { return errors.New("setHosts failed") } diff --git a/providers/dns/namecheap/namecheap.go b/providers/dns/namecheap/namecheap.go index e21ddf556..cf8520546 100644 --- a/providers/dns/namecheap/namecheap.go +++ b/providers/dns/namecheap/namecheap.go @@ -118,6 +118,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if err != nil { return nil, fmt.Errorf("namecheap: %w", err) } + config.ClientIP = clientIP } @@ -174,6 +175,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("namecheap: %w", err) } + return nil } @@ -193,8 +195,11 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } // Find the challenge TXT record and remove it if found. - var found bool - var newRecords []internal.Record + var ( + found bool + newRecords []internal.Record + ) + for _, h := range records { if h.Name == pr.key && h.Type == "TXT" { found = true @@ -211,6 +216,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("namecheap: %w", err) } + return nil } diff --git a/providers/dns/namecheap/namecheap_test.go b/providers/dns/namecheap/namecheap_test.go index e0c947095..e55a4a6bc 100644 --- a/providers/dns/namecheap/namecheap_test.go +++ b/providers/dns/namecheap/namecheap_test.go @@ -152,6 +152,7 @@ func Test_newPseudoRecord_domainSplit(t *testing.T) { for _, test := range tests { t.Run(test.domain, func(t *testing.T) { valid := true + ch, err := newPseudoRecord(test.domain, "") if err != nil { valid = false diff --git a/providers/dns/namedotcom/namedotcom.go b/providers/dns/namedotcom/namedotcom.go index 1c0f162de..3d1f33af1 100644 --- a/providers/dns/namedotcom/namedotcom.go +++ b/providers/dns/namedotcom/namedotcom.go @@ -161,6 +161,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { DomainName: domain, ID: rec.ID, } + _, err := d.client.DeleteRecord(request) if err != nil { return fmt.Errorf("namedotcom: %w", err) @@ -184,6 +185,7 @@ func (d *DNSProvider) getRecords(domain string) ([]*namecom.Record, error) { } var records []*namecom.Record + for request.Page > 0 { response, err := d.client.ListRecords(request) if err != nil { diff --git a/providers/dns/namedotcom/namedotcom_test.go b/providers/dns/namedotcom/namedotcom_test.go index c7d4deaa1..da9878bdc 100644 --- a/providers/dns/namedotcom/namedotcom_test.go +++ b/providers/dns/namedotcom/namedotcom_test.go @@ -57,6 +57,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -131,6 +132,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -144,6 +146,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/namesilo/namesilo.go b/providers/dns/namesilo/namesilo.go index e2f66f8c9..0297b4e1c 100644 --- a/providers/dns/namesilo/namesilo.go +++ b/providers/dns/namesilo/namesilo.go @@ -118,6 +118,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("namesilo: failed to add record %w", err) } + return nil } diff --git a/providers/dns/namesilo/namesilo_test.go b/providers/dns/namesilo/namesilo_test.go index e3ef956bf..09eacd035 100644 --- a/providers/dns/namesilo/namesilo_test.go +++ b/providers/dns/namesilo/namesilo_test.go @@ -45,6 +45,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -112,6 +113,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/nearlyfreespeech/internal/client.go b/providers/dns/nearlyfreespeech/internal/client.go index fcfe4e9b7..5d7e79fbe 100644 --- a/providers/dns/nearlyfreespeech/internal/client.go +++ b/providers/dns/nearlyfreespeech/internal/client.go @@ -97,6 +97,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errAPI := &APIError{} + err := json.Unmarshal(raw, errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) @@ -118,7 +119,6 @@ func (c Signer) Sign(uri, body, login, apiKey string) string { // Header is "login;timestamp;salt;hash". // hash is SHA1("login;timestamp;salt;api-key;request-uri;body-hash") // and body-hash is SHA1(body). - bodyHash := sha1.Sum([]byte(body)) timestamp := strconv.FormatInt(c.clock().Unix(), 10) diff --git a/providers/dns/nearlyfreespeech/internal/client_test.go b/providers/dns/nearlyfreespeech/internal/client_test.go index 1445286c3..26e4552be 100644 --- a/providers/dns/nearlyfreespeech/internal/client_test.go +++ b/providers/dns/nearlyfreespeech/internal/client_test.go @@ -163,6 +163,7 @@ func TestSigner_Sign(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { t.Parallel() + signer := NewSigner() signer.saltShaker = func() []byte { return []byte(test.salt) } signer.clock = func() time.Time { return time.Unix(test.now, 0) } diff --git a/providers/dns/nearlyfreespeech/nearlyfreespeech_test.go b/providers/dns/nearlyfreespeech/nearlyfreespeech_test.go index adc7efe1e..b67b350e9 100644 --- a/providers/dns/nearlyfreespeech/nearlyfreespeech_test.go +++ b/providers/dns/nearlyfreespeech/nearlyfreespeech_test.go @@ -54,6 +54,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -126,6 +127,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -139,6 +141,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/netcup/internal/client.go b/providers/dns/netcup/internal/client.go index 553733175..1287a8d7a 100644 --- a/providers/dns/netcup/internal/client.go +++ b/providers/dns/netcup/internal/client.go @@ -80,6 +80,7 @@ func (c *Client) GetDNSRecords(ctx context.Context, hostname string) ([]DNSRecor } var responseData InfoDNSRecordsResponse + err := c.doRequest(ctx, payload, &responseData) if err != nil { return nil, fmt.Errorf("error when sending the request: %w", err) @@ -139,6 +140,7 @@ func GetDNSRecordIdx(records []DNSRecord, record DNSRecord) (int, error) { return index, nil } } + return -1, errors.New("no DNS Record found") } @@ -173,6 +175,7 @@ func unmarshalResponseMsg(req *http.Request, resp *http.Response) (*ResponseMsg, } var respMsg ResponseMsg + err = json.Unmarshal(raw, &respMsg) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/netcup/internal/session.go b/providers/dns/netcup/internal/session.go index 6627d74e1..b53751edf 100644 --- a/providers/dns/netcup/internal/session.go +++ b/providers/dns/netcup/internal/session.go @@ -24,6 +24,7 @@ func (c *Client) login(ctx context.Context) (string, error) { } var responseData LoginResponse + err := c.doRequest(ctx, payload, &responseData) if err != nil { return "", fmt.Errorf("loging error: %w", err) diff --git a/providers/dns/netcup/netcup_test.go b/providers/dns/netcup/netcup_test.go index a0c631f46..fedc56ba9 100644 --- a/providers/dns/netcup/netcup_test.go +++ b/providers/dns/netcup/netcup_test.go @@ -72,6 +72,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -158,6 +159,7 @@ func TestLivePresentAndCleanup(t *testing.T) { } envTest.RestoreEnv() + p, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/netlify/internal/client.go b/providers/dns/netlify/internal/client.go index a8e3b35c3..3b6b681fe 100644 --- a/providers/dns/netlify/internal/client.go +++ b/providers/dns/netlify/internal/client.go @@ -59,6 +59,7 @@ func (c *Client) GetRecords(ctx context.Context, zoneID string) ([]DNSRecord, er } var records []DNSRecord + err = json.Unmarshal(raw, &records) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -93,6 +94,7 @@ func (c *Client) CreateRecord(ctx context.Context, zoneID string, record DNSReco } var recordResp DNSRecord + err = json.Unmarshal(raw, &recordResp) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/netlify/netlify.go b/providers/dns/netlify/netlify.go index b95a1a128..5b2980d24 100644 --- a/providers/dns/netlify/netlify.go +++ b/providers/dns/netlify/netlify.go @@ -149,6 +149,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("netlify: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/netlify/netlify_test.go b/providers/dns/netlify/netlify_test.go index f351802da..1e84517be 100644 --- a/providers/dns/netlify/netlify_test.go +++ b/providers/dns/netlify/netlify_test.go @@ -36,6 +36,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -93,6 +94,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -106,6 +108,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/nicmanager/internal/client.go b/providers/dns/nicmanager/internal/client.go index eb84e29ec..16bfe497b 100644 --- a/providers/dns/nicmanager/internal/client.go +++ b/providers/dns/nicmanager/internal/client.go @@ -83,6 +83,7 @@ func (c *Client) GetZone(ctx context.Context, name string) (*Zone, error) { } var zone Zone + err = c.do(req, http.StatusOK, &zone) if err != nil { return nil, err diff --git a/providers/dns/nicmanager/nicmanager.go b/providers/dns/nicmanager/nicmanager.go index ff9a20125..9b27df64e 100644 --- a/providers/dns/nicmanager/nicmanager.go +++ b/providers/dns/nicmanager/nicmanager.go @@ -191,8 +191,11 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { name := dns01.UnFqdn(info.EffectiveFQDN) - var existingRecord internal.Record - var existingRecordFound bool + var ( + existingRecord internal.Record + existingRecordFound bool + ) + for _, record := range zone.Records { if strings.EqualFold(record.Type, "TXT") && strings.EqualFold(record.Name, name) && record.Content == info.Value { existingRecord = record diff --git a/providers/dns/nicmanager/nicmanager_test.go b/providers/dns/nicmanager/nicmanager_test.go index bc2f50cc3..114cdb7ca 100644 --- a/providers/dns/nicmanager/nicmanager_test.go +++ b/providers/dns/nicmanager/nicmanager_test.go @@ -66,6 +66,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -159,6 +160,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -172,6 +174,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/nicru/internal/client.go b/providers/dns/nicru/internal/client.go index 37acd68f1..5d851fc76 100644 --- a/providers/dns/nicru/internal/client.go +++ b/providers/dns/nicru/internal/client.go @@ -30,6 +30,7 @@ func (tr Trimmer) Token() (xml.Token, error) { if cd, ok := t.(xml.CharData); ok { t = xml.CharData(bytes.TrimSpace(cd)) } + return t, err } diff --git a/providers/dns/nicru/nicru_test.go b/providers/dns/nicru/nicru_test.go index 12db3a4ff..7e71f9d2c 100644 --- a/providers/dns/nicru/nicru_test.go +++ b/providers/dns/nicru/nicru_test.go @@ -75,6 +75,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -171,6 +172,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -184,6 +186,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/nifcloud/internal/client.go b/providers/dns/nifcloud/internal/client.go index 4469a1f78..0f3851883 100644 --- a/providers/dns/nifcloud/internal/client.go +++ b/providers/dns/nifcloud/internal/client.go @@ -59,6 +59,7 @@ func (c *Client) ChangeResourceRecordSets(ctx context.Context, hostedZoneID stri } output := &ChangeResourceRecordSetsResponse{} + err = c.do(req, output) if err != nil { return nil, err @@ -77,6 +78,7 @@ func (c *Client) GetChange(ctx context.Context, statusID string) (*GetChangeResp } output := &GetChangeResponse{} + err = c.do(req, output) if err != nil { return nil, err @@ -129,6 +131,7 @@ func (c *Client) sign(req *http.Request) error { } mac := hmac.New(sha1.New, []byte(c.secretKey)) + _, err := mac.Write([]byte(req.Header.Get("Date"))) if err != nil { return err @@ -148,6 +151,7 @@ func newXMLRequest(ctx context.Context, method string, endpoint *url.URL, payloa if payload != nil { body.WriteString(xml.Header) + err := xml.NewEncoder(body).Encode(payload) if err != nil { return nil, fmt.Errorf("failed to create request XML body: %w", err) @@ -170,6 +174,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errResp := &ErrorResponse{} + err := xml.Unmarshal(raw, errResp) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/nifcloud/nifcloud.go b/providers/dns/nifcloud/nifcloud.go index 2310d3805..ced7eff09 100644 --- a/providers/dns/nifcloud/nifcloud.go +++ b/providers/dns/nifcloud/nifcloud.go @@ -119,6 +119,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("nifcloud: %w", err) } + return err } @@ -132,6 +133,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("nifcloud: %w", err) } + return err } diff --git a/providers/dns/nifcloud/nifcloud_test.go b/providers/dns/nifcloud/nifcloud_test.go index 9b635edfc..0eff98a71 100644 --- a/providers/dns/nifcloud/nifcloud_test.go +++ b/providers/dns/nifcloud/nifcloud_test.go @@ -57,6 +57,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -129,6 +130,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -142,6 +144,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/njalla/internal/client.go b/providers/dns/njalla/internal/client.go index f64db3c80..d2893253f 100644 --- a/providers/dns/njalla/internal/client.go +++ b/providers/dns/njalla/internal/client.go @@ -46,6 +46,7 @@ func (c *Client) AddRecord(ctx context.Context, record Record) (*Record, error) } var result APIResponse[*Record] + err = c.do(req, &result) if err != nil { return nil, err @@ -92,6 +93,7 @@ func (c *Client) ListRecords(ctx context.Context, domain string) ([]Record, erro } var result APIResponse[Records] + err = c.do(req, &result) if err != nil { return nil, err diff --git a/providers/dns/njalla/njalla.go b/providers/dns/njalla/njalla.go index f35e32f37..2f9aef8ea 100644 --- a/providers/dns/njalla/njalla.go +++ b/providers/dns/njalla/njalla.go @@ -148,6 +148,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("njalla: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/njalla/njalla_test.go b/providers/dns/njalla/njalla_test.go index f1489257b..61f106d75 100644 --- a/providers/dns/njalla/njalla_test.go +++ b/providers/dns/njalla/njalla_test.go @@ -36,6 +36,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -95,6 +96,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -108,6 +110,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/nodion/nodion.go b/providers/dns/nodion/nodion.go index 55af3a847..e34d7db28 100644 --- a/providers/dns/nodion/nodion.go +++ b/providers/dns/nodion/nodion.go @@ -172,6 +172,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.zoneIDsMu.Lock() zoneID, ok := d.zoneIDs[token] d.zoneIDsMu.Unlock() + if !ok { return fmt.Errorf("nodion: unknown zone ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/nodion/nodion_test.go b/providers/dns/nodion/nodion_test.go index fbf4b89eb..0ec5c1627 100644 --- a/providers/dns/nodion/nodion_test.go +++ b/providers/dns/nodion/nodion_test.go @@ -34,6 +34,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -91,6 +92,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -104,6 +106,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/ns1/ns1.go b/providers/dns/ns1/ns1.go index 83faf7e5e..6a7846e85 100644 --- a/providers/dns/ns1/ns1.go +++ b/providers/dns/ns1/ns1.go @@ -147,10 +147,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } name := dns01.UnFqdn(info.EffectiveFQDN) + _, err = d.client.Records.Delete(zone.Zone, name, "TXT") if err != nil { return fmt.Errorf("ns1: failed to delete record [zone: %q, domain: %q]: %w", zone.Zone, name, err) } + return nil } diff --git a/providers/dns/ns1/ns1_test.go b/providers/dns/ns1/ns1_test.go index 6df6b4afb..82fa70c52 100644 --- a/providers/dns/ns1/ns1_test.go +++ b/providers/dns/ns1/ns1_test.go @@ -37,6 +37,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -96,6 +97,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -109,6 +111,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/octenium/octenium.go b/providers/dns/octenium/octenium.go index 383523575..af469f5ed 100644 --- a/providers/dns/octenium/octenium.go +++ b/providers/dns/octenium/octenium.go @@ -146,6 +146,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.domainIDsMu.Lock() domainID, ok := d.domainIDs[token] d.domainIDsMu.Unlock() + if !ok { return fmt.Errorf("octenium: unknown domain ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/octenium/octenium_test.go b/providers/dns/octenium/octenium_test.go index a6c801bad..dbb8d64b3 100644 --- a/providers/dns/octenium/octenium_test.go +++ b/providers/dns/octenium/octenium_test.go @@ -43,6 +43,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -106,6 +107,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -119,6 +121,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/oraclecloud/oraclecloud.go b/providers/dns/oraclecloud/oraclecloud.go index 4ef891322..730b3f212 100644 --- a/providers/dns/oraclecloud/oraclecloud.go +++ b/providers/dns/oraclecloud/oraclecloud.go @@ -218,6 +218,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var deleteHash *string + for _, record := range domainRecords.Items { if record.Rdata != nil && *record.Rdata == `"`+info.Value+`"` { deleteHash = record.RecordHash diff --git a/providers/dns/oraclecloud/oraclecloud_test.go b/providers/dns/oraclecloud/oraclecloud_test.go index 058566504..c646e90f2 100644 --- a/providers/dns/oraclecloud/oraclecloud_test.go +++ b/providers/dns/oraclecloud/oraclecloud_test.go @@ -6,6 +6,7 @@ import ( "crypto/x509" "encoding/base64" "encoding/pem" + "maps" "net/http/httptest" "os" "testing" @@ -187,8 +188,10 @@ func TestNewDNSProvider(t *testing.T) { if privKeyFile != "" { _ = os.Remove(privKeyFile) } + envTest.RestoreEnv() }() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -257,9 +260,7 @@ func TestNewDNSProvider_instance_principal(t *testing.T) { envSDKAuthClientRegionURL: serverURL, } - for k, v := range test.envVars { - envVars[k] = v - } + maps.Copy(envVars, test.envVars) envTest.Apply(envVars) @@ -332,6 +333,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -345,6 +347,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/otc/internal/client.go b/providers/dns/otc/internal/client.go index 0bb4dd323..adb0682e1 100644 --- a/providers/dns/otc/internal/client.go +++ b/providers/dns/otc/internal/client.go @@ -69,9 +69,11 @@ func (c *Client) getZones(ctx context.Context, zone string, privateZone bool) (* query := endpoint.Query() query.Set("name", zone) + if privateZone { query.Set("type", "private") } + endpoint.RawQuery = query.Encode() req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) @@ -80,6 +82,7 @@ func (c *Client) getZones(ctx context.Context, zone string, privateZone bool) (* } var zones ZonesResponse + err = c.do(req, &zones) if err != nil { return nil, err @@ -126,6 +129,7 @@ func (c *Client) getRecordSet(ctx context.Context, zoneID, fqdn string) (*Record } var recordSetsRes RecordSetsResponse + err = c.do(req, &recordSetsRes) if err != nil { return nil, err @@ -166,9 +170,11 @@ func (c *Client) DeleteRecordSet(ctx context.Context, zoneID, recordID string) e func (c *Client) do(req *http.Request, result any) error { c.muToken.Lock() + if c.token != "" { req.Header.Set("X-Auth-Token", c.token) } + c.muToken.Unlock() resp, err := c.HTTPClient.Do(req) diff --git a/providers/dns/otc/internal/identity.go b/providers/dns/otc/internal/identity.go index f9e7cb08f..154ec65e2 100644 --- a/providers/dns/otc/internal/identity.go +++ b/providers/dns/otc/internal/identity.go @@ -46,6 +46,7 @@ func (c *Client) Login(ctx context.Context) error { c.muToken.Lock() defer c.muToken.Unlock() + c.token = token if c.token == "" { @@ -96,6 +97,7 @@ func (c *Client) obtainUserToken(ctx context.Context, payload LoginRequest) (*To } var newToken TokenResponse + err = json.Unmarshal(raw, &newToken) if err != nil { return nil, "", errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -106,6 +108,7 @@ func (c *Client) obtainUserToken(ctx context.Context, payload LoginRequest) (*To func getBaseURL(tokenResp *TokenResponse) (*url.URL, error) { var endpoints []Endpoint + for _, v := range tokenResp.Token.Catalog { if v.Type == "dns" { endpoints = append(endpoints, v.Endpoints...) diff --git a/providers/dns/otc/internal/types.go b/providers/dns/otc/internal/types.go index 38da4f110..e7bfe8fcb 100644 --- a/providers/dns/otc/internal/types.go +++ b/providers/dns/otc/internal/types.go @@ -41,8 +41,8 @@ type TokenResponse struct { } type Token struct { - User UserR `json:"user,omitempty"` - Domain Domain `json:"domain,omitempty"` + User UserR `json:"user"` + Domain Domain `json:"domain"` Catalog []Catalog `json:"catalog,omitempty"` Methods []string `json:"methods,omitempty"` Roles []Role `json:"roles,omitempty"` @@ -59,7 +59,7 @@ type Catalog struct { type UserR struct { ID string `json:"id,omitempty"` - Domain Domain `json:"domain,omitempty"` + Domain Domain `json:"domain"` Name string `json:"name,omitempty"` PasswordExpiresAt string `json:"password_expires_at,omitempty"` } @@ -106,7 +106,7 @@ type RecordSets struct { // ZonesResponse type ZonesResponse struct { - Links Links `json:"links,omitempty"` + Links Links `json:"links"` Zones []Zone `json:"zones"` Metadata Metadata `json:"metadata"` } diff --git a/providers/dns/otc/otc_test.go b/providers/dns/otc/otc_test.go index 0b59e02ac..518ce0f19 100644 --- a/providers/dns/otc/otc_test.go +++ b/providers/dns/otc/otc_test.go @@ -93,6 +93,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -191,6 +192,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -204,6 +206,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/ovh/ovh.go b/providers/dns/ovh/ovh.go index 7ef89eff2..b7e522540 100644 --- a/providers/dns/ovh/ovh.go +++ b/providers/dns/ovh/ovh.go @@ -191,6 +191,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // Create TXT record var respData Record + err = d.client.Post(reqURL, reqData, &respData) if err != nil { return fmt.Errorf("ovh: error when call api to add record (%s): %w", reqURL, err) @@ -198,6 +199,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // Apply the change reqURL = fmt.Sprintf("/domain/zone/%s/refresh", authZone) + err = d.client.Post(reqURL, nil, nil) if err != nil { return fmt.Errorf("ovh: error when call api to refresh zone (%s): %w", reqURL, err) @@ -218,6 +220,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("ovh: unknown record ID for '%s'", info.EffectiveFQDN) } @@ -238,6 +241,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // Apply the change reqURL = fmt.Sprintf("/domain/zone/%s/refresh", authZone) + err = d.client.Post(reqURL, nil, nil) if err != nil { return fmt.Errorf("ovh: error when call api to refresh zone (%s): %w", reqURL, err) @@ -258,8 +262,10 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { } func newClient(config *Config) (*ovh.Client, error) { - var client *ovh.Client - var err error + var ( + client *ovh.Client + err error + ) switch { case config.hasAppKeyAuth(): diff --git a/providers/dns/ovh/ovh_test.go b/providers/dns/ovh/ovh_test.go index fcb2300b6..332e7f192 100644 --- a/providers/dns/ovh/ovh_test.go +++ b/providers/dns/ovh/ovh_test.go @@ -162,6 +162,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -315,6 +316,7 @@ func TestNewDNSProviderConfig(t *testing.T) { // The OVH client use the same env vars than lego, so it requires to clean them. defer envTest.RestoreEnv() + envTest.ClearEnv() for _, test := range testCases { @@ -354,6 +356,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -367,6 +370,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/pdns/internal/client.go b/providers/dns/pdns/internal/client.go index f6b55d5de..f72dd4d78 100644 --- a/providers/dns/pdns/internal/client.go +++ b/providers/dns/pdns/internal/client.go @@ -69,6 +69,7 @@ func (c *Client) getAPIVersion(ctx context.Context) (int, error) { } var versions []apiVersion + err = json.Unmarshal(result, &versions) if err != nil { return 0, err @@ -98,6 +99,7 @@ func (c *Client) GetHostedZone(ctx context.Context, authZone string) (*HostedZon } var zone HostedZone + err = json.Unmarshal(result, &zone) if err != nil { return nil, err @@ -180,6 +182,7 @@ func (c *Client) do(req *http.Request) (json.RawMessage, error) { } var msg json.RawMessage + err = json.NewDecoder(resp.Body).Decode(&msg) if err != nil { if errors.Is(err, io.EOF) { @@ -193,10 +196,12 @@ func (c *Client) do(req *http.Request) (json.RawMessage, error) { // check for PowerDNS error message if len(msg) > 0 && msg[0] == '{' { var errInfo apiError + err = json.Unmarshal(msg, &errInfo) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, msg, err) } + if errInfo.ShortMsg != "" { return nil, fmt.Errorf("error talking to PDNS API: %w", errInfo) } diff --git a/providers/dns/pdns/pdns.go b/providers/dns/pdns/pdns.go index 0d3c6fdea..e7ead7078 100644 --- a/providers/dns/pdns/pdns.go +++ b/providers/dns/pdns/pdns.go @@ -213,6 +213,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var records []internal.Record + for _, r := range set.Records { if r.Content != strconv.Quote(info.Value) { records = append(records, r) diff --git a/providers/dns/pdns/pdns_test.go b/providers/dns/pdns/pdns_test.go index 6762e892e..0213ba17c 100644 --- a/providers/dns/pdns/pdns_test.go +++ b/providers/dns/pdns/pdns_test.go @@ -57,6 +57,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -136,6 +137,7 @@ func TestLivePresentAndCleanup(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -155,5 +157,6 @@ func mustParse(rawURL string) *url.URL { if err != nil { panic(err) } + return u } diff --git a/providers/dns/plesk/internal/client.go b/providers/dns/plesk/internal/client.go index 88a7fdd9f..47abba805 100644 --- a/providers/dns/plesk/internal/client.go +++ b/providers/dns/plesk/internal/client.go @@ -121,6 +121,7 @@ func (c *Client) doRequest(ctx context.Context, payload RequestPacketType) (*Res endpoint := c.baseURL.JoinPath("/enterprise/control/agent.php") body := new(bytes.Buffer) + err := xml.NewEncoder(body).Encode(payload) if err != nil { return nil, err @@ -153,6 +154,7 @@ func (c *Client) doRequest(ctx context.Context, payload RequestPacketType) (*Res } var response ResponsePacketType + err = xml.Unmarshal(raw, &response) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/plesk/plesk.go b/providers/dns/plesk/plesk.go index f377cb8ac..b764dff33 100644 --- a/providers/dns/plesk/plesk.go +++ b/providers/dns/plesk/plesk.go @@ -163,6 +163,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("plesk: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/plesk/plesk_test.go b/providers/dns/plesk/plesk_test.go index 417e2c1da..506a26a2a 100644 --- a/providers/dns/plesk/plesk_test.go +++ b/providers/dns/plesk/plesk_test.go @@ -67,6 +67,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -149,6 +150,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -162,6 +164,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/porkbun/porkbun.go b/providers/dns/porkbun/porkbun.go index 4805418ee..dc9efb013 100644 --- a/providers/dns/porkbun/porkbun.go +++ b/providers/dns/porkbun/porkbun.go @@ -154,6 +154,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("porkbun: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/porkbun/porkbun_test.go b/providers/dns/porkbun/porkbun_test.go index cdf022b5d..7c69edfdb 100644 --- a/providers/dns/porkbun/porkbun_test.go +++ b/providers/dns/porkbun/porkbun_test.go @@ -54,6 +54,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -124,6 +125,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -137,6 +139,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/rackspace/internal/client.go b/providers/dns/rackspace/internal/client.go index 076409ebd..4a1872484 100644 --- a/providers/dns/rackspace/internal/client.go +++ b/providers/dns/rackspace/internal/client.go @@ -113,6 +113,7 @@ func (c *Client) listDomainsByName(ctx context.Context, domain string) (*ZoneSea } var zoneSearchResponse ZoneSearchResponse + err = c.do(req, &zoneSearchResponse) if err != nil { return nil, err @@ -154,6 +155,7 @@ func (c *Client) searchRecords(ctx context.Context, zoneID, recordName, recordTy } var records Records + err = c.do(req, &records) if err != nil { return nil, err diff --git a/providers/dns/rackspace/internal/identity.go b/providers/dns/rackspace/internal/identity.go index 062350df5..3ff667fb8 100644 --- a/providers/dns/rackspace/internal/identity.go +++ b/providers/dns/rackspace/internal/identity.go @@ -65,6 +65,7 @@ func (a *Identifier) Login(ctx context.Context, apiUser, apiKey string) (*Identi } var identity Identity + err = json.Unmarshal(raw, &identity) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/rackspace/rackspace.go b/providers/dns/rackspace/rackspace.go index f796a494d..b4c7b4a0f 100644 --- a/providers/dns/rackspace/rackspace.go +++ b/providers/dns/rackspace/rackspace.go @@ -99,6 +99,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { // Iterate through the Service Catalog to get the DNS Endpoint var dnsEndpoint string + for _, service := range identity.Access.ServiceCatalog { if service.Name == "cloudDNS" { dnsEndpoint = service.Endpoints[0].PublicURL diff --git a/providers/dns/rackspace/rackspace_test.go b/providers/dns/rackspace/rackspace_test.go index 4f270ee6c..de0749fd3 100644 --- a/providers/dns/rackspace/rackspace_test.go +++ b/providers/dns/rackspace/rackspace_test.go @@ -75,6 +75,7 @@ func TestLiveNewDNSProvider_ValidEnv(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -87,6 +88,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -100,6 +102,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/rainyun/internal/client.go b/providers/dns/rainyun/internal/client.go index 3d99bd9be..595b39f29 100644 --- a/providers/dns/rainyun/internal/client.go +++ b/providers/dns/rainyun/internal/client.go @@ -84,6 +84,7 @@ func (c *Client) ListRecords(ctx context.Context, domainID int) ([]Record, error } var recordData APIResponse[Record] + err = c.do(req, &recordData) if err != nil { return nil, err @@ -173,6 +174,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/rainyun/rainyun_test.go b/providers/dns/rainyun/rainyun_test.go index d0048e5d0..d27d47e81 100644 --- a/providers/dns/rainyun/rainyun_test.go +++ b/providers/dns/rainyun/rainyun_test.go @@ -33,6 +33,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -92,6 +93,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -105,6 +107,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/rcodezero/internal/client.go b/providers/dns/rcodezero/internal/client.go index d37fec2dd..5cf39907e 100644 --- a/providers/dns/rcodezero/internal/client.go +++ b/providers/dns/rcodezero/internal/client.go @@ -64,6 +64,7 @@ func (c *Client) do(req *http.Request) (*APIResponse, error) { } result := &APIResponse{} + raw, err := io.ReadAll(resp.Body) if err != nil { return nil, errutils.NewReadResponseError(req, resp.StatusCode, err) @@ -105,6 +106,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errAPI := &APIResponse{} + err := json.Unmarshal(raw, errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/rcodezero/rcodezero_test.go b/providers/dns/rcodezero/rcodezero_test.go index 1f0946072..a4a242c30 100644 --- a/providers/dns/rcodezero/rcodezero_test.go +++ b/providers/dns/rcodezero/rcodezero_test.go @@ -37,6 +37,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -94,6 +95,7 @@ func TestLivePresentAndCleanup(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/regfish/regfish.go b/providers/dns/regfish/regfish.go index fb2ffaeb6..85aac92e5 100644 --- a/providers/dns/regfish/regfish.go +++ b/providers/dns/regfish/regfish.go @@ -132,6 +132,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("regfish: unknown record ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/regfish/regfish_test.go b/providers/dns/regfish/regfish_test.go index 80928048f..6613bd508 100644 --- a/providers/dns/regfish/regfish_test.go +++ b/providers/dns/regfish/regfish_test.go @@ -33,6 +33,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -92,6 +93,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -105,6 +107,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/regru/internal/client.go b/providers/dns/regru/internal/client.go index 4b0205b0f..b0b86d567 100644 --- a/providers/dns/regru/internal/client.go +++ b/providers/dns/regru/internal/client.go @@ -111,6 +111,7 @@ func (c *Client) doRequest(ctx context.Context, request any, fragments ...string } var apiResp APIResponse + err = json.Unmarshal(raw, &apiResp) if err != nil { return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) @@ -123,6 +124,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIResponse + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/regru/regru_test.go b/providers/dns/regru/regru_test.go index 15d86d75c..762eeb4d3 100644 --- a/providers/dns/regru/regru_test.go +++ b/providers/dns/regru/regru_test.go @@ -57,6 +57,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -129,6 +130,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -142,6 +144,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/rfc2136/rfc2136.go b/providers/dns/rfc2136/rfc2136.go index 84655b450..2c4fe7aeb 100644 --- a/providers/dns/rfc2136/rfc2136.go +++ b/providers/dns/rfc2136/rfc2136.go @@ -171,6 +171,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("rfc2136: failed to insert: %w", err) } + return nil } @@ -182,6 +183,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("rfc2136: failed to remove: %w", err) } + return nil } @@ -228,6 +230,7 @@ func (d *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { if err != nil { return fmt.Errorf("DNS update failed: %w", err) } + if reply != nil && reply.Rcode != dns.RcodeSuccess { return fmt.Errorf("DNS update failed: server replied: %s", dns.RcodeToString[reply.Rcode]) } diff --git a/providers/dns/rfc2136/rfc2136_test.go b/providers/dns/rfc2136/rfc2136_test.go index 1dc7270d2..ce4859e84 100644 --- a/providers/dns/rfc2136/rfc2136_test.go +++ b/providers/dns/rfc2136/rfc2136_test.go @@ -84,6 +84,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -250,6 +251,7 @@ func TestDNSProvider_Present_error(t *testing.T) { err = provider.Present(fakeDomain, "", fakeKeyAuth) require.Error(t, err) + if !strings.Contains(err.Error(), "NOTZONE") { t.Errorf("Expected Present() to return an error with the 'NOTZONE' rcode string, but it did not: %v", err) } diff --git a/providers/dns/rimuhosting/rimuhosting_test.go b/providers/dns/rimuhosting/rimuhosting_test.go index cbdacedc4..d8b086e25 100644 --- a/providers/dns/rimuhosting/rimuhosting_test.go +++ b/providers/dns/rimuhosting/rimuhosting_test.go @@ -36,6 +36,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -97,6 +98,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -110,6 +112,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/route53/route53.go b/providers/dns/route53/route53.go index 6e66149da..b41c95dac 100644 --- a/providers/dns/route53/route53.go +++ b/providers/dns/route53/route53.go @@ -155,6 +155,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { realValue := `"` + info.Value + `"` var found bool + for _, record := range records { if ptr.Deref(record.Value) == realValue { found = true @@ -200,6 +201,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var nonLegoRecords []awstypes.ResourceRecord + for _, record := range existingRecords { if ptr.Deref(record.Value) != `"`+info.Value+`"` { nonLegoRecords = append(nonLegoRecords, record) @@ -312,12 +314,14 @@ func (d *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string, reqParams := &route53.ListHostedZonesByNameInput{ DNSName: aws.String(dns01.UnFqdn(authZone)), } + resp, err := d.client.ListHostedZonesByName(ctx, reqParams) if err != nil { return "", err } var hostedZoneID string + for _, hostedZone := range resp.HostedZones { // .Name has a trailing dot if ptr.Deref(hostedZone.Name) == authZone && d.config.PrivateZone == hostedZone.Config.PrivateZone { @@ -353,6 +357,7 @@ func createAWSConfig(ctx context.Context, config *Config) (aws.Config, error) { retryCount := min(attempt, 7) delay := (1 << uint(retryCount)) * (rand.Intn(50) + 200) + return time.Duration(delay) * time.Millisecond, nil }) }) diff --git a/providers/dns/route53/route53_test.go b/providers/dns/route53/route53_test.go index f9f3b6293..41ed824bc 100644 --- a/providers/dns/route53/route53_test.go +++ b/providers/dns/route53/route53_test.go @@ -34,6 +34,7 @@ var envTest = tester.NewEnvTest( func Test_loadCredentials_FromEnv(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() _ = os.Setenv(EnvAccessKeyID, "123") @@ -60,6 +61,7 @@ func Test_loadCredentials_FromEnv(t *testing.T) { func Test_loadRegion_FromEnv(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() _ = os.Setenv(EnvRegion, "foo") @@ -72,6 +74,7 @@ func Test_loadRegion_FromEnv(t *testing.T) { func Test_getHostedZoneID_FromEnv(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() expectedZoneID := "zoneID" @@ -128,6 +131,7 @@ func TestNewDefaultConfig(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { envTest.ClearEnv() + for key, value := range test.envVars { _ = os.Setenv(key, value) } @@ -141,6 +145,7 @@ func TestNewDefaultConfig(t *testing.T) { func TestDNSProvider_Present(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() provider := servermock.NewBuilder( @@ -271,6 +276,7 @@ func Test_createAWSConfig(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.env) diff --git a/providers/dns/safedns/internal/client.go b/providers/dns/safedns/internal/client.go index 3e6f99919..51b12e99d 100644 --- a/providers/dns/safedns/internal/client.go +++ b/providers/dns/safedns/internal/client.go @@ -48,6 +48,7 @@ func (c *Client) AddRecord(ctx context.Context, zone string, record Record) (*Ad } respData := &AddRecordResponse{} + err = c.do(req, respData) if err != nil { return nil, fmt.Errorf("add record: %w", err) @@ -132,6 +133,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/safedns/safedns.go b/providers/dns/safedns/safedns.go index ce5c27672..be8ca4fe6 100644 --- a/providers/dns/safedns/safedns.go +++ b/providers/dns/safedns/safedns.go @@ -146,6 +146,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("safedns: unknown record ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/safedns/safedns_test.go b/providers/dns/safedns/safedns_test.go index dcb374718..ce7568056 100644 --- a/providers/dns/safedns/safedns_test.go +++ b/providers/dns/safedns/safedns_test.go @@ -36,6 +36,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -95,6 +96,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -108,6 +110,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/sakuracloud/sakuracloud.go b/providers/dns/sakuracloud/sakuracloud.go index fad675611..1adbe3a88 100644 --- a/providers/dns/sakuracloud/sakuracloud.go +++ b/providers/dns/sakuracloud/sakuracloud.go @@ -171,6 +171,7 @@ func newCaller(opts *api.CallerOptions) iaas.APICaller { if strings.HasSuffix(opts.APIRootURL, "/") { opts.APIRootURL = strings.TrimRight(opts.APIRootURL, "/") } + iaas.SakuraCloudAPIRoot = opts.APIRootURL } diff --git a/providers/dns/sakuracloud/sakuracloud_test.go b/providers/dns/sakuracloud/sakuracloud_test.go index 93cf20ea1..789a27544 100644 --- a/providers/dns/sakuracloud/sakuracloud_test.go +++ b/providers/dns/sakuracloud/sakuracloud_test.go @@ -57,6 +57,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -129,6 +130,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -142,6 +144,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/sakuracloud/wrapper.go b/providers/dns/sakuracloud/wrapper.go index c23a8b590..ff0b78e09 100644 --- a/providers/dns/sakuracloud/wrapper.go +++ b/providers/dns/sakuracloud/wrapper.go @@ -61,6 +61,7 @@ func (d *DNSProvider) cleanupTXTRecord(ctx context.Context, fqdn, value string) } var updRecords iaas.DNSRecords + for _, r := range zone.Records { if !(r.Name == subDomain && r.Type == "TXT" && r.RData == value) { //nolint:staticcheck // Clearer without De Morgan's law. updRecords = append(updRecords, r) @@ -71,6 +72,7 @@ func (d *DNSProvider) cleanupTXTRecord(ctx context.Context, fqdn, value string) Records: updRecords, SettingsHash: zone.SettingsHash, } + _, err = d.client.UpdateSettings(ctx, zone.ID, settings) if err != nil { return fmt.Errorf("API call failed: %w", err) diff --git a/providers/dns/sakuracloud/wrapper_test.go b/providers/dns/sakuracloud/wrapper_test.go index 958ef1b88..7432c67a6 100644 --- a/providers/dns/sakuracloud/wrapper_test.go +++ b/providers/dns/sakuracloud/wrapper_test.go @@ -44,6 +44,7 @@ func createDummyZone(t *testing.T, caller iaas.APICaller) { if zone.Name == "example.com" { err = dnsOp.Delete(ctx, zone.ID) require.NoError(t, err) + break } } @@ -96,6 +97,7 @@ func TestDNSProvider_concurrentAddAndCleanupRecords(t *testing.T) { dummyRecordCount := 10 var providers []*DNSProvider + for range dummyRecordCount { config := NewDefaultConfig() config.Token = "token3" diff --git a/providers/dns/scaleway/scaleway_test.go b/providers/dns/scaleway/scaleway_test.go index bf950e84e..b683d751a 100644 --- a/providers/dns/scaleway/scaleway_test.go +++ b/providers/dns/scaleway/scaleway_test.go @@ -41,6 +41,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -105,6 +106,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -118,6 +120,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/selectel/selectel.go b/providers/dns/selectel/selectel.go index a7fb97cac..804ef04d5 100644 --- a/providers/dns/selectel/selectel.go +++ b/providers/dns/selectel/selectel.go @@ -101,6 +101,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = clientdebug.Wrap(client.HTTPClient) var err error + client.BaseURL, err = url.Parse(config.BaseURL) if err != nil { return nil, fmt.Errorf("selectel: %w", err) @@ -133,6 +134,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Name: info.EffectiveFQDN, Content: info.Value, } + _, err = d.client.AddRecord(ctx, domainObj.ID, txtRecord) if err != nil { return fmt.Errorf("selectel: %w", err) @@ -162,6 +164,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // Delete records with specific FQDN var lastErr error + for _, record := range records { if record.Name == recordName { err = d.client.DeleteRecord(ctx, domainObj.ID, record.ID) diff --git a/providers/dns/selectel/selectel_test.go b/providers/dns/selectel/selectel_test.go index 0e2de2dbe..e3c36e226 100644 --- a/providers/dns/selectel/selectel_test.go +++ b/providers/dns/selectel/selectel_test.go @@ -36,6 +36,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -106,6 +107,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -119,6 +121,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/selectelv2/selectelv2_test.go b/providers/dns/selectelv2/selectelv2_test.go index 265c17e90..2627fa023 100644 --- a/providers/dns/selectelv2/selectelv2_test.go +++ b/providers/dns/selectelv2/selectelv2_test.go @@ -78,6 +78,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -170,6 +171,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -183,6 +185,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/selfhostde/mapping.go b/providers/dns/selfhostde/mapping.go index 0984419ef..fe11ceda1 100644 --- a/providers/dns/selfhostde/mapping.go +++ b/providers/dns/selfhostde/mapping.go @@ -88,8 +88,10 @@ func parseLine(line string) (string, *Seq, error) { name, rawIDs := line[:idx], line[idx+1:] - var ids []string - var count int + var ( + ids []string + count int + ) for { idx, err = safeIndex(rawIDs, recordSep) diff --git a/providers/dns/selfhostde/selfhostde.go b/providers/dns/selfhostde/selfhostde.go index ccaba4647..bb475deea 100644 --- a/providers/dns/selfhostde/selfhostde.go +++ b/providers/dns/selfhostde/selfhostde.go @@ -176,6 +176,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("selfhostde: unknown record ID for %q", dns01.UnFqdn(info.EffectiveFQDN)) } diff --git a/providers/dns/selfhostde/selfhostde_test.go b/providers/dns/selfhostde/selfhostde_test.go index 1161049b0..7c12195fa 100644 --- a/providers/dns/selfhostde/selfhostde_test.go +++ b/providers/dns/selfhostde/selfhostde_test.go @@ -71,6 +71,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -185,6 +186,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -198,6 +200,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/servercow/internal/client.go b/providers/dns/servercow/internal/client.go index 3695b0979..e15237201 100644 --- a/providers/dns/servercow/internal/client.go +++ b/providers/dns/servercow/internal/client.go @@ -47,6 +47,7 @@ func (c *Client) GetRecords(ctx context.Context, domain string) ([]Record, error } var records []Record + err = c.do(req, &records) if err != nil { return nil, err @@ -65,6 +66,7 @@ func (c *Client) CreateUpdateRecord(ctx context.Context, domain string, data Rec } var msg Message + err = c.do(req, &msg) if err != nil { return nil, err @@ -87,6 +89,7 @@ func (c *Client) DeleteRecord(ctx context.Context, domain string, data Record) ( } var msg Message + err = c.do(req, &msg) if err != nil { return nil, err @@ -168,6 +171,7 @@ func unmarshal(raw []byte, v any) error { } var apiErr Message + errU := json.Unmarshal(raw, &apiErr) if errU != nil { return fmt.Errorf("unmarshaling %T error: %w: %s", v, err, string(raw)) diff --git a/providers/dns/servercow/internal/types.go b/providers/dns/servercow/internal/types.go index 5a8fb6ff8..9a951e806 100644 --- a/providers/dns/servercow/internal/types.go +++ b/providers/dns/servercow/internal/types.go @@ -43,6 +43,7 @@ func (v *Value) UnmarshalJSON(b []byte) error { } *v = append(*v, s) + return nil } diff --git a/providers/dns/servercow/servercow.go b/providers/dns/servercow/servercow.go index 8583e7d9e..557c6b1ec 100644 --- a/providers/dns/servercow/servercow.go +++ b/providers/dns/servercow/servercow.go @@ -140,6 +140,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("servercow: failed to update TXT records: %w", err) } + return nil } @@ -194,6 +195,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("servercow: failed to delete TXT records: %w", err) } + return nil } diff --git a/providers/dns/servercow/servercow_test.go b/providers/dns/servercow/servercow_test.go index 1c3facad9..f2328fe1a 100644 --- a/providers/dns/servercow/servercow_test.go +++ b/providers/dns/servercow/servercow_test.go @@ -57,6 +57,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -129,6 +130,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -142,6 +144,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/shellrent/internal/client.go b/providers/dns/shellrent/internal/client.go index 8ec02bfc0..a70ff5452 100644 --- a/providers/dns/shellrent/internal/client.go +++ b/providers/dns/shellrent/internal/client.go @@ -114,6 +114,7 @@ func (c *Client) GetDomainDetails(ctx context.Context, domainID int) (*DomainDet if result.Code != 0 { return nil, result.Base } + return result.Data, nil } @@ -137,6 +138,7 @@ func (c *Client) CreateRecord(ctx context.Context, domainID int, record Record) if result.Code != 0 { return 0, result.Base } + return result.Data.ID.Value(), nil } @@ -219,6 +221,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response Base + err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/shellrent/internal/types.go b/providers/dns/shellrent/internal/types.go index a27b06347..6bdd82330 100644 --- a/providers/dns/shellrent/internal/types.go +++ b/providers/dns/shellrent/internal/types.go @@ -7,6 +7,7 @@ import ( type Response[T any] struct { Base + Data T `json:"data"` } @@ -57,6 +58,7 @@ func (m *IntOrString) UnmarshalJSON(data []byte) error { raw := string(data) if data[0] == '"' { var err error + raw, err = strconv.Unquote(string(data)) if err != nil { return err diff --git a/providers/dns/shellrent/shellrent.go b/providers/dns/shellrent/shellrent.go index bc8809943..5a3a1f6de 100644 --- a/providers/dns/shellrent/shellrent.go +++ b/providers/dns/shellrent/shellrent.go @@ -162,6 +162,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() key, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("shellrent: unknown request key for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/shellrent/shellrent_test.go b/providers/dns/shellrent/shellrent_test.go index e5d529917..8c4e3f6bf 100644 --- a/providers/dns/shellrent/shellrent_test.go +++ b/providers/dns/shellrent/shellrent_test.go @@ -47,6 +47,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -117,6 +118,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -130,6 +132,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/simply/internal/client.go b/providers/dns/simply/internal/client.go index 47e602d92..0c0655463 100644 --- a/providers/dns/simply/internal/client.go +++ b/providers/dns/simply/internal/client.go @@ -60,6 +60,7 @@ func (c *Client) GetRecords(ctx context.Context, zoneName string) ([]Record, err } result := &apiResponse[[]Record, json.RawMessage]{} + err = c.do(req, result) if err != nil { return nil, err @@ -78,6 +79,7 @@ func (c *Client) AddRecord(ctx context.Context, zoneName string, record Record) } result := &apiResponse[json.RawMessage, recordHeader]{} + err = c.do(req, result) if err != nil { return 0, err diff --git a/providers/dns/simply/simply.go b/providers/dns/simply/simply.go index 434bb0d30..fc3afd310 100644 --- a/providers/dns/simply/simply.go +++ b/providers/dns/simply/simply.go @@ -165,6 +165,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("simply: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/simply/simply_test.go b/providers/dns/simply/simply_test.go index ace8e0b72..e6de60d43 100644 --- a/providers/dns/simply/simply_test.go +++ b/providers/dns/simply/simply_test.go @@ -53,6 +53,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -121,6 +122,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -134,6 +136,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/sonic/internal/client.go b/providers/dns/sonic/internal/client.go index 3007a8248..cf8f7f067 100644 --- a/providers/dns/sonic/internal/client.go +++ b/providers/dns/sonic/internal/client.go @@ -83,6 +83,7 @@ func (c *Client) SetRecord(ctx context.Context, hostname, value string, ttl int) } r := APIResponse{} + err = json.Unmarshal(raw, &r) if err != nil { return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/sonic/sonic_test.go b/providers/dns/sonic/sonic_test.go index f9087f8e3..7dc7fc586 100644 --- a/providers/dns/sonic/sonic_test.go +++ b/providers/dns/sonic/sonic_test.go @@ -49,6 +49,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -119,6 +120,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -132,6 +134,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/spaceship/internal/client.go b/providers/dns/spaceship/internal/client.go index f739e01ba..e690fa467 100644 --- a/providers/dns/spaceship/internal/client.go +++ b/providers/dns/spaceship/internal/client.go @@ -114,6 +114,7 @@ func (c *Client) GetRecords(ctx context.Context, domain string) ([]Record, error } var result GetRecordsResponse + err = c.do(req, &result) if err != nil { return nil, err @@ -150,6 +151,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/spaceship/spaceship_test.go b/providers/dns/spaceship/spaceship_test.go index ba60cfa5e..d4eb37d88 100644 --- a/providers/dns/spaceship/spaceship_test.go +++ b/providers/dns/spaceship/spaceship_test.go @@ -50,6 +50,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -122,6 +123,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,6 +137,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/stackpath/internal/client.go b/providers/dns/stackpath/internal/client.go index f06feb807..8a40a4093 100644 --- a/providers/dns/stackpath/internal/client.go +++ b/providers/dns/stackpath/internal/client.go @@ -55,6 +55,7 @@ func (c *Client) GetZones(ctx context.Context, domain string) (*Zone, error) { req.URL.RawQuery = query.Encode() var zones Zones + err = c.do(req, &zones) if err != nil { return nil, err @@ -82,6 +83,7 @@ func (c *Client) GetZoneRecords(ctx context.Context, name string, zone *Zone) ([ req.URL.RawQuery = query.Encode() var records Records + err = c.do(req, &records) if err != nil { return nil, err @@ -177,6 +179,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) errResp := &ErrorResponse{} + err := json.Unmarshal(raw, errResp) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/stackpath/stackpath_test.go b/providers/dns/stackpath/stackpath_test.go index f8b83140f..a4b959222 100644 --- a/providers/dns/stackpath/stackpath_test.go +++ b/providers/dns/stackpath/stackpath_test.go @@ -72,6 +72,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -137,6 +138,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -150,6 +152,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/technitium/internal/client.go b/providers/dns/technitium/internal/client.go index a68008d34..965638b1d 100644 --- a/providers/dns/technitium/internal/client.go +++ b/providers/dns/technitium/internal/client.go @@ -125,6 +125,7 @@ func (c *Client) newFormRequest(ctx context.Context, endpoint *url.URL, payload if payload != nil { var err error + values, err = querystring.Values(payload) if err != nil { return nil, fmt.Errorf("failed to create request body: %w", err) @@ -149,6 +150,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIResponse[any] + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/technitium/technitium_test.go b/providers/dns/technitium/technitium_test.go index da50b6fe6..4eee530fd 100644 --- a/providers/dns/technitium/technitium_test.go +++ b/providers/dns/technitium/technitium_test.go @@ -50,6 +50,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -122,6 +123,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,6 +137,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/tencentcloud/tencentcloud_test.go b/providers/dns/tencentcloud/tencentcloud_test.go index c5a2fd974..ce6358174 100644 --- a/providers/dns/tencentcloud/tencentcloud_test.go +++ b/providers/dns/tencentcloud/tencentcloud_test.go @@ -55,6 +55,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -127,6 +128,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -140,6 +142,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/tencentcloud/wrapper.go b/providers/dns/tencentcloud/wrapper.go index 6fdb7f899..6a66bc1c6 100644 --- a/providers/dns/tencentcloud/wrapper.go +++ b/providers/dns/tencentcloud/wrapper.go @@ -38,6 +38,7 @@ func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*dnspod } var hostedZone *dnspod.DomainListItem + for _, zone := range domains { unfqdn := dns01.UnFqdn(authZone) if *zone.Name == unfqdn || *zone.Punycode == unfqdn { @@ -73,6 +74,7 @@ func (d *DNSProvider) findTxtRecords(ctx context.Context, zone *dnspod.DomainLis return nil, nil } } + return nil, err } diff --git a/providers/dns/timewebcloud/internal/client.go b/providers/dns/timewebcloud/internal/client.go index b3030861e..ec3c8703d 100644 --- a/providers/dns/timewebcloud/internal/client.go +++ b/providers/dns/timewebcloud/internal/client.go @@ -49,6 +49,7 @@ func (c *Client) CreateRecord(ctx context.Context, zone string, record DNSRecord } respData := &CreateRecordResponse{} + err = c.do(req, respData) if err != nil { return nil, err @@ -127,6 +128,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response ErrorResponse + err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/timewebcloud/timewebcloud.go b/providers/dns/timewebcloud/timewebcloud.go index 0d3a36b46..d71beea4b 100644 --- a/providers/dns/timewebcloud/timewebcloud.go +++ b/providers/dns/timewebcloud/timewebcloud.go @@ -145,6 +145,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("timewebcloud: unknown record ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/timewebcloud/timewebcloud_test.go b/providers/dns/timewebcloud/timewebcloud_test.go index cd3e2e26f..26e107578 100644 --- a/providers/dns/timewebcloud/timewebcloud_test.go +++ b/providers/dns/timewebcloud/timewebcloud_test.go @@ -36,6 +36,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -97,6 +98,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -110,6 +112,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/transip/transip.go b/providers/dns/transip/transip.go index a58a1bfe0..bc2913aa4 100644 --- a/providers/dns/transip/transip.go +++ b/providers/dns/transip/transip.go @@ -168,6 +168,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err = d.repository.RemoveDNSEntry(domainName, entry); err != nil { return fmt.Errorf("transip: couldn't get Record ID in CleanUp: %w", err) } + return nil } } diff --git a/providers/dns/transip/transip_test.go b/providers/dns/transip/transip_test.go index b42753680..3c6e86657 100644 --- a/providers/dns/transip/transip_test.go +++ b/providers/dns/transip/transip_test.go @@ -58,6 +58,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -79,6 +80,7 @@ func TestNewDNSProvider(t *testing.T) { // Therefore, we test if the error type is the same. t.Run("could not open private key path", func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(map[string]string{ @@ -156,6 +158,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -169,6 +172,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/ultradns/ultradns.go b/providers/dns/ultradns/ultradns.go index 0515cbaae..da76c56f4 100644 --- a/providers/dns/ultradns/ultradns.go +++ b/providers/dns/ultradns/ultradns.go @@ -4,6 +4,7 @@ package ultradns import ( "errors" "fmt" + "net/http" "time" "github.com/go-acme/lego/v4/challenge" @@ -121,7 +122,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { RecordType: "TXT", } - res, _, _ := recordService.Read(rrSetKeyData) + resp, _, _ := recordService.Read(rrSetKeyData) rrSetData := &rrset.RRSet{ OwnerName: info.EffectiveFQDN, @@ -130,11 +131,12 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { RData: []string{info.Value}, } - if res != nil && res.StatusCode == 200 { + if resp != nil && resp.StatusCode == http.StatusOK { _, err = recordService.Update(rrSetKeyData, rrSetData) } else { _, err = recordService.Create(rrSetKeyData, rrSetData) } + if err != nil { return fmt.Errorf("ultradns: %w", err) } diff --git a/providers/dns/ultradns/ultradns_test.go b/providers/dns/ultradns/ultradns_test.go index eefa63ec3..464bc51cd 100644 --- a/providers/dns/ultradns/ultradns_test.go +++ b/providers/dns/ultradns/ultradns_test.go @@ -177,6 +177,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/variomedia/internal/client.go b/providers/dns/variomedia/internal/client.go index 8c2a124cc..0e4ef9518 100644 --- a/providers/dns/variomedia/internal/client.go +++ b/providers/dns/variomedia/internal/client.go @@ -52,6 +52,7 @@ func (c *Client) CreateDNSRecord(ctx context.Context, record DNSRecord) (*Create } var result CreateDNSRecordResponse + err = c.do(req, &result) if err != nil { return nil, err @@ -71,6 +72,7 @@ func (c *Client) DeleteDNSRecord(ctx context.Context, id string) (*DeleteRecordR } var result DeleteRecordResponse + err = c.do(req, &result) if err != nil { return nil, err @@ -90,6 +92,7 @@ func (c *Client) GetJob(ctx context.Context, id string) (*GetJobResponse, error) } var result GetJobResponse + err = c.do(req, &result) if err != nil { return nil, err @@ -153,6 +156,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var errAPI APIError + err := json.Unmarshal(raw, &errAPI) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/variomedia/variomedia.go b/providers/dns/variomedia/variomedia.go index 2dbf546b1..90ac70a05 100644 --- a/providers/dns/variomedia/variomedia.go +++ b/providers/dns/variomedia/variomedia.go @@ -165,6 +165,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("variomedia: unknown record ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/variomedia/variomedia_test.go b/providers/dns/variomedia/variomedia_test.go index 305646070..552419fd0 100644 --- a/providers/dns/variomedia/variomedia_test.go +++ b/providers/dns/variomedia/variomedia_test.go @@ -33,6 +33,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -91,6 +92,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -104,6 +106,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/vegadns/vegadns_test.go b/providers/dns/vegadns/vegadns_test.go index fe40057c4..988326670 100644 --- a/providers/dns/vegadns/vegadns_test.go +++ b/providers/dns/vegadns/vegadns_test.go @@ -19,6 +19,7 @@ var envTest = tester.NewEnvTest(EnvKey, EnvSecret, EnvURL) func TestNewDNSProvider_Fail(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() _, err := NewDNSProvider() @@ -27,6 +28,7 @@ func TestNewDNSProvider_Fail(t *testing.T) { func TestDNSProvider_TimeoutSuccess(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() provider := mockBuilder().Build(t) @@ -79,6 +81,7 @@ func TestDNSProvider_Present(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() provider := test.builder.Build(t) @@ -138,6 +141,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() provider := test.builder.Build(t) @@ -167,6 +171,7 @@ func getDomainHandler() http.HandlerFunc { ] } `) + return } diff --git a/providers/dns/vercel/internal/client.go b/providers/dns/vercel/internal/client.go index d852689ae..930f3543e 100644 --- a/providers/dns/vercel/internal/client.go +++ b/providers/dns/vercel/internal/client.go @@ -51,6 +51,7 @@ func (c *Client) CreateRecord(ctx context.Context, zone string, record Record) ( } respData := &CreateRecordResponse{} + err = c.do(req, respData) if err != nil { return nil, err @@ -135,6 +136,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var response APIErrorResponse + err := json.Unmarshal(raw, &response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/vercel/vercel.go b/providers/dns/vercel/vercel.go index 447165262..965e3de12 100644 --- a/providers/dns/vercel/vercel.go +++ b/providers/dns/vercel/vercel.go @@ -148,6 +148,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("vercel: unknown record ID for '%s'", info.EffectiveFQDN) } diff --git a/providers/dns/vercel/vercel_test.go b/providers/dns/vercel/vercel_test.go index 6c19a4db5..d4cf37904 100644 --- a/providers/dns/vercel/vercel_test.go +++ b/providers/dns/vercel/vercel_test.go @@ -36,6 +36,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -95,6 +96,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -108,6 +110,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/versio/internal/client.go b/providers/dns/versio/internal/client.go index e91913556..6a92cc958 100644 --- a/providers/dns/versio/internal/client.go +++ b/providers/dns/versio/internal/client.go @@ -48,6 +48,7 @@ func (c *Client) UpdateDomain(ctx context.Context, domain string, msg *DomainInf } respData := &DomainInfoResponse{} + err = c.do(req, respData) if err != nil { return nil, err @@ -71,6 +72,7 @@ func (c *Client) GetDomain(ctx context.Context, domain string) (*DomainInfoRespo } respData := &DomainInfoResponse{} + err = c.do(req, respData) if err != nil { return nil, err @@ -88,6 +90,7 @@ func (c *Client) do(req *http.Request, result any) error { if resp != nil { defer func() { _ = resp.Body.Close() }() } + if err != nil { return errutils.NewHTTPDoError(req, err) } @@ -140,6 +143,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) response := &ErrorResponse{} + err := json.Unmarshal(raw, response) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/versio/versio.go b/providers/dns/versio/versio.go index bc999a674..05a7263c4 100644 --- a/providers/dns/versio/versio.go +++ b/providers/dns/versio/versio.go @@ -92,9 +92,11 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config == nil { return nil, errors.New("versio: the configuration of the DNS provider is nil") } + if config.Username == "" { return nil, errors.New("versio: the versio username is missing") } + if config.Password == "" { return nil, errors.New("versio: the versio password is missing") } @@ -158,6 +160,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("versio: %w", err) } + return nil } @@ -185,6 +188,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // loop through the existing entries and remove the specific record msg := &internal.DomainInfo{} + for _, e := range domains.DomainInfo.DNSRecords { if e.Name != info.EffectiveFQDN { msg.DNSRecords = append(msg.DNSRecords, e) @@ -195,5 +199,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("versio: %w", err) } + return nil } diff --git a/providers/dns/versio/versio_test.go b/providers/dns/versio/versio_test.go index 9e3db8b0d..563e70d05 100644 --- a/providers/dns/versio/versio_test.go +++ b/providers/dns/versio/versio_test.go @@ -54,6 +54,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -161,6 +162,7 @@ func TestDNSProvider_Present(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() provider := test.builder.Build(t) @@ -204,6 +206,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() provider := test.builder.Build(t) @@ -224,6 +227,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -237,6 +241,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/vinyldns/vinyldns.go b/providers/dns/vinyldns/vinyldns.go index 62d7c3442..65a024513 100644 --- a/providers/dns/vinyldns/vinyldns.go +++ b/providers/dns/vinyldns/vinyldns.go @@ -175,6 +175,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { value := d.formatValue(info.Value) var records []vinyldns.Record + for _, i := range existingRecord.Records { if i.Text != value { records = append(records, i) diff --git a/providers/dns/vinyldns/vinyldns_test.go b/providers/dns/vinyldns/vinyldns_test.go index c4741ea1c..7dfe2c13f 100644 --- a/providers/dns/vinyldns/vinyldns_test.go +++ b/providers/dns/vinyldns/vinyldns_test.go @@ -78,6 +78,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -246,6 +247,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -259,6 +261,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/vinyldns/wrapper.go b/providers/dns/vinyldns/wrapper.go index e247fd265..e7b59a82b 100644 --- a/providers/dns/vinyldns/wrapper.go +++ b/providers/dns/vinyldns/wrapper.go @@ -27,6 +27,7 @@ func (d *DNSProvider) getRecordSet(fqdn string) (*vinyldns.RecordSet, error) { } var recordSets []vinyldns.RecordSet + for _, i := range allRecordSets { if i.Type == "TXT" { recordSets = append(recordSets, i) diff --git a/providers/dns/vkcloud/internal/client.go b/providers/dns/vkcloud/internal/client.go index 5ced88d2d..2b03518db 100644 --- a/providers/dns/vkcloud/internal/client.go +++ b/providers/dns/vkcloud/internal/client.go @@ -46,6 +46,7 @@ func (c *Client) ListZones() ([]DNSZone, error) { endpoint := c.baseURL.JoinPath("/") var zones []DNSZone + opts := &gophercloud.RequestOpts{JSONResponse: &zones} err := c.request(http.MethodGet, endpoint, opts) @@ -60,6 +61,7 @@ func (c *Client) ListTXTRecords(zoneUUID string) ([]DNSTXTRecord, error) { endpoint := c.baseURL.JoinPath(zoneUUID, "txt", "/") var records []DNSTXTRecord + opts := &gophercloud.RequestOpts{JSONResponse: &records} err := c.request(http.MethodGet, endpoint, opts) diff --git a/providers/dns/vkcloud/vkcloud.go b/providers/dns/vkcloud/vkcloud.go index 2aea7838c..ffacdbe52 100644 --- a/providers/dns/vkcloud/vkcloud.go +++ b/providers/dns/vkcloud/vkcloud.go @@ -135,6 +135,7 @@ func (d *DNSProvider) Present(domain, _, keyAuth string) error { } var zoneUUID string + for _, zone := range zones { if zone.Zone == authZone { zoneUUID = zone.UUID diff --git a/providers/dns/vkcloud/vkcloud_test.go b/providers/dns/vkcloud/vkcloud_test.go index edc32363a..e7883b486 100644 --- a/providers/dns/vkcloud/vkcloud_test.go +++ b/providers/dns/vkcloud/vkcloud_test.go @@ -60,6 +60,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -188,6 +189,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -201,6 +203,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/volcengine/volcengine.go b/providers/dns/volcengine/volcengine.go index a271e0f26..9a5886e6d 100644 --- a/providers/dns/volcengine/volcengine.go +++ b/providers/dns/volcengine/volcengine.go @@ -159,6 +159,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("volcengine: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/volcengine/volcengine_test.go b/providers/dns/volcengine/volcengine_test.go index 5e9167612..0f79ed83a 100644 --- a/providers/dns/volcengine/volcengine_test.go +++ b/providers/dns/volcengine/volcengine_test.go @@ -55,6 +55,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -125,6 +126,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -138,6 +140,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/vscale/vscale.go b/providers/dns/vscale/vscale.go index 1ecff3a60..01fae946d 100644 --- a/providers/dns/vscale/vscale.go +++ b/providers/dns/vscale/vscale.go @@ -101,6 +101,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = clientdebug.Wrap(client.HTTPClient) var err error + client.BaseURL, err = url.Parse(config.BaseURL) if err != nil { return nil, fmt.Errorf("vscale: %w", err) @@ -133,6 +134,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Name: info.EffectiveFQDN, Content: info.Value, } + _, err = d.client.AddRecord(ctx, domainObj.ID, txtRecord) if err != nil { return fmt.Errorf("vscale: %w", err) @@ -162,6 +164,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // Delete records with specific FQDN var lastErr error + for _, record := range records { if record.Name == recordName { err = d.client.DeleteRecord(ctx, domainObj.ID, record.ID) diff --git a/providers/dns/vscale/vscale_test.go b/providers/dns/vscale/vscale_test.go index 6a9b25583..f3bc15531 100644 --- a/providers/dns/vscale/vscale_test.go +++ b/providers/dns/vscale/vscale_test.go @@ -36,6 +36,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -106,6 +107,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -119,6 +121,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/vultr/vultr.go b/providers/dns/vultr/vultr.go index 73e3480a2..2064cee19 100644 --- a/providers/dns/vultr/vultr.go +++ b/providers/dns/vultr/vultr.go @@ -136,6 +136,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var allErr []string + for _, rec := range records { err := d.client.DomainRecord.Delete(ctx, zoneDomain, rec.ID) if err != nil { @@ -205,6 +206,7 @@ func (d *DNSProvider) findTxtRecords(ctx context.Context, domain, fqdn string) ( listOptions := &govultr.ListOptions{PerPage: 25} var records []govultr.DomainRecord + for { result, meta, resp, err := d.client.DomainRecord.List(ctx, zoneDomain, listOptions) if err != nil { diff --git a/providers/dns/vultr/vultr_test.go b/providers/dns/vultr/vultr_test.go index f8b8fcd1f..17d962b2a 100644 --- a/providers/dns/vultr/vultr_test.go +++ b/providers/dns/vultr/vultr_test.go @@ -45,6 +45,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -221,6 +222,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -234,6 +236,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/webnames/internal/client.go b/providers/dns/webnames/internal/client.go index 5b1a8b357..985503d2a 100644 --- a/providers/dns/webnames/internal/client.go +++ b/providers/dns/webnames/internal/client.go @@ -83,6 +83,7 @@ func (c *Client) doRequest(ctx context.Context, data url.Values) error { } var r APIResponse + err = json.Unmarshal(raw, &r) if err != nil { return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) diff --git a/providers/dns/webnames/webnames_test.go b/providers/dns/webnames/webnames_test.go index 3ec69501f..553841e32 100644 --- a/providers/dns/webnames/webnames_test.go +++ b/providers/dns/webnames/webnames_test.go @@ -36,6 +36,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -93,6 +94,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -106,6 +108,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/websupport/websupport_test.go b/providers/dns/websupport/websupport_test.go index 051fdb837..c7b8572b5 100644 --- a/providers/dns/websupport/websupport_test.go +++ b/providers/dns/websupport/websupport_test.go @@ -50,6 +50,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -124,6 +125,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -137,6 +139,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/wedos/internal/client.go b/providers/dns/wedos/internal/client.go index 1f573e397..48c89d189 100644 --- a/providers/dns/wedos/internal/client.go +++ b/providers/dns/wedos/internal/client.go @@ -69,6 +69,7 @@ func (c *Client) AddRecord(ctx context.Context, zone string, record DNSRow) erro } cmd := commandDNSRowAdd + if record.ID == "" { payload.Name = record.Name } else { diff --git a/providers/dns/wedos/internal/token.go b/providers/dns/wedos/internal/token.go index dd126b442..11e680cb8 100644 --- a/providers/dns/wedos/internal/token.go +++ b/providers/dns/wedos/internal/token.go @@ -15,6 +15,7 @@ func authToken(userName, wapiPass string) string { func sha1string(txt string) string { h := sha1.New() _, _ = io.WriteString(h, txt) + return hex.EncodeToString(h.Sum(nil)) } @@ -46,11 +47,13 @@ func utcToCet(utc time.Time) time.Time { if utcMonth < time.March || utcMonth > time.October { return utc.Add(time.Hour) } + if utcMonth > time.March && utcMonth < time.October { return utc.Add(time.Hour * 2) } dayOff := 0 + breaking := time.Date(utc.Year(), utcMonth+1, dayOff, 1, 0, 0, 0, time.UTC) for breaking.Weekday() != time.Sunday { dayOff-- @@ -65,6 +68,7 @@ func utcToCet(utc time.Time) time.Time { if (utcMonth == time.March && utc.Before(breaking)) || (utcMonth == time.October && utc.After(breaking)) { return utc.Add(time.Hour) } + return utc.Add(time.Hour * 2) } diff --git a/providers/dns/wedos/wedos_test.go b/providers/dns/wedos/wedos_test.go index 9363002b5..25f70d0fc 100644 --- a/providers/dns/wedos/wedos_test.go +++ b/providers/dns/wedos/wedos_test.go @@ -54,6 +54,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -120,6 +121,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -133,6 +135,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/westcn/westcn.go b/providers/dns/westcn/westcn.go index 7efcfab21..c641f661d 100644 --- a/providers/dns/westcn/westcn.go +++ b/providers/dns/westcn/westcn.go @@ -148,6 +148,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { d.recordIDsMu.Lock() recordID, ok := d.recordIDs[token] d.recordIDsMu.Unlock() + if !ok { return fmt.Errorf("westcn: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) } diff --git a/providers/dns/westcn/westcn_test.go b/providers/dns/westcn/westcn_test.go index 71632d99f..36827fd06 100644 --- a/providers/dns/westcn/westcn_test.go +++ b/providers/dns/westcn/westcn_test.go @@ -50,6 +50,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -122,6 +123,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,6 +137,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/yandex/internal/client.go b/providers/dns/yandex/internal/client.go index 98480a793..4b0421f49 100644 --- a/providers/dns/yandex/internal/client.go +++ b/providers/dns/yandex/internal/client.go @@ -51,6 +51,7 @@ func (c *Client) AddRecord(ctx context.Context, payload Record) (*Record, error) } r := AddResponse{} + err = c.do(req, &r) if err != nil { return nil, err @@ -68,6 +69,7 @@ func (c *Client) RemoveRecord(ctx context.Context, payload Record) (int, error) } r := RemoveResponse{} + err = c.do(req, &r) if err != nil { return 0, err @@ -89,6 +91,7 @@ func (c *Client) GetRecords(ctx context.Context, domain string) ([]Record, error } r := ListResponse{} + err = c.do(req, &r) if err != nil { return nil, err diff --git a/providers/dns/yandex/internal/types.go b/providers/dns/yandex/internal/types.go index ed1873cef..48a85042c 100644 --- a/providers/dns/yandex/internal/types.go +++ b/providers/dns/yandex/internal/types.go @@ -30,18 +30,21 @@ func (r BaseResponse) GetError() string { type AddResponse struct { BaseResponse + Domain string `json:"domain,omitempty"` Record *Record `json:"record,omitempty"` } type RemoveResponse struct { BaseResponse + Domain string `json:"domain,omitempty"` RecordID int `json:"record_id,omitempty"` } type ListResponse struct { BaseResponse + Domain string `json:"domain,omitempty"` Records []Record `json:"records,omitempty"` } diff --git a/providers/dns/yandex/yandex.go b/providers/dns/yandex/yandex.go index d832f8859..7ae505ec0 100644 --- a/providers/dns/yandex/yandex.go +++ b/providers/dns/yandex/yandex.go @@ -136,6 +136,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var record *internal.Record + for _, rcd := range records { if rcd.Type == "TXT" && rcd.SubDomain == subDomain && rcd.Content == info.Value { record = &rcd @@ -156,6 +157,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("yandex: %w", err) } + return nil } diff --git a/providers/dns/yandex/yandex_test.go b/providers/dns/yandex/yandex_test.go index 144a24126..8a0a7534a 100644 --- a/providers/dns/yandex/yandex_test.go +++ b/providers/dns/yandex/yandex_test.go @@ -33,6 +33,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -95,6 +96,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -108,6 +110,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/yandex360/internal/client.go b/providers/dns/yandex360/internal/client.go index 9ffececaf..33aeb0daa 100644 --- a/providers/dns/yandex360/internal/client.go +++ b/providers/dns/yandex360/internal/client.go @@ -138,6 +138,7 @@ func parseError(req *http.Request, resp *http.Response) error { raw, _ := io.ReadAll(resp.Body) var apiErr APIError + err := json.Unmarshal(raw, &apiErr) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/yandex360/yandex360_test.go b/providers/dns/yandex360/yandex360_test.go index 545c90985..c1d37ad12 100644 --- a/providers/dns/yandex360/yandex360_test.go +++ b/providers/dns/yandex360/yandex360_test.go @@ -43,6 +43,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -109,6 +110,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -122,6 +124,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/yandexcloud/yandexcloud.go b/providers/dns/yandexcloud/yandexcloud.go index 346b6d952..f9c64def1 100644 --- a/providers/dns/yandexcloud/yandexcloud.go +++ b/providers/dns/yandexcloud/yandexcloud.go @@ -229,6 +229,7 @@ func (d *DNSProvider) upsertRecordSetData(ctx context.Context, zoneID, name, val } var deletions []*ycdnsproto.RecordSet + if exist != nil { record.SetData(append(record.GetData(), exist.GetData()...)) deletions = append(deletions, exist) @@ -307,6 +308,7 @@ func decodeCredentials(accountB64 string) (credentials.Credentials, error) { } key := &iamkey.Key{} + err = json.Unmarshal(account, key) if err != nil { return nil, err diff --git a/providers/dns/yandexcloud/yandexcloud_test.go b/providers/dns/yandexcloud/yandexcloud_test.go index 48f75d134..52dad574d 100644 --- a/providers/dns/yandexcloud/yandexcloud_test.go +++ b/providers/dns/yandexcloud/yandexcloud_test.go @@ -71,6 +71,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -143,6 +144,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -156,6 +158,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/zoneedit/internal/client.go b/providers/dns/zoneedit/internal/client.go index e97f4beb9..c8b99e173 100644 --- a/providers/dns/zoneedit/internal/client.go +++ b/providers/dns/zoneedit/internal/client.go @@ -98,6 +98,7 @@ func (c *Client) do(req *http.Request) error { } var apiErr APIError + err = xml.Unmarshal(raw, &apiErr) if err != nil { return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) diff --git a/providers/dns/zoneedit/zoneedit_test.go b/providers/dns/zoneedit/zoneedit_test.go index 2a9b1754d..0b251fddf 100644 --- a/providers/dns/zoneedit/zoneedit_test.go +++ b/providers/dns/zoneedit/zoneedit_test.go @@ -50,6 +50,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -122,6 +123,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -135,6 +137,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/dns/zoneee/zoneee.go b/providers/dns/zoneee/zoneee.go index 82e8effaf..5c34ea1c9 100644 --- a/providers/dns/zoneee/zoneee.go +++ b/providers/dns/zoneee/zoneee.go @@ -70,6 +70,7 @@ func NewDNSProvider() (*DNSProvider, error) { } rawEndpoint := env.GetOrDefaultString(EnvEndpoint, internal.DefaultEndpoint) + endpoint, err := url.Parse(rawEndpoint) if err != nil { return nil, fmt.Errorf("zoneee: %w", err) @@ -142,6 +143,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("zoneee: %w", err) } + return nil } @@ -164,6 +166,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } var id string + for _, record := range records { if record.Destination == info.Value { id = record.ID diff --git a/providers/dns/zoneee/zoneee_test.go b/providers/dns/zoneee/zoneee_test.go index 32090a41b..9ad87c02a 100644 --- a/providers/dns/zoneee/zoneee_test.go +++ b/providers/dns/zoneee/zoneee_test.go @@ -77,6 +77,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -247,6 +248,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -260,6 +262,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -286,6 +289,7 @@ func mockBuilder(username, apiKey string) *servermock.Builder[*DNSProvider] { func mockHandlerCreateRecord() http.HandlerFunc { return encodeJSONHandler(func(req *http.Request, rw http.ResponseWriter) (any, error) { record := internal.TXTRecord{} + err := json.NewDecoder(req.Body).Decode(&record) if err != nil { return nil, err @@ -340,6 +344,7 @@ func checkBasicAuth() servermock.LinkFunc { if username != fakeUsername || apiKey != fakeAPIKey || !ok { rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm=%q`, "Please enter your username and API key.")) http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + return } diff --git a/providers/dns/zonomi/zonomi_test.go b/providers/dns/zonomi/zonomi_test.go index fb1b68773..0583f4a1c 100644 --- a/providers/dns/zonomi/zonomi_test.go +++ b/providers/dns/zonomi/zonomi_test.go @@ -36,6 +36,7 @@ func TestNewDNSProvider(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { defer envTest.RestoreEnv() + envTest.ClearEnv() envTest.Apply(test.envVars) @@ -97,6 +98,7 @@ func TestLivePresent(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) @@ -110,6 +112,7 @@ func TestLiveCleanUp(t *testing.T) { } envTest.RestoreEnv() + provider, err := NewDNSProvider() require.NoError(t, err) diff --git a/providers/http/memcached/memcached.go b/providers/http/memcached/memcached.go index b26def2c4..376ae8c16 100644 --- a/providers/http/memcached/memcached.go +++ b/providers/http/memcached/memcached.go @@ -33,12 +33,14 @@ func (w *HTTPProvider) Present(domain, token, keyAuth string) error { var errs []error challengePath := path.Join("/", http01.ChallengePath(token)) + for _, host := range w.hosts { mc, err := memcache.New(host) if err != nil { errs = append(errs, err) continue } + _ = mc.Add(&memcache.Item{ Key: challengePath, Value: []byte(keyAuth), diff --git a/providers/http/memcached/memcached_test.go b/providers/http/memcached/memcached_test.go index fb450f988..5862efbc6 100644 --- a/providers/http/memcached/memcached_test.go +++ b/providers/http/memcached/memcached_test.go @@ -25,6 +25,7 @@ func loadMemcachedHosts() []string { if memcachedHostsStr != "" { return strings.Split(memcachedHostsStr, ",") } + return nil } @@ -38,6 +39,7 @@ func TestNewMemcachedProviderValid(t *testing.T) { if len(memcachedHosts) == 0 { t.Skip("Skipping memcached tests") } + _, err := NewMemcachedProvider(memcachedHosts) require.NoError(t, err) } @@ -46,6 +48,7 @@ func TestMemcachedPresentSingleHost(t *testing.T) { if len(memcachedHosts) == 0 { t.Skip("Skipping memcached tests") } + p, err := NewMemcachedProvider(memcachedHosts[0:1]) require.NoError(t, err) @@ -64,6 +67,7 @@ func TestMemcachedPresentMultiHost(t *testing.T) { if len(memcachedHosts) <= 1 { t.Skip("Skipping memcached multi-host tests") } + p, err := NewMemcachedProvider(memcachedHosts) require.NoError(t, err) @@ -71,6 +75,7 @@ func TestMemcachedPresentMultiHost(t *testing.T) { err = p.Present(domain, token, keyAuth) require.NoError(t, err) + for _, host := range memcachedHosts { mc, err := memcache.New(host) require.NoError(t, err) @@ -84,6 +89,7 @@ func TestMemcachedPresentPartialFailureMultiHost(t *testing.T) { if len(memcachedHosts) == 0 { t.Skip("Skipping memcached tests") } + hosts := append(memcachedHosts, "5.5.5.5:11211") p, err := NewMemcachedProvider(hosts) require.NoError(t, err) @@ -92,6 +98,7 @@ func TestMemcachedPresentPartialFailureMultiHost(t *testing.T) { err = p.Present(domain, token, keyAuth) require.NoError(t, err) + for _, host := range memcachedHosts { mc, err := memcache.New(host) require.NoError(t, err) @@ -105,6 +112,7 @@ func TestMemcachedCleanup(t *testing.T) { if len(memcachedHosts) == 0 { t.Skip("Skipping memcached tests") } + p, err := NewMemcachedProvider(memcachedHosts) require.NoError(t, err) require.NoError(t, p.CleanUp(domain, token, keyAuth)) diff --git a/providers/http/s3/s3.go b/providers/http/s3/s3.go index 07e1eed63..e277deeea 100644 --- a/providers/http/s3/s3.go +++ b/providers/http/s3/s3.go @@ -57,6 +57,7 @@ func (s *HTTPProvider) Present(domain, token, keyAuth string) error { if err != nil { return fmt.Errorf("s3: failed to upload token to s3: %w", err) } + return nil } diff --git a/providers/http/webroot/webroot.go b/providers/http/webroot/webroot.go index c5b49caee..c94c4579c 100644 --- a/providers/http/webroot/webroot.go +++ b/providers/http/webroot/webroot.go @@ -29,6 +29,7 @@ func (w *HTTPProvider) Present(domain, token, keyAuth string) error { var err error challengeFilePath := filepath.Join(w.path, http01.ChallengePath(token)) + err = os.MkdirAll(filepath.Dir(challengeFilePath), 0o755) if err != nil { return fmt.Errorf("could not create required directories in webroot for HTTP challenge: %w", err) diff --git a/providers/http/webroot/webroot_test.go b/providers/http/webroot/webroot_test.go index 124b324a3..4c55e2b90 100644 --- a/providers/http/webroot/webroot_test.go +++ b/providers/http/webroot/webroot_test.go @@ -29,6 +29,7 @@ func TestHTTPProvider(t *testing.T) { } var data []byte + data, err = os.ReadFile(challengeFilePath) require.NoError(t, err) diff --git a/registration/registar.go b/registration/registar.go index 15c28bad6..5d3ea250b 100644 --- a/registration/registar.go +++ b/registration/registar.go @@ -15,7 +15,7 @@ const mailTo = "mailto:" // of which the client needs to keep track itself. // WARNING: will be removed in the future (acme.ExtendedAccount), https://github.com/go-acme/lego/issues/855. type Resource struct { - Body acme.Account `json:"body,omitempty"` + Body acme.Account `json:"body"` URI string `json:"uri,omitempty"` } @@ -160,6 +160,7 @@ func (r *Registrar) ResolveAccountByKey() (*Resource, error) { log.Infof("acme: Trying to resolve account by key") accMsg := acme.Account{OnlyReturnExisting: true} + account, err := r.core.Accounts.New(accMsg) if err != nil { return nil, err From 31772ec503330d940093b11dc7efcc2d8141a752 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 30 Oct 2025 14:18:27 +0100 Subject: [PATCH 200/298] chore: update dependencies (#2695) --- go.mod | 76 ++++----- go.sum | 153 +++++++++--------- providers/dns/desec/desec.go | 4 - .../dns/internal/clientdebug/client_test.go | 16 +- providers/dns/vegadns/vegadns.go | 21 ++- providers/dns/vegadns/vegadns_test.go | 14 +- 6 files changed, 150 insertions(+), 134 deletions(-) diff --git a/go.mod b/go.mod index 2337d068e..3dc72c1ff 100644 --- a/go.mod +++ b/go.mod @@ -18,21 +18,21 @@ require ( github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 github.com/alibabacloud-go/tea v1.3.13 github.com/aliyun/credentials-go v1.4.7 - github.com/aws/aws-sdk-go-v2 v1.39.2 - github.com/aws/aws-sdk-go-v2/config v1.31.12 - github.com/aws/aws-sdk-go-v2/credentials v1.18.16 - github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.0 - github.com/aws/aws-sdk-go-v2/service/route53 v1.58.4 - github.com/aws/aws-sdk-go-v2/service/s3 v1.88.4 - github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 + github.com/aws/aws-sdk-go-v2 v1.39.4 + github.com/aws/aws-sdk-go-v2/config v1.31.15 + github.com/aws/aws-sdk-go-v2/credentials v1.18.19 + github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.2 + github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 github.com/aziontech/azionapi-go-sdk v0.143.0 - github.com/baidubce/bce-sdk-go v0.9.248 + github.com/baidubce/bce-sdk-go v0.9.250 github.com/cenkalti/backoff/v5 v5.0.3 github.com/dnsimple/dnsimple-go/v4 v4.0.0 github.com/exoscale/egoscale/v3 v3.1.27 github.com/go-acme/alidns-20150109/v4 v4.6.1 github.com/go-acme/tencentclouddnspod v1.1.10 - github.com/go-acme/tencentedgdeone v1.1.19 + github.com/go-acme/tencentedgdeone v1.1.48 github.com/go-jose/go-jose/v4 v4.1.3 github.com/go-viper/mapstructure/v2 v2.4.0 github.com/google/go-cmp v0.7.0 @@ -41,7 +41,7 @@ require ( github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/go-version v1.7.0 - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.172 + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.173 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 github.com/labbsr0x/bindman-dns-webhook v1.0.2 @@ -54,7 +54,7 @@ require ( github.com/namedotcom/go/v4 v4.0.2 github.com/nrdcg/auroradns v1.1.0 github.com/nrdcg/bunny-go v0.1.0 - github.com/nrdcg/desec v0.11.0 + github.com/nrdcg/desec v0.11.1 github.com/nrdcg/dnspod-go v0.4.0 github.com/nrdcg/freemyip v0.3.0 github.com/nrdcg/goacmedns v0.2.0 @@ -62,39 +62,39 @@ require ( github.com/nrdcg/mailinabox v0.3.0 github.com/nrdcg/namesilo v0.5.0 github.com/nrdcg/nodion v0.1.0 - github.com/nrdcg/oci-go-sdk/common/v1065 v1065.102.0 - github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.102.0 + github.com/nrdcg/oci-go-sdk/common/v1065 v1065.103.0 + github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.103.0 github.com/nrdcg/porkbun v0.4.0 - github.com/nrdcg/vegadns v0.2.0 + github.com/nrdcg/vegadns v0.3.0 github.com/nzdjb/go-metaname v1.0.0 github.com/ovh/go-ovh v1.9.0 github.com/pquerna/otp v1.5.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.3.3 - github.com/sacloud/iaas-api-go v1.19.0 + github.com/sacloud/iaas-api-go v1.20.0 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35 github.com/selectel/domains-go v1.1.0 github.com/selectel/go-selvpcclient/v4 v4.1.0 github.com/softlayer/softlayer-go v1.2.1 github.com/stretchr/testify v1.11.1 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.41 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48 github.com/transip/gotransip/v6 v6.26.1 github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 github.com/urfave/cli/v2 v2.27.7 github.com/vinyldns/go-vinyldns v0.9.16 - github.com/volcengine/volc-sdk-golang v1.0.223 + github.com/volcengine/volc-sdk-golang v1.0.224 github.com/vultr/govultr/v3 v3.24.0 - github.com/yandex-cloud/go-genproto v0.31.0 - github.com/yandex-cloud/go-sdk/services/dns v0.0.12 - github.com/yandex-cloud/go-sdk/v2 v2.19.0 + github.com/yandex-cloud/go-genproto v0.34.0 + github.com/yandex-cloud/go-sdk/services/dns v0.0.16 + github.com/yandex-cloud/go-sdk/v2 v2.24.0 golang.org/x/crypto v0.43.0 golang.org/x/net v0.46.0 golang.org/x/oauth2 v0.32.0 golang.org/x/text v0.30.0 golang.org/x/time v0.14.0 - google.golang.org/api v0.252.0 - gopkg.in/ns1/ns1-go.v2 v2.15.0 + google.golang.org/api v0.254.0 + gopkg.in/ns1/ns1-go.v2 v2.15.1 gopkg.in/yaml.v2 v2.4.0 software.sslmate.com/src/go-pkcs12 v0.6.0 ) @@ -115,19 +115,19 @@ require ( github.com/alibabacloud-go/debug v1.0.1 // indirect github.com/alibabacloud-go/openapi-util v0.1.1 // indirect github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 // indirect - github.com/aws/smithy-go v1.23.0 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 // indirect + github.com/aws/smithy-go v1.23.1 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -150,7 +150,7 @@ require ( github.com/go-playground/validator/v10 v10.23.0 // indirect github.com/go-resty/resty/v2 v2.16.5 // indirect github.com/goccy/go-yaml v1.9.8 // indirect - github.com/gofrs/flock v0.12.1 // indirect + github.com/gofrs/flock v0.13.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -215,8 +215,8 @@ require ( golang.org/x/tools v0.37.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // indirect - google.golang.org/grpc v1.75.1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect + google.golang.org/grpc v1.76.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 382959ff4..a8d61029d 100644 --- a/go.sum +++ b/go.sum @@ -170,52 +170,52 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I= -github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00= -github.com/aws/aws-sdk-go-v2/config v1.31.12 h1:pYM1Qgy0dKZLHX2cXslNacbcEFMkDMl+Bcj5ROuS6p8= -github.com/aws/aws-sdk-go-v2/config v1.31.12/go.mod h1:/MM0dyD7KSDPR+39p9ZNVKaHDLb9qnfDurvVS2KAhN8= -github.com/aws/aws-sdk-go-v2/credentials v1.18.16 h1:4JHirI4zp958zC026Sm+V4pSDwW4pwLefKrc0bF2lwI= -github.com/aws/aws-sdk-go-v2/credentials v1.18.16/go.mod h1:qQMtGx9OSw7ty1yLclzLxXCRbrkjWAM7JnObZjmCB7I= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 h1:Mv4Bc0mWmv6oDuSWTKnk+wgeqPL5DRFu5bQL9BGPQ8Y= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9/go.mod h1:IKlKfRppK2a1y0gy1yH6zD+yX5uplJ6UuPlgd48dJiQ= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 h1:se2vOWGD3dWQUtfn4wEjRQJb1HK1XsNIt825gskZ970= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9/go.mod h1:hijCGH2VfbZQxqCDN7bwz/4dzxV+hkyhjawAtdPWKZA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 h1:6RBnKZLkJM4hQ+kN6E7yWFveOTg8NLPHAkqrs4ZPlTU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9/go.mod h1:V9rQKRmK7AWuEsOMnHzKj8WyrIir1yUJbZxDuZLFvXI= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9 h1:w9LnHqTq8MEdlnyhV4Bwfizd65lfNCNgdlNC6mM5paE= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.9/go.mod h1:LGEP6EK4nj+bwWNdrvX/FnDTFowdBNwcSPuZu/ouFys= +github.com/aws/aws-sdk-go-v2 v1.39.4 h1:qTsQKcdQPHnfGYBBs+Btl8QwxJeoWcOcPcixK90mRhg= +github.com/aws/aws-sdk-go-v2 v1.39.4/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 h1:t9yYsydLYNBk9cJ73rgPhPWqOh/52fcWDQB5b1JsKSY= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2/go.mod h1:IusfVNTmiSN3t4rhxWFaBAqn+mcNdwKtPcV16eYdgko= +github.com/aws/aws-sdk-go-v2/config v1.31.15 h1:gE3M4xuNXfC/9bG4hyowGm/35uQTi7bUKeYs5e/6uvU= +github.com/aws/aws-sdk-go-v2/config v1.31.15/go.mod h1:HvnvGJoE2I95KAIW8kkWVPJ4XhdrlvwJpV6pEzFQa8o= +github.com/aws/aws-sdk-go-v2/credentials v1.18.19 h1:Jc1zzwkSY1QbkEcLujwqRTXOdvW8ppND3jRBb/VhBQc= +github.com/aws/aws-sdk-go-v2/credentials v1.18.19/go.mod h1:DIfQ9fAk5H0pGtnqfqkbSIzky82qYnGvh06ASQXXg6A= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 h1:X7X4YKb+c0rkI6d4uJ5tEMxXgCZ+jZ/D6mvkno8c8Uw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11/go.mod h1:EqM6vPZQsZHYvC4Cai35UDg/f5NCEU+vp0WfbVqVcZc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 h1:7AANQZkF3ihM8fbdftpjhken0TP9sBzFbV/Ze/Y4HXA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11/go.mod h1:NTF4QCGkm6fzVwncpkFQqoquQyOolcyXfbpC98urj+c= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 h1:ShdtWUZT37LCAA4Mw2kJAJtzaszfSHFb5n25sdcv4YE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11/go.mod h1:7bUb2sSr2MZ3M/N+VyETLTQtInemHXb/Fl3s8CLzm0Y= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11 h1:bKgSxk1TW//00PGQqYmrq83c+2myGidEclp+t9pPqVI= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11/go.mod h1:vrPYCQ6rFHL8jzQA8ppu3gWX18zxjLIDGTeqDxkBmSI= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.0 h1:X0FveUndcZ3lKbSpIC6rMYGRiQTcUVRNH6X4yYtIrlU= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.0/go.mod h1:IWjQYlqw4EX9jw2g3qnEPPWvCE6bS8fKzhMed1OK7c8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 h1:5r34CgVOD4WZudeEKZ9/iKpiT6cM1JyEROpXjOcdWv8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9/go.mod h1:dB12CEbNWPbzO2uC6QSWHteqOg4JfBVJOojbAoAUb5I= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9 h1:wuZ5uW2uhJR63zwNlqWH2W4aL4ZjeJP3o92/W+odDY4= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9/go.mod h1:/G58M2fGszCrOzvJUkDdY8O9kycodunH4VdT5oBAqls= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.0 h1:JOLRYFWMMKUABCp94HHfo0JBVQDVTLXOvWWphjpBBiQ= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.0/go.mod h1:WEOSRNyfIfvgrD9MuSIGrogKyuFahaVMziVq1pHI0NQ= -github.com/aws/aws-sdk-go-v2/service/route53 v1.58.4 h1:KycXrohD5OxAZ5h02YechO2gevvoHfAPAaJM5l8zqb0= -github.com/aws/aws-sdk-go-v2/service/route53 v1.58.4/go.mod h1:xNLZLn4SusktBQ5moqUOgiDKGz3a7vHwF4W0KD+WBPc= -github.com/aws/aws-sdk-go-v2/service/s3 v1.88.4 h1:mUI3b885qJgfqKDUSj6RgbRqLdX0wGmg8ruM03zNfQA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.88.4/go.mod h1:6v8ukAxc7z4x4oBjGUsLnH7KGLY9Uhcgij19UJNkiMg= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 h1:A1oRkiSQOWstGh61y4Wc/yQ04sqrQZr1Si/oAXj20/s= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.6/go.mod h1:5PfYspyCU5Vw1wNPsxi15LZovOnULudOQuVxphSflQA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 h1:5fm5RTONng73/QA73LhCNR7UT9RpFH3hR6HWL6bIgVY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1/go.mod h1:xBEjWD13h+6nq+z4AkqSfSvqRKFgDIQeaMguAJndOWo= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 h1:p3jIvqYwUZgu/XYeI48bJxOhvm47hZb5HUQ0tn6Q9kA= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.6/go.mod h1:WtKK+ppze5yKPkZ0XwqIVWD4beCwv056ZbPQNoeHqM8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 h1:xtuxji5CS0JknaXoACOunXOYOQzgfTvGAc9s2QdCJA4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2/go.mod h1:zxwi0DIR0rcRcgdbl7E2MSOvxDyyXGBlScvBkARFaLQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.2 h1:DGFpGybmutVsCuF6vSuLZ25Vh55E3VmsnJmFfjeBx4M= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.2/go.mod h1:hm/wU1HDvXCFEDzOLorQnZZ/CVvPXvWEmHMSmqgQRuA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 h1:GpMf3z2KJa4RnJ0ew3Hac+hRFYLZ9DDjfgXjuW+pB54= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11/go.mod h1:6MZP3ZI4QQsgUCFTwMZA2V0sEriNQ8k2hmoHF3qjimQ= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11 h1:weapBOuuFIBEQ9OX/NVW3tFQCvSutyjZYk/ga5jDLPo= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11/go.mod h1:3C1gN4FmIVLwYSh8etngUS+f1viY6nLCDVtZmrFbDy0= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.2 h1:pr1dQ9vamhAf2mYOgiRRC/w9Ht4POFhy6+xXw7hOqwY= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.2/go.mod h1:A4Ch93K7Wam4Qe0Wl0XbPgcgoL5KIJtFIe7wHw6OPWE= +github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1 h1:KuoA/cmy/yK8n9v/d6WH36cZwGxFOrn0TmZ4lNN3MKQ= +github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1/go.mod h1:BymbICXBfXQHO6i+yTBhocA9a6DM0uMDQqYelqa9wzs= +github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0 h1:JbCUlVDEjmhpvpIgXP9QN+/jW61WWWj99cGmxMC49hM= +github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0/go.mod h1:UHKgcRSx8PVtvsc1Poxb/Co3PD3wL7P+f49P0+cWtuY= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 h1:M5nimZmugcZUO9wG7iVtROxPhiqyZX6ejS1lxlDPbTU= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.8/go.mod h1:mbef/pgKhtKRwrigPPs7SSSKZgytzP8PQ6P6JAAdqyM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 h1:S5GuJZpYxE0lKeMHKn+BRTz6PTFpgThyJ+5mYfux7BM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3/go.mod h1:X4OF+BTd7HIb3L+tc4UlWHVrpgwZZIVENU15pRDVTI0= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 h1:Ekml5vGg6sHSZLZJQJagefnVe6PmqC2oiRkBq4F7fU0= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.9/go.mod h1:/e15V+o1zFHWdH3u7lpI3rVBcxszktIKuHKCY2/py+k= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= -github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= +github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/aziontech/azionapi-go-sdk v0.143.0 h1:4eEBlYT10prgeCVTNR9FIc7f59Crbl2zrH1a4D1BUqU= github.com/aziontech/azionapi-go-sdk v0.143.0/go.mod h1:cA5DY/VP4X5Eu11LpQNzNn83ziKjja7QVMIl4J45feA= -github.com/baidubce/bce-sdk-go v0.9.248 h1:vB5OMuEC2xnO197M6OWUi24C8mYOZHKXT/8HuKQJUhU= -github.com/baidubce/bce-sdk-go v0.9.248/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/baidubce/bce-sdk-go v0.9.250 h1:fnvV5clsNCAP6pCauj0eNaUnoLVmjQGnco7rcMqp984= +github.com/baidubce/bce-sdk-go v0.9.250/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -316,8 +316,8 @@ github.com/go-acme/alidns-20150109/v4 v4.6.1 h1:Dch3aWRcw4U62+jKPjPQN3iW3TPvgIyw github.com/go-acme/alidns-20150109/v4 v4.6.1/go.mod h1:RBcqBA5IvUWtlpjx6dC6EkPVyBNLQ+mR18XoaP38BFY= github.com/go-acme/tencentclouddnspod v1.1.10 h1:ERVJ4mc3cT4Nb3+n6H/c1AwZnChGBqLoymE0NVYscKI= github.com/go-acme/tencentclouddnspod v1.1.10/go.mod h1:Bo/0YQJ/99FM+44HmCQkByuptX1tJsJ9V14MGV/2Qco= -github.com/go-acme/tencentedgdeone v1.1.19 h1:1jdEpMITrDuXHnu7QLOy2hpLW0BlDof70/KuRT+EiTo= -github.com/go-acme/tencentedgdeone v1.1.19/go.mod h1:gpu7HvXfcKWBrq5HAEBowZNkdk7JFPkagRzC/infUY0= +github.com/go-acme/tencentedgdeone v1.1.48 h1:WLyLBsRVhSLFmtbEFXk0naLODSQn7X6J0Fc/qR8xVUk= +github.com/go-acme/tencentedgdeone v1.1.48/go.mod h1:mu6tA+bPhlSd+CKUfzRikE0mfxmTlBI6dVTn9LY9dRI= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -369,8 +369,8 @@ github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVr github.com/goccy/go-yaml v1.9.8 h1:5gMyLUeU1/6zl+WFfR1hN7D2kf+1/eRGa7DFtToiBvQ= github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= -github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= +github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -529,8 +529,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.172 h1:68VUVbLKwBxPh8tjCXwnLO/d8/thdZ+ExpxdFMEdK5A= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.172/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.173 h1:Y4ixGadyrK9xHw6Z+cyiiME3SBXepEcUoiT+B8C5FoQ= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.173/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -687,8 +687,8 @@ github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk= github.com/nrdcg/bunny-go v0.1.0 h1:GAHTRpHaG/TxfLZlqoJ8OJFzw8rI74+jOTkzxWh0uHA= github.com/nrdcg/bunny-go v0.1.0/go.mod h1:u+C9dgsspgtWVaAz6QkyV17s9fxD8viwwKoxb9XMz1A= -github.com/nrdcg/desec v0.11.0 h1:XZVHy07sg12y8FozMp+l7XvzPsdzog0AYXuQMaHBsfs= -github.com/nrdcg/desec v0.11.0/go.mod h1:5+4vyhMRTs49V9CNoODF/HwT8Mwxv9DJ6j+7NekUnBs= +github.com/nrdcg/desec v0.11.1 h1:ilpKmCr4gGsLcyq3RHfHNmlRzm9fzT2XbWxoVaUCS0s= +github.com/nrdcg/desec v0.11.1/go.mod h1:2LuxHlOcwML/7cntu0eimONmA1U+ZxFDAonoSXr4igQ= github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U= github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc= @@ -703,14 +703,14 @@ github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE= github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.102.0 h1:W28ZizQSS2aRWkFA3iAP9eiZS4OLFaiv35nXtq2lW/s= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.102.0/go.mod h1:cVbzGjRhtXgrduaQbR1GR1x+VDU60NcXPMZ3+eQuiiY= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.102.0 h1:gAOs1dkE7LFoWflzqrDqAhOprc0kF1a0fyV8C4HUPj4= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.102.0/go.mod h1:EUBSYwop1K40VpcKy1haIK6kFK/gPT1atEk89OkY0Kg= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.103.0 h1:GPwwX9GFIBjV4u1M3Cr8eKCP6drW01IsfQSDIz6SUk8= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.103.0/go.mod h1:SfDIKzNQ5AGNMMOA3LGqSPnn63F6Gc4E4bsKArqymvg= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.103.0 h1:MjHla6lf1jpjGXORLpzMeo/tSmx0ejmjMjdjTByaDGY= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.103.0/go.mod h1:o1/kMADX0SlB4hJjWtcs3M6VIUOGR78yhPyiBv6oBkk= github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= -github.com/nrdcg/vegadns v0.2.0 h1:tLs8WDuWORJP+YlDOKf+eQCKtIwXeUW17R5Ls5NcyB8= -github.com/nrdcg/vegadns v0.2.0/go.mod h1:NqSyRKZuJlAsv8VI/7rSubfPXN68NwaJ0aG9KxQVFVo= +github.com/nrdcg/vegadns v0.3.0 h1:11FQMw7xVIRUWO9o5+Z/5YZhmPWlm4oxUUH3F6EVqQU= +github.com/nrdcg/vegadns v0.3.0/go.mod h1:NqSyRKZuJlAsv8VI/7rSubfPXN68NwaJ0aG9KxQVFVo= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -814,8 +814,8 @@ github.com/sacloud/api-client-go v0.3.3 h1:ZpSAyGpITA8UFO3Hq4qMHZLGuNI1FgxAxo4sq github.com/sacloud/api-client-go v0.3.3/go.mod h1:0p3ukcWYXRCc2AUWTl1aA+3sXLvurvvDqhRaLZRLBwo= github.com/sacloud/go-http v0.1.9 h1:Xa5PY8/pb7XWhwG9nAeXSrYXPbtfBWqawgzxD5co3VE= github.com/sacloud/go-http v0.1.9/go.mod h1:DpDG+MSyxYaBwPJ7l3aKLMzwYdTVtC5Bo63HActcgoE= -github.com/sacloud/iaas-api-go v1.19.0 h1:Bw4uygqukcvlblhWrITp94nikXqy2fnKoAC6929LkIA= -github.com/sacloud/iaas-api-go v1.19.0/go.mod h1:XV995RM1I7k5AHb7UZrCVyDF/8bZXDxa+uk1EXoj/Zs= +github.com/sacloud/iaas-api-go v1.20.0 h1:L4TfAzoFSwxrD3QXX8UxJa2o+GZrP9b863K+voTy3tQ= +github.com/sacloud/iaas-api-go v1.20.0/go.mod h1:XV995RM1I7k5AHb7UZrCVyDF/8bZXDxa+uk1EXoj/Zs= github.com/sacloud/packages-go v0.0.11 h1:hrRWLmfPM9w7GBs6xb5/ue6pEMl8t1UuDKyR/KfteHo= github.com/sacloud/packages-go v0.0.11/go.mod h1:XNF5MCTWcHo9NiqWnYctVbASSSZR3ZOmmQORIzcurJ8= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= @@ -902,9 +902,8 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.10/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.19/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.41 h1:XQDGrLX6v4McMP+2myhgQcy5JaPqSgwpLM1qa7ngUII= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.41/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48 h1:aoRUrz2ag27jQWcOKHgeE+toSti6/xPqHKMLruOtJuM= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= @@ -919,8 +918,8 @@ github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= -github.com/volcengine/volc-sdk-golang v1.0.223 h1:1EEK6VOUaA2Tu0VBD4VC5iSTTFag+KuNo+Vix469Tz4= -github.com/volcengine/volc-sdk-golang v1.0.223/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= +github.com/volcengine/volc-sdk-golang v1.0.224 h1:k9Vtg64tQAgFTOGWzhyL0b0axuTuExXbLNVlslWlBZI= +github.com/volcengine/volc-sdk-golang v1.0.224/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= github.com/vultr/govultr/v3 v3.24.0 h1:fTTTj0VBve+Miy+wGhlb90M2NMDfpGFi6Frlj3HVy6M= github.com/vultr/govultr/v3 v3.24.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -931,12 +930,12 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yandex-cloud/go-genproto v0.31.0 h1:mFMS5SD1Lt8qErwefR8ChK3d0jg0tvbDLq57IqenpTg= -github.com/yandex-cloud/go-genproto v0.31.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= -github.com/yandex-cloud/go-sdk/services/dns v0.0.12 h1:c5TNaX7r3DqY37YJFbr7HyQFRcSe1WzCbR81LVwxXyk= -github.com/yandex-cloud/go-sdk/services/dns v0.0.12/go.mod h1:6CRtIkxq6iTSZIOT42EFns54CEr35ncECy4ix9lXUd4= -github.com/yandex-cloud/go-sdk/v2 v2.19.0 h1:Cuzjn6kkOD/KrBF/QyDbKS7b5GAu8fC2ZUjdBjit60A= -github.com/yandex-cloud/go-sdk/v2 v2.19.0/go.mod h1:4SwghU8RB4v2OQzhESgq5SF8XmCXIP80WhgrrNpetJ8= +github.com/yandex-cloud/go-genproto v0.34.0 h1:qhTJpPxOTKQbV44rIqoZSdzxDtZW27fkFjAcipEy8Zs= +github.com/yandex-cloud/go-genproto v0.34.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-sdk/services/dns v0.0.16 h1:0UYrBlQjTO2ct5xcSx6rqkQB95wRBPMVwxfqLQD1sUE= +github.com/yandex-cloud/go-sdk/services/dns v0.0.16/go.mod h1:HlS3aIAdYEmJu2Ska/nzpcuv9LLVSMMXKGhzyLQwf5s= +github.com/yandex-cloud/go-sdk/v2 v2.24.0 h1:G53N/RB5g/jw2xNN0egspnwd2ByHA1OVH6wbTx/tIlo= +github.com/yandex-cloud/go-sdk/v2 v2.24.0/go.mod h1:ZRdpyOig8c/W3bNhwvkeXWWPeDScd9nmXv4AJzKvOsk= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= @@ -1379,8 +1378,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.252.0 h1:xfKJeAJaMwb8OC9fesr369rjciQ704AjU/psjkKURSI= -google.golang.org/api v0.252.0/go.mod h1:dnHOv81x5RAmumZ7BWLShB/u7JZNeyalImxHmtTHxqw= +google.golang.org/api v0.254.0 h1:jl3XrGj7lRjnlUvZAbAdhINTLbsg5dbjmR90+pTQvt4= +google.golang.org/api v0.254.0/go.mod h1:5BkSURm3D9kAqjGvBNgf0EcbX6Rnrf6UArKkwBzAyqQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1423,8 +1422,8 @@ google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuO google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 h1:CirRxTOwnRWVLKzDNrs0CXAaVozJoR4G9xvdRecrdpk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1442,8 +1441,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= -google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1476,8 +1475,8 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/ns1/ns1-go.v2 v2.15.0 h1:cE3xSMdSCV8kf9SQldzqgW/Ueh7sv3yO2JwKtYxxz3E= -gopkg.in/ns1/ns1-go.v2 v2.15.0/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= +gopkg.in/ns1/ns1-go.v2 v2.15.1 h1:8rri2TzAPYcVbBGXn48+dz1Xg30PzHfZ4k8A9JOS0Z0= +gopkg.in/ns1/ns1-go.v2 v2.15.1/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/providers/dns/desec/desec.go b/providers/dns/desec/desec.go index 75618b943..9cc54f65e 100644 --- a/providers/dns/desec/desec.go +++ b/providers/dns/desec/desec.go @@ -88,10 +88,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { opts := desec.NewDefaultClientOptions() if config.HTTPClient != nil { opts.HTTPClient = config.HTTPClient - } else { - // Because the desec.NewDefaultClientOptions uses the http.DefaultClient. - // TODO(ldez): change the desec lib. - opts.HTTPClient = &http.Client{Timeout: 30 * time.Second} } opts.HTTPClient = clientdebug.Wrap(opts.HTTPClient) diff --git a/providers/dns/internal/clientdebug/client_test.go b/providers/dns/internal/clientdebug/client_test.go index e467cf8f9..3a0c4021a 100644 --- a/providers/dns/internal/clientdebug/client_test.go +++ b/providers/dns/internal/clientdebug/client_test.go @@ -32,12 +32,14 @@ func TestWrap_redact_env_vars(t *testing.T) { WithEnvKeys("MY_VAR_01", "MY_VAR_02", "MY_VAR_03", "MY_VAR_04", "MY_VAR_05", "MY_VAR_06"), ) + now := time.Now() + resp, err := client.Transport.RoundTrip(req) require.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) - assertDump(t, server, buf, "env_vars.txt") + assertDump(t, now, server, buf, "env_vars.txt") } func TestWrap_redact_headers(t *testing.T) { @@ -49,12 +51,14 @@ func TestWrap_redact_headers(t *testing.T) { WithHeaders("Secret-Request-Header", "Super-Secret-Request-Header", "Secret-Response-Header"), ) + now := time.Now() + resp, err := client.Transport.RoundTrip(req) require.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) - assertDump(t, server, buf, "headers.txt") + assertDump(t, now, server, buf, "headers.txt") } func TestWrap_redact_values(t *testing.T) { @@ -66,12 +70,14 @@ func TestWrap_redact_values(t *testing.T) { WithValues("query-aaaa-aaaa", "path-aaaa-aaaa", "request-body-aaaa-aaaa"), ) + now := time.Now() + resp, err := client.Transport.RoundTrip(req) require.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) - assertDump(t, server, buf, "values.txt") + assertDump(t, now, server, buf, "values.txt") } func fakeRequest(t *testing.T, baseURL string) *http.Request { @@ -144,7 +150,7 @@ func setupTest(t *testing.T, buf io.Writer, opts ...Option) (*httptest.Server, * return server, client, req } -func assertDump(t *testing.T, server *httptest.Server, actual *bytes.Buffer, filename string) { +func assertDump(t *testing.T, now time.Time, server *httptest.Server, actual *bytes.Buffer, filename string) { t.Helper() tmpl, err := template.New(filename).ParseFiles(filepath.Join("testdata", filename)) @@ -160,7 +166,7 @@ func assertDump(t *testing.T, server *httptest.Server, actual *bytes.Buffer, fil err = tmpl.Execute(expected, map[string]string{ "Host": baseURL.Host, - "Date": time.Now().In(location).Format(time.RFC1123), + "Date": now.In(location).Format(time.RFC1123), }) require.NoError(t, err) diff --git a/providers/dns/vegadns/vegadns.go b/providers/dns/vegadns/vegadns.go index 6375ecc26..9f1f189c3 100644 --- a/providers/dns/vegadns/vegadns.go +++ b/providers/dns/vegadns/vegadns.go @@ -137,14 +137,14 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("vegadns: find domain ID for %s: %w", info.EffectiveFQDN, err) } - recordID, err := d.client.GetRecordID(ctx, domainID, dns01.UnFqdn(info.EffectiveFQDN), "TXT") + recordID, err := d.findRecordID(ctx, domainID, dns01.UnFqdn(info.EffectiveFQDN)) if err != nil { - return fmt.Errorf("vegadns: get Record ID: %w", err) + return fmt.Errorf("vegadns: find record ID for %d: %w", domainID, err) } err = d.client.DeleteRecord(ctx, recordID) if err != nil { - return fmt.Errorf("vegadns: %w", err) + return fmt.Errorf("vegadns: delete record: %w", err) } return nil @@ -162,3 +162,18 @@ func (d *DNSProvider) findDomainID(ctx context.Context, fqdn string) (int, error return 0, errors.New("domain not found") } + +func (d *DNSProvider) findRecordID(ctx context.Context, domainID int, name string) (int, error) { + records, err := d.client.GetRecords(ctx, domainID) + if err != nil { + return 0, fmt.Errorf("get records: %w", err) + } + + for _, r := range records { + if r.Name == name && r.RecordType == "TXT" { + return r.RecordID, nil + } + } + + return 0, errors.New("record not found") +} diff --git a/providers/dns/vegadns/vegadns_test.go b/providers/dns/vegadns/vegadns_test.go index 988326670..edcd2c60d 100644 --- a/providers/dns/vegadns/vegadns_test.go +++ b/providers/dns/vegadns/vegadns_test.go @@ -46,7 +46,7 @@ func TestDNSProvider_Present(t *testing.T) { expectedError string }{ { - desc: "Success", + desc: "success", builder: mockBuilder(). Route("POST /1.0/token", servermock.ResponseFromFixture("token.json")). @@ -56,7 +56,7 @@ func TestDNSProvider_Present(t *testing.T) { WithStatusCode(http.StatusCreated)), }, { - desc: "FailToFindZone", + desc: "fail to find the zone", builder: mockBuilder(). Route("POST /1.0/token", servermock.ResponseFromFixture("token.json")). @@ -66,7 +66,7 @@ func TestDNSProvider_Present(t *testing.T) { expectedError: "vegadns: find domain ID for _acme-challenge.example.com.: domain not found", }, { - desc: "FailToCreateTXT", + desc: "fail to create TXT record", builder: mockBuilder(). Route("POST /1.0/token", servermock.ResponseFromFixture("token.json")). @@ -103,7 +103,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { expectedError string }{ { - desc: "Success", + desc: "success", builder: mockBuilder(). Route("POST /1.0/token", servermock.ResponseFromFixture("token.json")). @@ -115,7 +115,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { servermock.ResponseFromFixture("record_delete.json")), }, { - desc: "FailToFindZone", + desc: "fail to find the zone", builder: mockBuilder(). Route("POST /1.0/token", servermock.ResponseFromFixture("token.json")). @@ -125,7 +125,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { expectedError: "vegadns: find domain ID for _acme-challenge.example.com.: domain not found", }, { - desc: "FailToGetRecordID", + desc: "fail to get record ID", builder: mockBuilder(). Route("POST /1.0/token", servermock.ResponseFromFixture("token.json")). @@ -134,7 +134,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { servermock.Noop(). WithStatusCode(http.StatusNotFound), servermock.CheckQueryParameter().With("domain_id", "1")), - expectedError: "vegadns: get Record ID: bad answer from VegaDNS (code: 404, message: )", + expectedError: "vegadns: find record ID for 1: get records: bad answer from VegaDNS (code: 404, message: )", }, } From 7d099f2ad71cf479608d617c7cddb15c8ad010e2 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 30 Oct 2025 21:41:06 +0100 Subject: [PATCH 201/298] Add DNS provider for webnames.ca (#2698) --- README.md | 6 +- cmd/zz_gen_cmd_dnshelp.go | 22 ++ docs/content/dns/zz_gen_webnamesca.md | 69 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/webnamesca/internal/client.go | 162 ++++++++++++++ .../dns/webnamesca/internal/client_test.go | 96 +++++++++ .../internal/fixtures/add_txt_record.json | 34 +++ .../internal/fixtures/delete_txt_record.json | 36 ++++ .../webnamesca/internal/fixtures/error.json | 6 + providers/dns/webnamesca/internal/types.go | 33 +++ providers/dns/webnamesca/webnamesca.go | 134 ++++++++++++ providers/dns/webnamesca/webnamesca.toml | 24 +++ providers/dns/webnamesca/webnamesca_test.go | 199 ++++++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 14 files changed, 822 insertions(+), 4 deletions(-) create mode 100644 docs/content/dns/zz_gen_webnamesca.md create mode 100644 providers/dns/webnamesca/internal/client.go create mode 100644 providers/dns/webnamesca/internal/client_test.go create mode 100644 providers/dns/webnamesca/internal/fixtures/add_txt_record.json create mode 100644 providers/dns/webnamesca/internal/fixtures/delete_txt_record.json create mode 100644 providers/dns/webnamesca/internal/fixtures/error.json create mode 100644 providers/dns/webnamesca/internal/types.go create mode 100644 providers/dns/webnamesca/webnamesca.go create mode 100644 providers/dns/webnamesca/webnamesca.toml create mode 100644 providers/dns/webnamesca/webnamesca_test.go diff --git a/README.md b/README.md index 630c7a14d..7a24db638 100644 --- a/README.md +++ b/README.md @@ -254,20 +254,20 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Vultr Webnames + webnames.ca Websupport WEDOS West.cn/西部数码 - Yandex 360 + Yandex 360 Yandex Cloud Yandex PDD Zone.ee - ZoneEdit + ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index cab22c30a..083b09ace 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -168,6 +168,7 @@ func allDNSCodes() string { "vscale", "vultr", "webnames", + "webnamesca", "websupport", "wedos", "westcn", @@ -3530,6 +3531,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/webnames`) + case "webnamesca": + // generated from: providers/dns/webnamesca/webnamesca.toml + ew.writeln(`Configuration for webnames.ca.`) + ew.writeln(`Code: 'webnamesca'`) + ew.writeln(`Since: 'v4.28.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "WEBNAMESCA_API_KEY": API key`) + ew.writeln(` - "WEBNAMESCA_API_USER": API username`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "WEBNAMESCA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "WEBNAMESCA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "WEBNAMESCA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "WEBNAMESCA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/webnamesca`) + case "websupport": // generated from: providers/dns/websupport/websupport.toml ew.writeln(`Configuration for Websupport.`) diff --git a/docs/content/dns/zz_gen_webnamesca.md b/docs/content/dns/zz_gen_webnamesca.md new file mode 100644 index 000000000..41a33cb82 --- /dev/null +++ b/docs/content/dns/zz_gen_webnamesca.md @@ -0,0 +1,69 @@ +--- +title: "webnames.ca" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: webnamesca +dnsprovider: + since: "v4.28.0" + code: "webnamesca" + url: "https://www.webnames.ca/" +--- + + + + + + +Configuration for [webnames.ca](https://www.webnames.ca/). + + + + +- Code: `webnamesca` +- Since: v4.28.0 + + +Here is an example bash command using the webnames.ca provider: + +```bash +WEBNAMESCA_API_USER="xxx" \ +WEBNAMESCA_API_KEY="yyy" \ +lego --email you@example.com --dns webnamesca -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `WEBNAMESCA_API_KEY` | API key | +| `WEBNAMESCA_API_USER` | API username | + +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 | +|--------------------------------|-------------| +| `WEBNAMESCA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `WEBNAMESCA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `WEBNAMESCA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `WEBNAMESCA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://www.webnames.ca/_/swagger/index.html) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index bab9bd525..80cbcaac0 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/webnamesca/internal/client.go b/providers/dns/webnamesca/internal/client.go new file mode 100644 index 000000000..203ff9eac --- /dev/null +++ b/providers/dns/webnamesca/internal/client.go @@ -0,0 +1,162 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" +) + +const defaultBaseURL = "https://www.webnames.ca/_/APICore" + +// Client the webnames.ca API client. +type Client struct { + user string + key string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(user, key string) (*Client, error) { + if user == "" || key == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + user: user, + key: key, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) AddTXTRecord(ctx context.Context, domainName, hostName, value string) ([]DNSRecordSet, error) { + endpoint := c.BaseURL.JoinPath("domains", domainName, "add-txt-record") + + query := endpoint.Query() + query.Set("hostName", hostName) + query.Set("txt", value) + + endpoint.RawQuery = query.Encode() + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, nil) + if err != nil { + return nil, err + } + + var result APIResponse[*DNSInfo] + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result.Result.DNSRecordSets, nil +} + +func (c *Client) DeleteTXTRecord(ctx context.Context, domainName, hostName, value string) ([]DNSRecordSet, error) { + endpoint := c.BaseURL.JoinPath("domains", domainName, "delete-txt-record") + + query := endpoint.Query() + query.Set("hostName", hostName) + query.Set("txt", value) + + endpoint.RawQuery = query.Encode() + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return nil, err + } + + var result APIResponse[*DNSInfo] + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result.Result.DNSRecordSets, nil +} + +func (c *Client) do(req *http.Request, result any) error { + useragent.SetHeader(req.Header) + + req.Header.Set("API-User", c.user) + req.Header.Set("API-Key", c.key) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/webnamesca/internal/client_test.go b/providers/dns/webnamesca/internal/client_test.go new file mode 100644 index 000000000..ad8571ed0 --- /dev/null +++ b/providers/dns/webnamesca/internal/client_test.go @@ -0,0 +1,96 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("user", "secret") + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + With("API-User", "user"). + With("API-Key", "secret"). + WithJSONHeaders(), + ) +} + +func TestClient_AddTXTRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /domains/example.com/add-txt-record", + servermock.ResponseFromFixture("add_txt_record.json"), + servermock.CheckQueryParameter().Strict(). + With("hostName", "foo.example.com"). + With("txt", "value")). + Build(t) + + result, err := client.AddTXTRecord(t.Context(), "example.com", "foo.example.com", "value") + require.NoError(t, err) + + expected := []DNSRecordSet{{ + Hostname: "_acme-challenge.example.com", + Type: "TXT", + Records: []string{"value"}, + }} + + assert.Equal(t, expected, result) +} + +func TestClient_AddTXTRecord_error(t *testing.T) { + client := mockBuilder(). + Route("POST /domains/example.com/add-txt-record", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) + + _, err := client.AddTXTRecord(t.Context(), "example.com", "foo.example.com", "value") + require.EqualError(t, err, "message: User does not exist., details: string, logiD: 35579, result: {}") +} + +func TestClient_DeleteTXTRecord(t *testing.T) { + client := mockBuilder(). + Route("DELETE /domains/example.com/delete-txt-record", + servermock.ResponseFromFixture("delete_txt_record.json"), + servermock.CheckQueryParameter().Strict(). + With("hostName", "foo.example.com"). + With("txt", "value")). + Build(t) + + result, err := client.DeleteTXTRecord(t.Context(), "example.com", "foo.example.com", "value") + require.NoError(t, err) + + expected := []DNSRecordSet{{ + Hostname: "_acme-challenge.example.com", + Type: "TXT", + Records: []string{"value"}, + }} + + assert.Equal(t, expected, result) +} + +func TestClient_DeleteTXTRecord_error(t *testing.T) { + client := mockBuilder(). + Route("DELETE /domains/example.com/delete-txt-record", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) + + _, err := client.DeleteTXTRecord(t.Context(), "example.com", "foo.example.com", "value") + require.EqualError(t, err, "message: User does not exist., details: string, logiD: 35579, result: {}") +} diff --git a/providers/dns/webnamesca/internal/fixtures/add_txt_record.json b/providers/dns/webnamesca/internal/fixtures/add_txt_record.json new file mode 100644 index 000000000..9754689a7 --- /dev/null +++ b/providers/dns/webnamesca/internal/fixtures/add_txt_record.json @@ -0,0 +1,34 @@ +{ + "result": { + "domainAdvancedDNSConfigID": 3258480, + "domainID": 1333334, + "dtCreated": "2025-10-30T11:55:23.243", + "dtModified": "2025-10-30T11:55:23.177", + "timeToLive": 21600, + "soAorigin": "hosting.webnames.ca", + "soArefresh": 21600, + "soAretry": 180, + "soAexpire": 1209600, + "soAnegcache": 3600, + "forwardingURL": null, + "gripping": false, + "name": null, + "dtSubmitted": "2025-10-30T11:55:24.927", + "dtRequestedDNSChange": null, + "type": "REAL_DOMAIN", + "userManaged": false, + "effectiveMgmtOption": "AD", + "urlForwardRootOnly": false, + "enableDNSSEC": false, + "dnsRecordSets": [ + { + "hostname": "_acme-challenge.example.com", + "type": "TXT", + "records": [ + "value" + ] + } + ] + }, + "logID": 36014 +} diff --git a/providers/dns/webnamesca/internal/fixtures/delete_txt_record.json b/providers/dns/webnamesca/internal/fixtures/delete_txt_record.json new file mode 100644 index 000000000..be2279ef6 --- /dev/null +++ b/providers/dns/webnamesca/internal/fixtures/delete_txt_record.json @@ -0,0 +1,36 @@ +{ + "errorMessage": "string", + "errorDetails": "string", + "logID": 0, + "result": { + "domainAdvancedDNSConfigID": 0, + "domainID": 0, + "dtCreated": "2025-10-29T21:22:31.478", + "dtModified": "2025-10-29T21:22:31.478", + "timeToLive": 0, + "soAorigin": "string", + "soArefresh": 0, + "soAretry": 0, + "soAexpire": 0, + "soAnegcache": 0, + "forwardingURL": "string", + "gripping": true, + "name": "string", + "dtSubmitted": "2025-10-29T21:22:31.478", + "dtRequestedDNSChange": "2025-10-29T21:22:31.478", + "type": "string", + "userManaged": true, + "effectiveMgmtOption": "string", + "urlForwardRootOnly": true, + "enableDNSSEC": true, + "dnsRecordSets": [ + { + "hostname": "_acme-challenge.example.com", + "type": "TXT", + "records": [ + "value" + ] + } + ] + } +} diff --git a/providers/dns/webnamesca/internal/fixtures/error.json b/providers/dns/webnamesca/internal/fixtures/error.json new file mode 100644 index 000000000..3e7548abb --- /dev/null +++ b/providers/dns/webnamesca/internal/fixtures/error.json @@ -0,0 +1,6 @@ +{ + "errorMessage": "User does not exist.", + "errorDetails": "string", + "logID": 35579, + "result": {} +} diff --git a/providers/dns/webnamesca/internal/types.go b/providers/dns/webnamesca/internal/types.go new file mode 100644 index 000000000..8dc56c33a --- /dev/null +++ b/providers/dns/webnamesca/internal/types.go @@ -0,0 +1,33 @@ +package internal + +import ( + "encoding/json" + "fmt" +) + +type APIError struct { + ErrorMessage string `json:"errorMessage,omitempty"` + ErrorDetails string `json:"errorDetails,omitempty"` + LogID int `json:"logID,omitempty"` + Result json.RawMessage `json:"result,omitempty"` +} + +func (a *APIError) Error() string { + return fmt.Sprintf("message: %s, details: %s, logiD: %d, result: %s", a.ErrorMessage, a.ErrorDetails, a.LogID, a.Result) +} + +type APIResponse[T any] struct { + Result T `json:"result,omitempty"` + LogID int `json:"logID,omitempty"` +} + +type DNSInfo struct { + DomainID int `json:"domainID,omitempty"` + DNSRecordSets []DNSRecordSet `json:"dnsRecordSets,omitempty"` +} + +type DNSRecordSet struct { + Hostname string `json:"hostname"` + Type string `json:"type"` + Records []string `json:"records"` +} diff --git a/providers/dns/webnamesca/webnamesca.go b/providers/dns/webnamesca/webnamesca.go new file mode 100644 index 000000000..874c1c48e --- /dev/null +++ b/providers/dns/webnamesca/webnamesca.go @@ -0,0 +1,134 @@ +// Package webnamesca implements a DNS provider for solving the DNS-01 challenge using webnames.ca. +package webnamesca + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/webnamesca/internal" +) + +// Environment variables names. +const ( + envNamespace = "WEBNAMESCA_" + + EnvAPIUser = envNamespace + "API_USER" + 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 { + APIUser string + 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for webnames.ca. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIUser, EnvAPIKey) + if err != nil { + return nil, fmt.Errorf("webnamesca: %w", err) + } + + config := NewDefaultConfig() + config.APIUser = values[EnvAPIUser] + config.APIKey = values[EnvAPIKey] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for webnames.ca. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("webnamesca: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.APIUser, config.APIKey) + if err != nil { + return nil, fmt.Errorf("webnamesca: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("webnamesca: could not find zone for domain %q: %w", domain, err) + } + + _, err = d.client.AddTXTRecord(context.Background(), dns01.UnFqdn(authZone), dns01.UnFqdn(info.EffectiveFQDN), info.Value) + if err != nil { + return fmt.Errorf("webnamesca: add TXT record: %w", err) + } + + 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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("webnamesca: could not find zone for domain %q: %w", domain, err) + } + + _, err = d.client.DeleteTXTRecord(context.Background(), dns01.UnFqdn(authZone), dns01.UnFqdn(info.EffectiveFQDN), info.Value) + if err != nil { + return fmt.Errorf("webnamesca: delete TXT record: %w", err) + } + + 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/webnamesca/webnamesca.toml b/providers/dns/webnamesca/webnamesca.toml new file mode 100644 index 000000000..c7d30751b --- /dev/null +++ b/providers/dns/webnamesca/webnamesca.toml @@ -0,0 +1,24 @@ +Name = "webnames.ca" +Description = '''''' +URL = "https://www.webnames.ca/" +Code = "webnamesca" +Since = "v4.28.0" + +Example = ''' +WEBNAMESCA_API_USER="xxx" \ +WEBNAMESCA_API_KEY="yyy" \ +lego --email you@example.com --dns webnamesca -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + WEBNAMESCA_API_USER = "API username" + WEBNAMESCA_API_KEY = "API key" + [Configuration.Additional] + WEBNAMESCA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + WEBNAMESCA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + WEBNAMESCA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + WEBNAMESCA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://www.webnames.ca/_/swagger/index.html" diff --git a/providers/dns/webnamesca/webnamesca_test.go b/providers/dns/webnamesca/webnamesca_test.go new file mode 100644 index 000000000..0459ef44e --- /dev/null +++ b/providers/dns/webnamesca/webnamesca_test.go @@ -0,0 +1,199 @@ +package webnamesca + +import ( + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvAPIUser, EnvAPIKey).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvAPIUser: "user", + EnvAPIKey: "secret", + }, + }, + { + desc: "missing EnvAPIUser", + envVars: map[string]string{ + EnvAPIUser: "", + EnvAPIKey: "secret", + }, + expected: "webnamesca: some credentials information are missing: WEBNAMESCA_API_USER", + }, + { + desc: "missing EnvAPIKey", + envVars: map[string]string{ + EnvAPIUser: "user", + EnvAPIKey: "", + }, + expected: "webnamesca: some credentials information are missing: WEBNAMESCA_API_KEY", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "webnamesca: some credentials information are missing: WEBNAMESCA_API_USER,WEBNAMESCA_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 + apiUser string + apiKey string + expected string + }{ + { + desc: "success", + apiUser: "user", + apiKey: "secret", + }, + { + desc: "missing apiUser", + apiKey: "secret", + expected: "webnamesca: credentials missing", + }, + { + desc: "missing apiKey", + apiUser: "user", + expected: "webnamesca: credentials missing", + }, + { + desc: "missing credentials", + expected: "webnamesca: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIUser = test.apiUser + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.APIUser = "user" + config.APIKey = "secret" + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With("API-User", "user"). + With("API-Key", "secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("POST /domains/example.com/add-txt-record", + servermock.ResponseFromInternal("add_txt_record.json"), + servermock.CheckQueryParameter().Strict(). + With("hostName", "_acme-challenge.example.com"). + With("txt", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY")). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("DELETE /domains/example.com/delete-txt-record", + servermock.ResponseFromInternal("delete_txt_record.json"), + servermock.CheckQueryParameter().Strict(). + With("hostName", "_acme-challenge.example.com"). + With("txt", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY")). + Build(t) + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 5cb428eb1..76400ebad 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -162,6 +162,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/vscale" "github.com/go-acme/lego/v4/providers/dns/vultr" "github.com/go-acme/lego/v4/providers/dns/webnames" + "github.com/go-acme/lego/v4/providers/dns/webnamesca" "github.com/go-acme/lego/v4/providers/dns/websupport" "github.com/go-acme/lego/v4/providers/dns/wedos" "github.com/go-acme/lego/v4/providers/dns/westcn" @@ -488,6 +489,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return vultr.NewDNSProvider() case "webnames": return webnames.NewDNSProvider() + case "webnamesca": + return webnamesca.NewDNSProvider() case "websupport": return websupport.NewDNSProvider() case "wedos": From 591116b3a48511bc53f84a21a543bbe8b45a563d Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 30 Oct 2025 21:57:17 +0100 Subject: [PATCH 202/298] webnames: rename to webnamesru to avoid ambiguity with webnamesca (#2700) --- README.md | 4 +-- cmd/zz_gen_cmd_dnshelp.go | 10 +++---- docs/content/dns/zz_gen_webnames.md | 18 ++++++------- providers/dns/webnames/webnames.go | 36 ++++++++++++++----------- providers/dns/webnames/webnames.toml | 15 ++++++----- providers/dns/webnames/webnames_test.go | 4 +-- providers/dns/zz_gen_dns_providers.go | 2 +- 7 files changed, 48 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 7a24db638..1a8480b24 100644 --- a/README.md +++ b/README.md @@ -252,9 +252,9 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Volcano Engine/火山引擎 Vscale Vultr - Webnames - webnames.ca + + webnames.ru Websupport WEDOS West.cn/西部数码 diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 083b09ace..e21f37e63 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -3514,19 +3514,19 @@ func displayDNSHelp(w io.Writer, name string) error { case "webnames": // generated from: providers/dns/webnames/webnames.toml - ew.writeln(`Configuration for Webnames.`) + ew.writeln(`Configuration for webnames.ru.`) ew.writeln(`Code: 'webnames'`) ew.writeln(`Since: 'v4.15.0'`) ew.writeln() ew.writeln(`Credentials:`) - ew.writeln(` - "WEBNAMES_API_KEY": Domain API key`) + ew.writeln(` - "WEBNAMESRU_API_KEY": Domain API key`) ew.writeln() ew.writeln(`Additional Configuration:`) - ew.writeln(` - "WEBNAMES_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) - ew.writeln(` - "WEBNAMES_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) - ew.writeln(` - "WEBNAMES_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "WEBNAMESRU_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "WEBNAMESRU_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "WEBNAMESRU_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/webnames`) diff --git a/docs/content/dns/zz_gen_webnames.md b/docs/content/dns/zz_gen_webnames.md index d721466b3..4945775a5 100644 --- a/docs/content/dns/zz_gen_webnames.md +++ b/docs/content/dns/zz_gen_webnames.md @@ -1,5 +1,5 @@ --- -title: "Webnames" +title: "webnames.ru" date: 2019-03-03T16:39:46+01:00 draft: false slug: webnames @@ -14,7 +14,7 @@ dnsprovider: -Configuration for [Webnames](https://www.webnames.ru/). +Configuration for [webnames.ru](https://www.webnames.ru/). @@ -23,11 +23,11 @@ Configuration for [Webnames](https://www.webnames.ru/). - Since: v4.15.0 -Here is an example bash command using the Webnames provider: +Here is an example bash command using the webnames.ru provider: ```bash -WEBNAMES_API_KEY=xxxxxx \ -lego --email you@example.com --dns webnames -d '*.example.com' -d example.com run +WEBNAMESRU_API_KEY=xxxxxx \ +lego --email you@example.com --dns webnamesru -d '*.example.com' -d example.com run ``` @@ -37,7 +37,7 @@ lego --email you@example.com --dns webnames -d '*.example.com' -d example.com ru | Environment Variable Name | Description | |-----------------------|-------------| -| `WEBNAMES_API_KEY` | Domain API key | +| `WEBNAMESRU_API_KEY` | Domain 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" %}}). @@ -47,9 +47,9 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| -| `WEBNAMES_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | -| `WEBNAMES_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | -| `WEBNAMES_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `WEBNAMESRU_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `WEBNAMESRU_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `WEBNAMESRU_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | 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" %}}). diff --git a/providers/dns/webnames/webnames.go b/providers/dns/webnames/webnames.go index 5dc5c4f2d..9c27164e3 100644 --- a/providers/dns/webnames/webnames.go +++ b/providers/dns/webnames/webnames.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "strings" "time" "github.com/go-acme/lego/v4/challenge" @@ -17,7 +18,8 @@ import ( // Environment variables names. const ( - envNamespace = "WEBNAMES_" + envNamespace = "WEBNAMESRU_" + altEnvNamespace = "WEBNAMES_" EnvAPIKey = envNamespace + "API_KEY" @@ -40,10 +42,10 @@ type Config struct { // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + PropagationTimeout: env.GetOneWithFallback(EnvPropagationTimeout, dns01.DefaultPropagationTimeout, env.ParseSecond, altEnvName(EnvPropagationTimeout)), + PollingInterval: env.GetOneWithFallback(EnvPollingInterval, dns01.DefaultPollingInterval, env.ParseSecond, altEnvName(EnvPollingInterval)), HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + Timeout: env.GetOneWithFallback(EnvHTTPTimeout, 20*time.Second, env.ParseSecond, altEnvName(EnvHTTPTimeout)), }, } } @@ -55,11 +57,11 @@ type DNSProvider struct { } // NewDNSProvider returns a new DNS provider using -// environment variable WEBNAMES_API_KEY for adding and removing the DNS record. +// environment variable WEBNAMESRU_API_KEY for adding and removing the DNS record. func NewDNSProvider() (*DNSProvider, error) { - values, err := env.Get(EnvAPIKey) + values, err := env.GetWithFallback([]string{EnvAPIKey, altEnvName(EnvAPIKey)}) if err != nil { - return nil, fmt.Errorf("webnames: %w", err) + return nil, fmt.Errorf("webnamesru: %w", err) } config := NewDefaultConfig() @@ -71,11 +73,11 @@ func NewDNSProvider() (*DNSProvider, error) { // NewDNSProviderConfig return a DNSProvider instance configured for Webnames. func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config == nil { - return nil, errors.New("webnames: the configuration of the DNS provider is nil") + return nil, errors.New("webnamesru: the configuration of the DNS provider is nil") } if config.APIKey == "" { - return nil, errors.New("webnames: credentials missing") + return nil, errors.New("webnamesru: credentials missing") } client := internal.NewClient(config.APIKey) @@ -95,17 +97,17 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) if err != nil { - return fmt.Errorf("webnames: could not find zone for domain %q: %w", domain, err) + return fmt.Errorf("webnamesru: could not find zone for domain %q: %w", domain, err) } subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) if err != nil { - return fmt.Errorf("webnames: %w", err) + return fmt.Errorf("webnamesru: %w", err) } err = d.client.AddTXTRecord(context.Background(), dns01.UnFqdn(authZone), subDomain, info.Value) if err != nil { - return fmt.Errorf("webnames: failed to create TXT records [domain: %s, sub domain: %s]: %w", + return fmt.Errorf("webnamesru: failed to create TXT records [domain: %s, sub domain: %s]: %w", dns01.UnFqdn(authZone), subDomain, err) } @@ -118,17 +120,17 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) if err != nil { - return fmt.Errorf("webnames: could not find zone for domain %q: %w", domain, err) + return fmt.Errorf("webnamesru: could not find zone for domain %q: %w", domain, err) } subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) if err != nil { - return fmt.Errorf("webnames: %w", err) + return fmt.Errorf("webnamesru: %w", err) } err = d.client.RemoveTXTRecord(context.Background(), dns01.UnFqdn(authZone), subDomain, info.Value) if err != nil { - return fmt.Errorf("webnames: failed to remove TXT records [domain: %s, sub domain: %s]: %w", + return fmt.Errorf("webnamesru: failed to remove TXT records [domain: %s, sub domain: %s]: %w", dns01.UnFqdn(authZone), subDomain, err) } @@ -140,3 +142,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } + +func altEnvName(v string) string { + return strings.ReplaceAll(v, envNamespace, altEnvNamespace) +} diff --git a/providers/dns/webnames/webnames.toml b/providers/dns/webnames/webnames.toml index 1962a69eb..04dea25c5 100644 --- a/providers/dns/webnames/webnames.toml +++ b/providers/dns/webnames/webnames.toml @@ -1,12 +1,13 @@ -Name = "Webnames" +Name = "webnames.ru" Description = '''''' URL = "https://www.webnames.ru/" Code = "webnames" +Aliases = ["webnamesru"] Since = "v4.15.0" Example = ''' -WEBNAMES_API_KEY=xxxxxx \ -lego --email you@example.com --dns webnames -d '*.example.com' -d example.com run +WEBNAMESRU_API_KEY=xxxxxx \ +lego --email you@example.com --dns webnamesru -d '*.example.com' -d example.com run ''' Additional = ''' @@ -19,11 +20,11 @@ The API key can be found: Personal account / My domains and services / Select th [Configuration] [Configuration.Credentials] - WEBNAMES_API_KEY = "Domain API key" + WEBNAMESRU_API_KEY = "Domain API key" [Configuration.Additional] - WEBNAMES_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" - WEBNAMES_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - WEBNAMES_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + WEBNAMESRU_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + WEBNAMESRU_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + WEBNAMESRU_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" [Links] API = "https://github.com/regtime-ltd/certbot-dns-webnames" diff --git a/providers/dns/webnames/webnames_test.go b/providers/dns/webnames/webnames_test.go index 553841e32..072591c68 100644 --- a/providers/dns/webnames/webnames_test.go +++ b/providers/dns/webnames/webnames_test.go @@ -29,7 +29,7 @@ func TestNewDNSProvider(t *testing.T) { envVars: map[string]string{ EnvAPIKey: "", }, - expected: "webnames: some credentials information are missing: WEBNAMES_API_KEY", + expected: "webnamesru: some credentials information are missing: WEBNAMESRU_API_KEY", }, } @@ -66,7 +66,7 @@ func TestNewDNSProviderConfig(t *testing.T) { }, { desc: "missing credentials", - expected: "webnames: credentials missing", + expected: "webnamesru: credentials missing", }, } diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 76400ebad..32de816a3 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -487,7 +487,7 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return vscale.NewDNSProvider() case "vultr": return vultr.NewDNSProvider() - case "webnames": + case "webnames", "webnamesru": return webnames.NewDNSProvider() case "webnamesca": return webnamesca.NewDNSProvider() From 102f7067ac462c7d69ba47514ce64d072ef5b40d Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 31 Oct 2025 11:15:56 +0100 Subject: [PATCH 203/298] Prepare release v4.28.0 --- CHANGELOG.md | 21 +++++++++++++++++++ acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b60c38a70..ccfd912d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,27 @@ Everybody thinks that the others will donate, but in the end, nobody does. So if you think that lego is worth it, please consider [donating](https://donate.ldez.dev). +## v4.28.0 + +- Release date: 2025-10-31 +- Tag: [v4.28.0](https://github.com/go-acme/lego/releases/tag/v4.28.0) + +### Added + +- **[dnsprovider]** Add DNS provider for Anexia +- **[dnsprovider]** Add DNS provider for webnames.ca +- **[dnsprovider]** webnames: rename to webnamesru to avoid ambiguity with webnamesca + +### Changed + +- **[dnsprovider,log]** hetzner: add deprecation logs +- **[dnsprovider]** iwantmyname: provider deprecation +- **[cli]** improve retryable HTTP client error handling + +### Fixed + +- **[dnsprovider]** hostinger: fix record update + ## v4.27.0 - Release date: 2025-10-17 diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index f7aee3fa5..11f6edf99 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.27.0" + ourUserAgent = "xenolf-acme/4.28.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index b2ef87912..8eed28947 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.27.0+dev-detach" +const defaultVersion = "v4.28.0+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index b0d6f3437..1fdaef71a 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.27.0" + ourUserAgent = "goacme-lego/4.28.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From e12c9fc637c302f1d4075b25c519852f2e3c453d Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 31 Oct 2025 11:16:12 +0100 Subject: [PATCH 204/298] Detach v4.28.0 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 11f6edf99..903fd7483 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 8eed28947..afee84317 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.28.0+dev-release" +const defaultVersion = "v4.28.0+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 1fdaef71a..2a48e1942 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From 14778cc1f11156a5ab4253252d5ec96e3248cccd Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 6 Nov 2025 13:19:09 +0100 Subject: [PATCH 205/298] fix: skip nil response (#2705) --- cmd/setup.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/setup.go b/cmd/setup.go index 4d17f2e27..319b7680e 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -180,6 +180,10 @@ func checkRetry(ctx context.Context, resp *http.Response, err error) (bool, erro return rt, err } + if resp == nil { + return rt, nil + } + if resp.StatusCode/100 == 2 { return rt, nil } From b704b26e6c49a540cbb5dd51c2f3a9d70053c86c Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 6 Nov 2025 16:53:15 +0100 Subject: [PATCH 206/298] Prepare release v4.28.1 --- .github/workflows/release.yml | 2 ++ CHANGELOG.md | 9 +++++++++ acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a83c85909..ca2e1867e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -81,6 +81,8 @@ jobs: - uses: actions/attest-build-provenance@v3 with: subject-checksums: ./dist/lego_*_checksums.txt + github-token: ${{ secrets.GH_TOKEN_REPO }} - uses: actions/attest-build-provenance@v3 with: subject-checksums: ./dist/digests.txt + github-token: ${{ secrets.GH_TOKEN_REPO }} diff --git a/CHANGELOG.md b/CHANGELOG.md index ccfd912d5..31f8ff569 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ Everybody thinks that the others will donate, but in the end, nobody does. So if you think that lego is worth it, please consider [donating](https://donate.ldez.dev). +## v4.28.1 + +- Release date: 2025-11-06 +- Tag: [v4.28.1](https://github.com/go-acme/lego/releases/tag/v4.28.1) + +### Fixed + +- **[cli]** fix: skip nil response + ## v4.28.0 - Release date: 2025-10-31 diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 903fd7483..2e5f84d82 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.28.0" + ourUserAgent = "xenolf-acme/4.28.1" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index afee84317..1f4a291dd 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.28.0+dev-detach" +const defaultVersion = "v4.28.1+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 2a48e1942..57e2a727a 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.28.0" + ourUserAgent = "goacme-lego/4.28.1" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From 22955739a15861793e814c044e102502d61c5e3e Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 6 Nov 2025 17:05:33 +0100 Subject: [PATCH 207/298] Detach v4.28.1 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 2e5f84d82..bcfbebb2a 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 1f4a291dd..b564906c1 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.28.1+dev-release" +const defaultVersion = "v4.28.1+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 57e2a727a..1e176ab9a 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From d5dc3866e666984c954695f246b8ea3dcd1dfbe4 Mon Sep 17 00:00:00 2001 From: oliverbr <5881331+oliverbr@users.noreply.github.com> Date: Sat, 8 Nov 2025 03:29:10 +0100 Subject: [PATCH 208/298] gandiv5: update base API URL (#2708) --- providers/dns/gandiv5/internal/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/dns/gandiv5/internal/client.go b/providers/dns/gandiv5/internal/client.go index 018a05799..36e0dafb1 100644 --- a/providers/dns/gandiv5/internal/client.go +++ b/providers/dns/gandiv5/internal/client.go @@ -15,7 +15,7 @@ import ( ) // defaultBaseURL endpoint is the Gandi API endpoint used by Present and CleanUp. -const defaultBaseURL = "https://dns.api.gandi.net/api/v5" +const defaultBaseURL = "https://api.gandi.net/v5/livedns" // APIKeyHeader API key header. const APIKeyHeader = "X-Api-Key" From 1c33fba18007f3f63cba61c82d81663210042b8d Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 8 Nov 2025 13:38:05 +0100 Subject: [PATCH 209/298] chore: add major version tag for Docker images (#2709) --- .goreleaser.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 073997209..c358f8a38 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -90,8 +90,9 @@ dockers_v2: - linux/arm/v7 tags: - 'latest' - - '{{ .Tag }}' + - 'v{{ .Major }}' - 'v{{ .Major }}.{{ .Minor }}' + - '{{ .Tag }}' labels: # https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys 'org.opencontainers.image.title': '{{.ProjectName}}' From 877738cef3c705d6bdcfd85daa476fccffa20e24 Mon Sep 17 00:00:00 2001 From: Evgeniy Medvedev Date: Sun, 9 Nov 2025 23:56:16 +0400 Subject: [PATCH 210/298] Add DNS provider for EdgeCenter (#2710) Co-authored-by: Fernandez Ludovic --- README.md | 60 +++---- cmd/zz_gen_cmd_dnshelp.go | 21 +++ docs/content/dns/zz_gen_edgecenter.md | 67 ++++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/edgecenter/edgecenter.go | 160 ++++++++++++++++++ providers/dns/edgecenter/edgecenter.toml | 22 +++ providers/dns/edgecenter/edgecenter_test.go | 116 +++++++++++++ providers/dns/gcore/gcore.go | 30 +--- providers/dns/gcore/gcore_test.go | 28 --- .../internal => internal/gcore}/client.go | 23 +-- .../gcore}/client_test.go | 4 +- .../internal => internal/gcore}/types.go | 2 +- providers/dns/zz_gen_dns_providers.go | 3 + 13 files changed, 444 insertions(+), 94 deletions(-) create mode 100644 docs/content/dns/zz_gen_edgecenter.md create mode 100644 providers/dns/edgecenter/edgecenter.go create mode 100644 providers/dns/edgecenter/edgecenter.toml create mode 100644 providers/dns/edgecenter/edgecenter_test.go rename providers/dns/{gcore/internal => internal/gcore}/client.go (89%) rename providers/dns/{gcore/internal => internal/gcore}/client_test.go (98%) rename providers/dns/{gcore/internal => internal/gcore}/types.go (96%) diff --git a/README.md b/README.md index 1a8480b24..59fb983eb 100644 --- a/README.md +++ b/README.md @@ -122,152 +122,152 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). DynDnsFree.de Dynu EasyDNS - Efficient IP + EdgeCenter + Efficient IP Epik Exoscale External program - F5 XC + F5 XC freemyip.com G-Core Gandi - Gandi Live DNS (v5) + Gandi Live DNS (v5) Glesys Go Daddy Google Cloud - Google Domains + Google Domains Hetzner Hosting.de Hostinger - Hosttech + Hosttech HTTP request http.net Huawei Cloud - Hurricane Electric DNS + Hurricane Electric DNS HyperOne IBM Cloud (SoftLayer) IIJ DNS Platform Service - Infoblox + Infoblox Infomaniak Internet Initiative Japan Internet.bs - INWX + INWX Ionos IPv64 iwantmyname (Deprecated) - Joker + Joker Joohoi's ACME-DNS KeyHelp Liara - Lima-City + Lima-City Linode (v4) Liquid Web Loopia - LuaDNS + LuaDNS Mail-in-a-Box ManageEngine CloudDNS Manual - Metaname + Metaname Metaregistrar mijn.host Mittwald - myaddr.{tools,dev,io} + myaddr.{tools,dev,io} MyDNS.jp MythicBeasts Name.com - Namecheap + Namecheap Namesilo NearlyFreeSpeech.NET Netcup - Netlify + Netlify Nicmanager NIFCloud Njalla - Nodion + Nodion NS1 Octenium Open Telekom Cloud - Oracle Cloud + Oracle Cloud OVH plesk.com Porkbun - PowerDNS + PowerDNS Rackspace Rain Yun/雨云 RcodeZero - reg.ru + reg.ru Regfish RFC2136 RimuHosting - RU CENTER + RU CENTER Sakura Cloud Scaleway Selectel - Selectel v2 + Selectel v2 SelfHost.(de|eu) Servercow Shellrent - Simply.com + Simply.com Sonic Spaceship Stackpath - Technitium + Technitium Tencent Cloud DNS Tencent EdgeOne Timeweb Cloud - TransIP + TransIP UKFast SafeDNS Ultradns Variomedia - VegaDNS + VegaDNS Vercel Versio.[nl|eu|uk] VinylDNS - VK Cloud + VK Cloud Volcano Engine/火山引擎 Vscale Vultr - webnames.ca + webnames.ca webnames.ru Websupport WEDOS - West.cn/西部数码 + West.cn/西部数码 Yandex 360 Yandex Cloud Yandex PDD - Zone.ee + Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index e21f37e63..2859afd9b 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -62,6 +62,7 @@ func allDNSCodes() string { "dyndnsfree", "dynu", "easydns", + "edgecenter", "edgedns", "edgeone", "efficientip", @@ -1252,6 +1253,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/easydns`) + case "edgecenter": + // generated from: providers/dns/edgecenter/edgecenter.toml + ew.writeln(`Configuration for EdgeCenter.`) + ew.writeln(`Code: 'edgecenter'`) + ew.writeln(`Since: 'v4.29.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "EDGECENTER_PERMANENT_API_TOKEN": Permanent API token (https://edgecenter.ru/blog/permanent-api-token-explained/)`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "EDGECENTER_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "EDGECENTER_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 20)`) + ew.writeln(` - "EDGECENTER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 360)`) + ew.writeln(` - "EDGECENTER_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/edgecenter`) + case "edgedns": // generated from: providers/dns/edgedns/edgedns.toml ew.writeln(`Configuration for Akamai EdgeDNS.`) diff --git a/docs/content/dns/zz_gen_edgecenter.md b/docs/content/dns/zz_gen_edgecenter.md new file mode 100644 index 000000000..7c7dd9379 --- /dev/null +++ b/docs/content/dns/zz_gen_edgecenter.md @@ -0,0 +1,67 @@ +--- +title: "EdgeCenter" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: edgecenter +dnsprovider: + since: "v4.29.0" + code: "edgecenter" + url: "https://edgecenter.ru/dns" +--- + + + + + + +Configuration for [EdgeCenter](https://edgecenter.ru/dns). + + + + +- Code: `edgecenter` +- Since: v4.29.0 + + +Here is an example bash command using the EdgeCenter provider: + +```bash +EDGECENTER_PERMANENT_API_TOKEN=xxxxx \ +lego --email you@example.com --dns edgecenter -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `EDGECENTER_PERMANENT_API_TOKEN` | Permanent API token (https://edgecenter.ru/blog/permanent-api-token-explained/) | + +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 | +|--------------------------------|-------------| +| `EDGECENTER_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `EDGECENTER_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 20) | +| `EDGECENTER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 360) | +| `EDGECENTER_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://apidocs.edgecenter.ru/dns) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 80cbcaac0..071c0f344 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/edgecenter/edgecenter.go b/providers/dns/edgecenter/edgecenter.go new file mode 100644 index 000000000..2040a304c --- /dev/null +++ b/providers/dns/edgecenter/edgecenter.go @@ -0,0 +1,160 @@ +package edgecenter + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/internal/gcore" +) + +// Environment variables names. +const ( + envNamespace = "EDGECENTER_" + + EnvPermanentAPIToken = envNamespace + "PERMANENT_API_TOKEN" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +const ( + defaultPropagationTimeout = 360 * time.Second + defaultPollingInterval = 20 * time.Second +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config for DNSProvider. +type Config struct { + APIToken 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, defaultPropagationTimeout), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, defaultPollingInterval), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second), + }, + } +} + +// DNSProvider an implementation of challenge.Provider contract. +type DNSProvider struct { + config *Config + client *gcore.Client +} + +// NewDNSProvider returns an instance of DNSProvider configured for G-Core DNS API. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvPermanentAPIToken) + if err != nil { + return nil, fmt.Errorf("edgecenter: %w", err) + } + + config := NewDefaultConfig() + config.APIToken = values[EnvPermanentAPIToken] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for G-Core DNS API. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("edgecenter: the configuration of the DNS provider is nil") + } + + if config.APIToken == "" { + return nil, errors.New("edgecenter: incomplete credentials provided") + } + + client := gcore.NewClient(config.APIToken) + client.BaseURL, _ = url.Parse(gcore.DefaultEdgeCenterBaseURL) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record to fulfill the dns-01 challenge. +func (d *DNSProvider) Present(domain, _, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + zone, err := d.guessZone(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("edgecenter: %w", err) + } + + err = d.client.AddRRSet(ctx, zone, dns01.UnFqdn(info.EffectiveFQDN), info.Value, d.config.TTL) + if err != nil { + return fmt.Errorf("edgecenter: add txt record: %w", err) + } + + return nil +} + +// CleanUp removes the record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + zone, err := d.guessZone(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("edgecenter: %w", err) + } + + err = d.client.DeleteRRSet(ctx, zone, dns01.UnFqdn(info.EffectiveFQDN)) + if err != nil { + return fmt.Errorf("edgecenter: remove txt record: %w", err) + } + + 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 +} + +func (d *DNSProvider) guessZone(ctx context.Context, fqdn string) (string, error) { + var lastErr error + + for zone := range dns01.UnFqdnDomainsSeq(fqdn) { + dnsZone, err := d.client.GetZone(ctx, zone) + if err != nil { + lastErr = err + continue + } + + return dnsZone.Name, nil + } + + return "", fmt.Errorf("zone %q not found: %w", fqdn, lastErr) +} diff --git a/providers/dns/edgecenter/edgecenter.toml b/providers/dns/edgecenter/edgecenter.toml new file mode 100644 index 000000000..0cd4b0cb6 --- /dev/null +++ b/providers/dns/edgecenter/edgecenter.toml @@ -0,0 +1,22 @@ +Name = "EdgeCenter" +Description = '''''' +URL = "https://edgecenter.ru/dns" +Code = "edgecenter" +Since = "v4.29.0" + +Example = ''' +EDGECENTER_PERMANENT_API_TOKEN=xxxxx \ +lego --email you@example.com --dns edgecenter -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + EDGECENTER_PERMANENT_API_TOKEN = "Permanent API token (https://edgecenter.ru/blog/permanent-api-token-explained/)" + [Configuration.Additional] + EDGECENTER_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 20)" + EDGECENTER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 360)" + EDGECENTER_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + EDGECENTER_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" + +[Links] + API = "https://apidocs.edgecenter.ru/dns" diff --git a/providers/dns/edgecenter/edgecenter_test.go b/providers/dns/edgecenter/edgecenter_test.go new file mode 100644 index 000000000..79814680d --- /dev/null +++ b/providers/dns/edgecenter/edgecenter_test.go @@ -0,0 +1,116 @@ +package edgecenter + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +var envTest = tester.NewEnvTest(EnvPermanentAPIToken).WithDomain(envNamespace + "DOMAIN") + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvPermanentAPIToken: "A", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{ + EnvPermanentAPIToken: "", + }, + expected: "edgecenter: some credentials information are missing: EDGECENTER_PERMANENT_API_TOKEN", + }, + } + + 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 + apiToken string + expected string + }{ + { + desc: "success", + apiToken: "A", + }, + { + desc: "missing credentials", + expected: "edgecenter: incomplete credentials provided", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIToken = test.apiToken + + 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/gcore/gcore.go b/providers/dns/gcore/gcore.go index 19a548810..6400dc0b3 100644 --- a/providers/dns/gcore/gcore.go +++ b/providers/dns/gcore/gcore.go @@ -5,14 +5,13 @@ import ( "errors" "fmt" "net/http" - "strings" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/gcore/internal" "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/internal/gcore" ) // Environment variables names. @@ -58,7 +57,7 @@ func NewDefaultConfig() *Config { // DNSProvider an implementation of challenge.Provider contract. type DNSProvider struct { config *Config - client *internal.Client + client *gcore.Client } // NewDNSProvider returns an instance of DNSProvider configured for G-Core DNS API. @@ -84,7 +83,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("gcore: incomplete credentials provided") } - client := internal.NewClient(config.APIToken) + client := gcore.NewClient(config.APIToken) if config.HTTPClient != nil { client.HTTPClient = config.HTTPClient @@ -145,28 +144,15 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { func (d *DNSProvider) guessZone(ctx context.Context, fqdn string) (string, error) { var lastErr error - for _, zone := range extractAllZones(fqdn) { + for zone := range dns01.UnFqdnDomainsSeq(fqdn) { dnsZone, err := d.client.GetZone(ctx, zone) - if err == nil { - return dnsZone.Name, nil + if err != nil { + lastErr = err + continue } - lastErr = err + return dnsZone.Name, nil } return "", fmt.Errorf("zone %q not found: %w", fqdn, lastErr) } - -func extractAllZones(fqdn string) []string { - parts := strings.Split(dns01.UnFqdn(fqdn), ".") - if len(parts) < 3 { - return nil - } - - var zones []string - for i := 1; i < len(parts)-1; i++ { - zones = append(zones, strings.Join(parts[i:], ".")) - } - - return zones -} diff --git a/providers/dns/gcore/gcore_test.go b/providers/dns/gcore/gcore_test.go index 88769df21..88c1d02a5 100644 --- a/providers/dns/gcore/gcore_test.go +++ b/providers/dns/gcore/gcore_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/go-acme/lego/v4/platform/tester" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -115,30 +114,3 @@ func TestLiveCleanUp(t *testing.T) { err = provider.CleanUp(envTest.GetDomain(), "", "123d==") require.NoError(t, err) } - -func Test_extractAllZones(t *testing.T) { - testCases := []struct { - desc string - fqdn string - expected []string - }{ - { - desc: "success", - fqdn: "_acme-challenge.my.test.domain.com.", - expected: []string{"my.test.domain.com", "test.domain.com", "domain.com"}, - }, - { - desc: "empty", - fqdn: "_acme-challenge.com.", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - got := extractAllZones(test.fqdn) - assert.Equal(t, test.expected, got) - }) - } -} diff --git a/providers/dns/gcore/internal/client.go b/providers/dns/internal/gcore/client.go similarity index 89% rename from providers/dns/gcore/internal/client.go rename to providers/dns/internal/gcore/client.go index 638aaf0d7..93f17c0d2 100644 --- a/providers/dns/gcore/internal/client.go +++ b/providers/dns/internal/gcore/client.go @@ -1,4 +1,4 @@ -package internal +package gcore import ( "bytes" @@ -14,7 +14,10 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) -const defaultBaseURL = "https://api.gcore.com/dns" +const ( + DefaultGCoreBaseURL = "https://api.gcore.com/dns" + DefaultEdgeCenterBaseURL = "https://api.edgecenter.ru/dns" +) const ( authorizationHeader = "Authorization" @@ -27,17 +30,17 @@ const txtRecordType = "TXT" type Client struct { token string - baseURL *url.URL + BaseURL *url.URL HTTPClient *http.Client } // NewClient constructor of Client. func NewClient(token string) *Client { - baseURL, _ := url.Parse(defaultBaseURL) + baseURL, _ := url.Parse(DefaultGCoreBaseURL) return &Client{ token: token, - baseURL: baseURL, + BaseURL: baseURL, HTTPClient: &http.Client{Timeout: 10 * time.Second}, } } @@ -45,7 +48,7 @@ func NewClient(token string) *Client { // GetZone gets zone information. // https://api.gcore.com/docs/dns#tag/zones/operation/Zone func (c *Client) GetZone(ctx context.Context, name string) (Zone, error) { - endpoint := c.baseURL.JoinPath("v2", "zones", name) + endpoint := c.BaseURL.JoinPath("v2", "zones", name) zone := Zone{} @@ -60,7 +63,7 @@ func (c *Client) GetZone(ctx context.Context, name string) (Zone, error) { // GetRRSet gets RRSet item. // https://api.gcore.com/docs/dns#tag/rrsets/operation/RRSet func (c *Client) GetRRSet(ctx context.Context, zone, name string) (RRSet, error) { - endpoint := c.baseURL.JoinPath("v2", "zones", zone, name, txtRecordType) + endpoint := c.BaseURL.JoinPath("v2", "zones", zone, name, txtRecordType) var result RRSet @@ -75,7 +78,7 @@ func (c *Client) GetRRSet(ctx context.Context, zone, name string) (RRSet, error) // DeleteRRSet removes RRSet record. // https://api.gcore.com/docs/dns#tag/rrsets/operation/DeleteRRSet func (c *Client) DeleteRRSet(ctx context.Context, zone, name string) error { - endpoint := c.baseURL.JoinPath("v2", "zones", zone, name, txtRecordType) + endpoint := c.BaseURL.JoinPath("v2", "zones", zone, name, txtRecordType) err := c.doRequest(ctx, http.MethodDelete, endpoint, nil, nil) if err != nil { @@ -106,14 +109,14 @@ func (c *Client) AddRRSet(ctx context.Context, zone, recordName, value string, t // https://api.gcore.com/docs/dns#tag/rrsets/operation/CreateRRSet func (c *Client) createRRSet(ctx context.Context, zone, name string, record RRSet) error { - endpoint := c.baseURL.JoinPath("v2", "zones", zone, name, txtRecordType) + endpoint := c.BaseURL.JoinPath("v2", "zones", zone, name, txtRecordType) return c.doRequest(ctx, http.MethodPost, endpoint, record, nil) } // https://api.gcore.com/docs/dns#tag/rrsets/operation/UpdateRRSet func (c *Client) updateRRSet(ctx context.Context, zone, name string, record RRSet) error { - endpoint := c.baseURL.JoinPath("v2", "zones", zone, name, txtRecordType) + endpoint := c.BaseURL.JoinPath("v2", "zones", zone, name, txtRecordType) return c.doRequest(ctx, http.MethodPut, endpoint, record, nil) } diff --git a/providers/dns/gcore/internal/client_test.go b/providers/dns/internal/gcore/client_test.go similarity index 98% rename from providers/dns/gcore/internal/client_test.go rename to providers/dns/internal/gcore/client_test.go index 4a0f83311..79289ef42 100644 --- a/providers/dns/gcore/internal/client_test.go +++ b/providers/dns/internal/gcore/client_test.go @@ -1,4 +1,4 @@ -package internal +package gcore import ( "net/http" @@ -21,7 +21,7 @@ func mockBuilder() *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { client := NewClient(testToken) - client.baseURL, _ = url.Parse(server.URL) + client.BaseURL, _ = url.Parse(server.URL) client.HTTPClient = server.Client() return client, nil diff --git a/providers/dns/gcore/internal/types.go b/providers/dns/internal/gcore/types.go similarity index 96% rename from providers/dns/gcore/internal/types.go rename to providers/dns/internal/gcore/types.go index 4245f5ba8..1c4fbd502 100644 --- a/providers/dns/gcore/internal/types.go +++ b/providers/dns/internal/gcore/types.go @@ -1,4 +1,4 @@ -package internal +package gcore import "fmt" diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 32de816a3..90bb2e13c 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -56,6 +56,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/dyndnsfree" "github.com/go-acme/lego/v4/providers/dns/dynu" "github.com/go-acme/lego/v4/providers/dns/easydns" + "github.com/go-acme/lego/v4/providers/dns/edgecenter" "github.com/go-acme/lego/v4/providers/dns/edgedns" "github.com/go-acme/lego/v4/providers/dns/edgeone" "github.com/go-acme/lego/v4/providers/dns/efficientip" @@ -277,6 +278,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return dynu.NewDNSProvider() case "easydns": return easydns.NewDNSProvider() + case "edgecenter": + return edgecenter.NewDNSProvider() case "edgedns", "fastdns": return edgedns.NewDNSProvider() case "edgeone": From b338263c96bef5d1eefb77c40f1c87d2d57cdc6b Mon Sep 17 00:00:00 2001 From: RHQYZ Date: Tue, 11 Nov 2025 20:36:35 +0800 Subject: [PATCH 211/298] baiducloud: pagination and TTL (#2712) Co-authored-by: Fernandez Ludovic --- cmd/zz_gen_cmd_dnshelp.go | 2 +- docs/content/dns/zz_gen_baiducloud.md | 2 +- providers/dns/baiducloud/baiducloud.go | 38 ++++++++++++++++-------- providers/dns/baiducloud/baiducloud.toml | 2 +- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 2859afd9b..24a2470bb 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -472,7 +472,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "BAIDUCLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "BAIDUCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) - ew.writeln(` - "BAIDUCLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + ew.writeln(` - "BAIDUCLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/baiducloud`) diff --git a/docs/content/dns/zz_gen_baiducloud.md b/docs/content/dns/zz_gen_baiducloud.md index 11a71c1ab..9f59aa156 100644 --- a/docs/content/dns/zz_gen_baiducloud.md +++ b/docs/content/dns/zz_gen_baiducloud.md @@ -51,7 +51,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). |--------------------------------|-------------| | `BAIDUCLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `BAIDUCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | -| `BAIDUCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | +| `BAIDUCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | 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" %}}). diff --git a/providers/dns/baiducloud/baiducloud.go b/providers/dns/baiducloud/baiducloud.go index fc317904a..1dc8d90ed 100644 --- a/providers/dns/baiducloud/baiducloud.go +++ b/providers/dns/baiducloud/baiducloud.go @@ -24,6 +24,9 @@ const ( EnvPollingInterval = envNamespace + "POLLING_INTERVAL" ) +// 300 is the minimum TTL for free users. +const defaultTTL = 300 + // Config is used to configure the creation of the DNSProvider. type Config struct { AccessKeyID string @@ -37,7 +40,7 @@ type Config struct { // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), + TTL: env.GetOrDefaultInt(EnvTTL, defaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), } @@ -103,6 +106,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Rr: subDomain, Type: "TXT", Value: info.Value, + Ttl: ptr.Pointer(int32(d.config.TTL)), } err = d.client.CreateRecord(dns01.UnFqdn(authZone), crr, "") @@ -122,14 +126,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("baiducloud: could not find zone for domain %q: %w", domain, err) } - lrr := &baidudns.ListRecordRequest{} - - recordResponse, err := d.client.ListRecord(dns01.UnFqdn(authZone), lrr) - if err != nil { - return fmt.Errorf("baiducloud: list record: %w", err) - } - - recordID, err := findRecordID(recordResponse, info) + recordID, err := d.findRecordID(dns01.UnFqdn(authZone), info.Value) if err != nil { return fmt.Errorf("baiducloud: find record: %w", err) } @@ -142,11 +139,26 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } -func findRecordID(recordResponse *baidudns.ListRecordResponse, info dns01.ChallengeInfo) (string, error) { - for _, record := range recordResponse.Records { - if record.Type == "TXT" && record.Value == info.Value { - return record.Id, nil +func (d *DNSProvider) findRecordID(zoneName, tokenValue string) (string, error) { + lrr := &baidudns.ListRecordRequest{} + + for { + recordResponse, err := d.client.ListRecord(zoneName, lrr) + if err != nil { + return "", fmt.Errorf("baiducloud: list record: %w", err) } + + for _, record := range recordResponse.Records { + if record.Type == "TXT" && record.Value == tokenValue { + return record.Id, nil + } + } + + if !recordResponse.IsTruncated { + break + } + + lrr.Marker = recordResponse.NextMarker } return "", errors.New("record not found") diff --git a/providers/dns/baiducloud/baiducloud.toml b/providers/dns/baiducloud/baiducloud.toml index 941d90b2c..8422eafd5 100644 --- a/providers/dns/baiducloud/baiducloud.toml +++ b/providers/dns/baiducloud/baiducloud.toml @@ -17,7 +17,7 @@ lego --email you@example.com --dns baiducloud -d '*.example.com' -d example.com [Configuration.Additional] BAIDUCLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" BAIDUCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" - BAIDUCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + BAIDUCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" [Links] API = "https://cloud.baidu.com/doc/DNS/s/El4s7lssr" From a8226a67133d2127f8b309e36210af7bd5002e5f Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 14 Nov 2025 14:12:57 +0100 Subject: [PATCH 212/298] namecheap: add experimental proxy support (#2715) --- .golangci.yml | 10 +-- providers/dns/internal/clientdebug/client.go | 3 + providers/dns/namecheap/namecheap.go | 3 +- providers/dns/namecheap/transport.go | 71 ++++++++++++++++++++ providers/dns/namecheap/transport_test.go | 39 +++++++++++ 5 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 providers/dns/namecheap/transport.go create mode 100644 providers/dns/namecheap/transport_test.go diff --git a/.golangci.yml b/.golangci.yml index a6f0c4bfa..2b4bcc41b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -216,11 +216,7 @@ linters: text: load is a global variable linters: - gochecknoglobals - - path: providers/dns/([\d\w]+/)*[\d\w]+_test.go - text: envTest is a global variable - linters: - - gochecknoglobals - - path: providers/http/([\d\w]+/)*[\d\w]+_test.go + - path: providers/(dns|http)/([\d\w]+/)*[\d\w]+_test.go text: envTest is a global variable linters: - gochecknoglobals @@ -228,6 +224,10 @@ linters: text: testCases is a global variable linters: - gochecknoglobals + - path: providers/dns/namecheap/transport.go + text: (envProxyOnce|envProxyFuncValue) is a global variable + linters: + - gochecknoglobals - path: providers/dns/acmedns/mock_test.go text: egTestAccount is a global variable linters: diff --git a/providers/dns/internal/clientdebug/client.go b/providers/dns/internal/clientdebug/client.go index ad2a06405..342577b93 100644 --- a/providers/dns/internal/clientdebug/client.go +++ b/providers/dns/internal/clientdebug/client.go @@ -91,6 +91,9 @@ func (d *DumpTransport) RoundTrip(h *http.Request) (*http.Response, error) { _, _ = fmt.Fprintln(d.writer, d.redact(data)) resp, err := d.rt.RoundTrip(h) + if err != nil { + return nil, err + } data, _ = httputil.DumpResponse(resp, true) diff --git a/providers/dns/namecheap/namecheap.go b/providers/dns/namecheap/namecheap.go index cf8520546..54640f8e0 100644 --- a/providers/dns/namecheap/namecheap.go +++ b/providers/dns/namecheap/namecheap.go @@ -76,7 +76,8 @@ func NewDefaultConfig() *Config { PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, time.Hour), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 15*time.Second), HTTPClient: &http.Client{ - Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, time.Minute), + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, time.Minute), + Transport: defaultTransport(envNamespace), }, } } diff --git a/providers/dns/namecheap/transport.go b/providers/dns/namecheap/transport.go new file mode 100644 index 000000000..584dc6e50 --- /dev/null +++ b/providers/dns/namecheap/transport.go @@ -0,0 +1,71 @@ +package namecheap + +import ( + "net/http" + "net/url" + "strings" + "sync" + + "github.com/go-acme/lego/v4/platform/config/env" + "golang.org/x/net/http/httpproxy" +) + +const ( + envHTTPProxy = "HTTP_PROXY" + envHTTPProxyLower = "http_proxy" + envHTTPSProxy = "HTTPS_PROXY" + envHTTPSProxyLower = "https_proxy" + envNoProxy = "NO_PROXY" + envNoProxyLower = "no_proxy" + envRequestMethod = "REQUEST_METHOD" +) + +// Allows lazy loading of the proxy. +var ( + envProxyOnce sync.Once + envProxyFuncValue func(*url.URL) (*url.URL, error) +) + +func defaultTransport(namespace string) http.RoundTripper { + tr, ok := http.DefaultTransport.(*http.Transport) + if !ok { + return nil + } + + clone := tr.Clone() + clone.Proxy = proxyFromEnvironment(namespace) + + return clone +} + +// Inspired by: +// - https://pkg.go.dev/net/http#ProxyFromEnvironment +// - https://pkg.go.dev/golang.org/x/net/http/httpproxy#FromEnvironment +func envProxyFunc(namespace string) func(*url.URL) (*url.URL, error) { + envProxyOnce.Do(func() { + cfg := &httpproxy.Config{ + HTTPProxy: getEnv(namespace, envHTTPProxy, envHTTPProxyLower), + HTTPSProxy: getEnv(namespace, envHTTPSProxy, envHTTPSProxyLower), + NoProxy: getEnv(namespace, envNoProxy, envNoProxyLower), + CGI: env.GetOneWithFallback(namespace+envRequestMethod, "", env.ParseString, envRequestMethod) != "", + } + + envProxyFuncValue = cfg.ProxyFunc() + }) + + return envProxyFuncValue +} + +// Inspired by: +// - https://pkg.go.dev/net/http#ProxyFromEnvironment +// - https://pkg.go.dev/golang.org/x/net/http/httpproxy#FromEnvironment +func proxyFromEnvironment(namespace string) func(req *http.Request) (*url.URL, error) { + return func(req *http.Request) (*url.URL, error) { + return envProxyFunc(namespace)(req.URL) + } +} + +func getEnv(namespace, baseEnvName, baseEnvNameLower string) string { + return env.GetOneWithFallback(namespace+baseEnvName, "", env.ParseString, + strings.ToLower(namespace)+baseEnvNameLower, baseEnvName, baseEnvNameLower) +} diff --git a/providers/dns/namecheap/transport_test.go b/providers/dns/namecheap/transport_test.go new file mode 100644 index 000000000..cd3e9ff17 --- /dev/null +++ b/providers/dns/namecheap/transport_test.go @@ -0,0 +1,39 @@ +package namecheap + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_defaultTransport(t *testing.T) { + client := servermock.NewBuilder( + func(server *httptest.Server) (*http.Client, error) { + cl := server.Client() + + t.Setenv("NAMECHEAP_HTTP_PROXY", server.URL) + + cl.Transport = defaultTransport(envNamespace) + + return cl, nil + }). + Route("/", + servermock.Noop().WithStatusCode(http.StatusTeapot)). + Build(t) + + req, err := http.NewRequest(http.MethodGet, "http://example.com", nil) + require.NoError(t, err) + + resp, err := client.Do(req) + require.NoError(t, err) + + t.Cleanup(func() { + _ = resp.Body.Close() + }) + + assert.Equal(t, http.StatusTeapot, resp.StatusCode) +} From ea8aca4366818773d79540a6fc2da7aff1cc20bb Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 14 Nov 2025 14:33:54 +0100 Subject: [PATCH 213/298] Add DNS provider for AlibabaCloud ESA (#2703) --- README.md | 84 ++++----- cmd/zz_gen_cmd_dnshelp.go | 24 +++ docs/content/dns/zz_gen_aliesa.md | 78 ++++++++ docs/data/zz_cli_help.toml | 2 +- go.mod | 1 + go.sum | 2 + providers/dns/aliesa/aliesa.go | 251 ++++++++++++++++++++++++++ providers/dns/aliesa/aliesa.toml | 33 ++++ providers/dns/aliesa/aliesa_test.go | 151 ++++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 10 files changed, 586 insertions(+), 43 deletions(-) create mode 100644 docs/content/dns/zz_gen_aliesa.md create mode 100644 providers/dns/aliesa/aliesa.go create mode 100644 providers/dns/aliesa/aliesa.toml create mode 100644 providers/dns/aliesa/aliesa_test.go diff --git a/README.md b/README.md index 59fb983eb..aadf28eb3 100644 --- a/README.md +++ b/README.md @@ -62,212 +62,212 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Active24 Akamai EdgeDNS Alibaba Cloud DNS - all-inkl + AlibabaCloud ESA + all-inkl Amazon Lightsail Amazon Route 53 Anexia CloudDNS - ArvanCloud + ArvanCloud Aurora DNS Autodns Axelname - Azion + Azion Azure (deprecated) Azure DNS Baidu Cloud - Beget.com + Beget.com Binary Lane Bindman Bluecat - BookMyName + BookMyName Brandit (deprecated) Bunny Checkdomain - Civo + Civo Cloud.ru CloudDNS Cloudflare - ClouDNS + ClouDNS CloudXNS (Deprecated) ConoHa v2 ConoHa v3 - Constellix + Constellix Core-Networks CPanel/WHM Derak Cloud - deSEC.io + deSEC.io Designate DNSaaS for Openstack Digital Ocean DirectAdmin - DNS Made Easy + DNS Made Easy dnsHome.de DNSimple DNSPod (deprecated) - Domain Offensive (do.de) + Domain Offensive (do.de) Domeneshop DreamHost Duck DNS - Dyn + Dyn DynDnsFree.de Dynu EasyDNS - EdgeCenter + EdgeCenter Efficient IP Epik Exoscale - External program + External program F5 XC freemyip.com G-Core - Gandi + Gandi Gandi Live DNS (v5) Glesys Go Daddy - Google Cloud + Google Cloud Google Domains Hetzner Hosting.de - Hostinger + Hostinger Hosttech HTTP request http.net - Huawei Cloud + Huawei Cloud Hurricane Electric DNS HyperOne IBM Cloud (SoftLayer) - IIJ DNS Platform Service + IIJ DNS Platform Service Infoblox Infomaniak Internet Initiative Japan - Internet.bs + Internet.bs INWX Ionos IPv64 - iwantmyname (Deprecated) + iwantmyname (Deprecated) Joker Joohoi's ACME-DNS KeyHelp - Liara + Liara Lima-City Linode (v4) Liquid Web - Loopia + Loopia LuaDNS Mail-in-a-Box ManageEngine CloudDNS - Manual + Manual Metaname Metaregistrar mijn.host - Mittwald + Mittwald myaddr.{tools,dev,io} MyDNS.jp MythicBeasts - Name.com + Name.com Namecheap Namesilo NearlyFreeSpeech.NET - Netcup + Netcup Netlify Nicmanager NIFCloud - Njalla + Njalla Nodion NS1 Octenium - Open Telekom Cloud + Open Telekom Cloud Oracle Cloud OVH plesk.com - Porkbun + Porkbun PowerDNS Rackspace Rain Yun/雨云 - RcodeZero + RcodeZero reg.ru Regfish RFC2136 - RimuHosting + RimuHosting RU CENTER Sakura Cloud Scaleway - Selectel + Selectel Selectel v2 SelfHost.(de|eu) Servercow - Shellrent + Shellrent Simply.com Sonic Spaceship - Stackpath + Stackpath Technitium Tencent Cloud DNS Tencent EdgeOne - 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.ca webnames.ru Websupport - WEDOS + WEDOS West.cn/西部数码 Yandex 360 Yandex Cloud - Yandex PDD + Yandex PDD Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 24a2470bb..77f9a4176 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -16,6 +16,7 @@ func allDNSCodes() string { "acme-dns", "active24", "alidns", + "aliesa", "allinkl", "anexia", "arvancloud", @@ -252,6 +253,29 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/alidns`) + case "aliesa": + // generated from: providers/dns/aliesa/aliesa.toml + ew.writeln(`Configuration for AlibabaCloud ESA.`) + ew.writeln(`Code: 'aliesa'`) + ew.writeln(`Since: 'v4.29.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "ALIESA_ACCESS_KEY": Access key ID`) + ew.writeln(` - "ALIESA_RAM_ROLE": Your instance RAM role (https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance)`) + ew.writeln(` - "ALIESA_SECRET_KEY": Access Key secret`) + ew.writeln(` - "ALIESA_SECURITY_TOKEN": STS Security Token (optional)`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "ALIESA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "ALIESA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "ALIESA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "ALIESA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/aliesa`) + case "allinkl": // generated from: providers/dns/allinkl/allinkl.toml ew.writeln(`Configuration for all-inkl.`) diff --git a/docs/content/dns/zz_gen_aliesa.md b/docs/content/dns/zz_gen_aliesa.md new file mode 100644 index 000000000..b286a718a --- /dev/null +++ b/docs/content/dns/zz_gen_aliesa.md @@ -0,0 +1,78 @@ +--- +title: "AlibabaCloud ESA" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: aliesa +dnsprovider: + since: "v4.29.0" + code: "aliesa" + url: "https://www.alibabacloud.com/en/product/esa" +--- + + + + + + +Configuration for [AlibabaCloud ESA](https://www.alibabacloud.com/en/product/esa). + + + + +- Code: `aliesa` +- Since: v4.29.0 + + +Here is an example bash command using the AlibabaCloud ESA provider: + +```bash +# Setup using instance RAM role +ALIESA_RAM_ROLE=lego \ +lego --email you@example.com --dns aliesa -d '*.example.com' -d example.com run + +# Or, using credentials +ALIESA_ACCESS_KEY=abcdefghijklmnopqrstuvwx \ +ALIESA_SECRET_KEY=your-secret-key \ +ALIESA_SECURITY_TOKEN=your-sts-token \ +lego --email you@example.com --dns aliesa - -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `ALIESA_ACCESS_KEY` | Access key ID | +| `ALIESA_RAM_ROLE` | Your instance RAM role (https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance) | +| `ALIESA_SECRET_KEY` | Access Key secret | +| `ALIESA_SECURITY_TOKEN` | STS Security Token (optional) | + +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 | +|--------------------------------|-------------| +| `ALIESA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `ALIESA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `ALIESA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `ALIESA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-overview?spm=a2c63.p38356.help-menu-2673927.d_6_0_0.20b224c28PSZDc#:~:text=DNS-,DNS%20records,-DNS%20records) +- [Go client](https://github.com/alibabacloud-go/esa-20240910) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 071c0f344..62ed20102 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/go.mod b/go.mod index 3dc72c1ff..702da550f 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/dnsimple/dnsimple-go/v4 v4.0.0 github.com/exoscale/egoscale/v3 v3.1.27 github.com/go-acme/alidns-20150109/v4 v4.6.1 + github.com/go-acme/esa-20240910/v2 v2.40.1 github.com/go-acme/tencentclouddnspod v1.1.10 github.com/go-acme/tencentedgdeone v1.1.48 github.com/go-jose/go-jose/v4 v4.1.3 diff --git a/go.sum b/go.sum index a8d61029d..337db59b3 100644 --- a/go.sum +++ b/go.sum @@ -314,6 +314,8 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-acme/alidns-20150109/v4 v4.6.1 h1:Dch3aWRcw4U62+jKPjPQN3iW3TPvgIywATbvHzojXeo= github.com/go-acme/alidns-20150109/v4 v4.6.1/go.mod h1:RBcqBA5IvUWtlpjx6dC6EkPVyBNLQ+mR18XoaP38BFY= +github.com/go-acme/esa-20240910/v2 v2.40.1 h1:pog3UlF5d+3LPoo1L8u8PqB189recIXX8T7pGoEz18A= +github.com/go-acme/esa-20240910/v2 v2.40.1/go.mod h1:ZYdN9EN9ikn26SNapxCVjZ65pHT/1qm4fzuJ7QGVX6g= github.com/go-acme/tencentclouddnspod v1.1.10 h1:ERVJ4mc3cT4Nb3+n6H/c1AwZnChGBqLoymE0NVYscKI= github.com/go-acme/tencentclouddnspod v1.1.10/go.mod h1:Bo/0YQJ/99FM+44HmCQkByuptX1tJsJ9V14MGV/2Qco= github.com/go-acme/tencentedgdeone v1.1.48 h1:WLyLBsRVhSLFmtbEFXk0naLODSQn7X6J0Fc/qR8xVUk= diff --git a/providers/dns/aliesa/aliesa.go b/providers/dns/aliesa/aliesa.go new file mode 100644 index 000000000..deb8162da --- /dev/null +++ b/providers/dns/aliesa/aliesa.go @@ -0,0 +1,251 @@ +// Package aliesa implements a DNS provider for solving the DNS-01 challenge using AlibabaCloud ESA. +package aliesa + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" + "github.com/alibabacloud-go/tea/dara" + "github.com/aliyun/credentials-go/credentials" + esa "github.com/go-acme/esa-20240910/v2/client" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" +) + +// Environment variables names. +const ( + envNamespace = "ALIESA_" + + EnvRAMRole = envNamespace + "RAM_ROLE" + EnvAccessKey = envNamespace + "ACCESS_KEY" + EnvSecretKey = envNamespace + "SECRET_KEY" + EnvSecurityToken = envNamespace + "SECURITY_TOKEN" + EnvRegionID = envNamespace + "REGION_ID" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +const defaultRegionID = "cn-hangzhou" + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + RAMRole string + APIKey string + SecretKey string + SecurityToken string + RegionID string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPTimeout time.Duration +} + +// 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), + HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *esa.Client + + recordIDs map[string]int64 + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for AlibabaCloud ESA. +func NewDNSProvider() (*DNSProvider, error) { + config := NewDefaultConfig() + config.RegionID = env.GetOrFile(EnvRegionID) + + values, err := env.Get(EnvRAMRole) + if err == nil { + config.RAMRole = values[EnvRAMRole] + return NewDNSProviderConfig(config) + } + + values, err = env.Get(EnvAccessKey, EnvSecretKey) + if err != nil { + return nil, fmt.Errorf("aliesa: %w", err) + } + + config.APIKey = values[EnvAccessKey] + config.SecretKey = values[EnvSecretKey] + config.SecurityToken = env.GetOrFile(EnvSecurityToken) + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for AlibabaCloud ESA. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("aliesa: the configuration of the DNS provider is nil") + } + + if config.RegionID == "" { + config.RegionID = defaultRegionID + } + + cfg := new(openapi.Config). + SetRegionId(config.RegionID). + SetReadTimeout(int(config.HTTPTimeout.Milliseconds())) + + switch { + case config.RAMRole != "": + // https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance + credentialsCfg := new(credentials.Config). + SetType("ecs_ram_role"). + SetRoleName(config.RAMRole) + + credentialClient, err := credentials.NewCredential(credentialsCfg) + if err != nil { + return nil, fmt.Errorf("aliesa: new credential: %w", err) + } + + cfg = cfg.SetCredential(credentialClient) + + case config.APIKey != "" && config.SecretKey != "" && config.SecurityToken != "": + cfg = cfg. + SetAccessKeyId(config.APIKey). + SetAccessKeySecret(config.SecretKey). + SetSecurityToken(config.SecurityToken) + + case config.APIKey != "" && config.SecretKey != "": + cfg = cfg. + SetAccessKeyId(config.APIKey). + SetAccessKeySecret(config.SecretKey) + + default: + return nil, errors.New("aliesa: ram role or credentials missing") + } + + client, err := esa.NewClient(cfg) + if err != nil { + return nil, fmt.Errorf("aliesa: new client: %w", err) + } + + // Workaround to get a regional URL. + // https://github.com/alibabacloud-go/esa-20240910/blame/7660e3aab2045d4820e4b83427a154efe0c79319/client/client.go#L27 + // The `EndpointRule` is hardcoded with an empty string, so the region is ignored. + client.Endpoint = nil + client.EndpointRule = ptr.Pointer("regional") + + client.Endpoint, err = esa.GetEndpoint(client, dara.String("esa"), client.RegionId, client.EndpointRule, client.Network, client.Suffix, client.EndpointMap, client.Endpoint) + if err != nil { + return nil, fmt.Errorf("aliesa: get endpoint: %w", err) + } + + return &DNSProvider{ + config: config, + client: client, + recordIDs: make(map[string]int64), + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + siteID, err := d.getSiteID(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("aliesa: %w", err) + } + + crReq := new(esa.CreateRecordRequest). + SetSiteId(siteID). + SetType("TXT"). + SetRecordName(dns01.UnFqdn(info.EffectiveFQDN)). + SetTtl(int32(d.config.TTL)). + SetData(new(esa.CreateRecordRequestData).SetValue(info.Value)) + + // https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-createrecord + crResp, err := esa.CreateRecordWithContext(ctx, d.client, crReq, &dara.RuntimeOptions{}) + if err != nil { + return fmt.Errorf("aliesa: create record: %w", err) + } + + d.recordIDsMu.Lock() + d.recordIDs[token] = ptr.Deref(crResp.Body.GetRecordId()) + d.recordIDsMu.Unlock() + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + // gets the record's unique ID + d.recordIDsMu.Lock() + recordID, ok := d.recordIDs[token] + d.recordIDsMu.Unlock() + + if !ok { + return fmt.Errorf("aliesa: unknown record ID for '%s'", info.EffectiveFQDN) + } + + drReq := new(esa.DeleteRecordRequest). + SetRecordId(recordID) + + // https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-deleterecord + _, err := esa.DeleteRecordWithContext(ctx, d.client, drReq, &dara.RuntimeOptions{}) + if err != nil { + return fmt.Errorf("aliesa: delete record: %w", err) + } + + 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 +} + +func (d *DNSProvider) getSiteID(ctx context.Context, fqdn string) (int64, error) { + authZone, err := dns01.FindZoneByFqdn(fqdn) + if err != nil { + return 0, fmt.Errorf("aliesa: could not find zone for domain %q: %w", fqdn, err) + } + + lsReq := new(esa.ListSitesRequest). + SetSiteName(dns01.UnFqdn(authZone)). + SetSiteSearchType("suffix") + + // https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-listsites + lsResp, err := esa.ListSitesWithContext(ctx, d.client, lsReq, &dara.RuntimeOptions{}) + if err != nil { + return 0, fmt.Errorf("list sites: %w", err) + } + + for f := range dns01.UnFqdnDomainsSeq(fqdn) { + domain := dns01.UnFqdn(f) + + for _, site := range lsResp.Body.GetSites() { + if ptr.Deref(site.GetSiteName()) == domain { + return ptr.Deref(site.GetSiteId()), nil + } + } + } + + return 0, fmt.Errorf("site not found (fqdn: %q)", fqdn) +} diff --git a/providers/dns/aliesa/aliesa.toml b/providers/dns/aliesa/aliesa.toml new file mode 100644 index 000000000..d0f6cdb91 --- /dev/null +++ b/providers/dns/aliesa/aliesa.toml @@ -0,0 +1,33 @@ +Name = "AlibabaCloud ESA" +Description = '''''' +URL = "https://www.alibabacloud.com/en/product/esa" +Code = "aliesa" +Since = "v4.29.0" + +Example = ''' +# Setup using instance RAM role +ALIESA_RAM_ROLE=lego \ +lego --email you@example.com --dns aliesa -d '*.example.com' -d example.com run + +# Or, using credentials +ALIESA_ACCESS_KEY=abcdefghijklmnopqrstuvwx \ +ALIESA_SECRET_KEY=your-secret-key \ +ALIESA_SECURITY_TOKEN=your-sts-token \ +lego --email you@example.com --dns aliesa - -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + ALIESA_RAM_ROLE = "Your instance RAM role (https://www.alibabacloud.com/help/en/ecs/user-guide/attach-an-instance-ram-role-to-an-ecs-instance)" + ALIESA_ACCESS_KEY = "Access key ID" + ALIESA_SECRET_KEY = "Access Key secret" + ALIESA_SECURITY_TOKEN = "STS Security Token (optional)" + [Configuration.Additional] + ALIESA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + ALIESA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + ALIESA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + ALIESA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-overview?spm=a2c63.p38356.help-menu-2673927.d_6_0_0.20b224c28PSZDc#:~:text=DNS-,DNS%20records,-DNS%20records" + GoClient = "https://github.com/alibabacloud-go/esa-20240910" diff --git a/providers/dns/aliesa/aliesa_test.go b/providers/dns/aliesa/aliesa_test.go new file mode 100644 index 000000000..025529409 --- /dev/null +++ b/providers/dns/aliesa/aliesa_test.go @@ -0,0 +1,151 @@ +package aliesa + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest( + EnvAccessKey, + EnvSecretKey, + EnvRAMRole). + WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvAccessKey: "123", + EnvSecretKey: "456", + }, + }, + { + desc: "success (RAM role)", + envVars: map[string]string{ + EnvRAMRole: "LegoInstanceRole", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{ + EnvAccessKey: "", + EnvSecretKey: "", + }, + expected: "aliesa: some credentials information are missing: ALIESA_ACCESS_KEY,ALIESA_SECRET_KEY", + }, + { + desc: "missing access key", + envVars: map[string]string{ + EnvAccessKey: "", + EnvSecretKey: "456", + }, + expected: "aliesa: some credentials information are missing: ALIESA_ACCESS_KEY", + }, + { + desc: "missing secret key", + envVars: map[string]string{ + EnvAccessKey: "123", + EnvSecretKey: "", + }, + expected: "aliesa: some credentials information are missing: ALIESA_SECRET_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 + ramRole string + apiKey string + secretKey string + expected string + }{ + { + desc: "success", + apiKey: "123", + secretKey: "456", + }, + { + desc: "success", + ramRole: "LegoInstanceRole", + }, + { + desc: "missing credentials", + expected: "aliesa: ram role or credentials missing", + }, + { + desc: "missing api key", + secretKey: "456", + expected: "aliesa: ram role or credentials missing", + }, + { + desc: "missing secret key", + apiKey: "123", + expected: "aliesa: ram role or credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIKey = test.apiKey + config.SecretKey = test.secretKey + config.RAMRole = test.ramRole + + 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) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 90bb2e13c..ff6ab0c28 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -10,6 +10,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/acmedns" "github.com/go-acme/lego/v4/providers/dns/active24" "github.com/go-acme/lego/v4/providers/dns/alidns" + "github.com/go-acme/lego/v4/providers/dns/aliesa" "github.com/go-acme/lego/v4/providers/dns/allinkl" "github.com/go-acme/lego/v4/providers/dns/anexia" "github.com/go-acme/lego/v4/providers/dns/arvancloud" @@ -186,6 +187,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return active24.NewDNSProvider() case "alidns": return alidns.NewDNSProvider() + case "aliesa": + return aliesa.NewDNSProvider() case "allinkl": return allinkl.NewDNSProvider() case "anexia": From 57c14f8d2ab22bcd3d3bc7e36827f2b833da001f Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Fri, 14 Nov 2025 18:35:15 +0100 Subject: [PATCH 214/298] chore: add pull request template --- .github/PULL_REQUEST_TEMPLATE.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..8b1690de5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ + From 0abf391bd1db8a24cf7b1910ff4be514d8581cd9 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 19 Nov 2025 01:27:40 +0100 Subject: [PATCH 215/298] docs: remove author names --- docs/hugo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/hugo.toml b/docs/hugo.toml index b17206d43..fe076a306 100644 --- a/docs/hugo.toml +++ b/docs/hugo.toml @@ -15,6 +15,7 @@ title = "Lego" custom_css = ["css/theme-custom.css"] disableLandingPageButton = true hideAuthorEmail = true + hideAuthorName = true # Author of the site, will be used in meta information [params.author] From 93b8bb71ca5e6de001b28e3d01c83f4362ca851b Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 22 Nov 2025 01:11:55 +0100 Subject: [PATCH 216/298] hetzner: use int64 for IDs (#2720) --- providers/dns/hetzner/internal/hetznerv1/hetznerv1.go | 2 +- providers/dns/hetzner/internal/hetznerv1/internal/client.go | 4 ++-- .../dns/hetzner/internal/hetznerv1/internal/client_test.go | 6 +++--- .../hetznerv1/internal/fixtures/add_rrset_records.json | 2 +- .../internal/hetznerv1/internal/fixtures/get_action.json | 4 ++-- providers/dns/hetzner/internal/hetznerv1/internal/types.go | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go b/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go index 4fb95eb6f..b31c766ce 100644 --- a/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go +++ b/providers/dns/hetzner/internal/hetznerv1/hetznerv1.go @@ -184,7 +184,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { return d.config.PropagationTimeout, d.config.PollingInterval } -func (d *DNSProvider) waitAction(ctx context.Context, actionID int) error { +func (d *DNSProvider) waitAction(ctx context.Context, actionID int64) error { return wait.Retry(ctx, func() error { result, err := d.client.GetAction(ctx, actionID) diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/client.go b/providers/dns/hetzner/internal/hetznerv1/internal/client.go index 35c3d461b..2f29f642a 100644 --- a/providers/dns/hetzner/internal/hetznerv1/internal/client.go +++ b/providers/dns/hetzner/internal/hetznerv1/internal/client.go @@ -85,8 +85,8 @@ func (c *Client) RemoveRRSetRecords(ctx context.Context, zoneIDName, recordType, // GetAction gets an action. // https://docs.hetzner.cloud/reference/cloud#actions-get-an-action -func (c *Client) GetAction(ctx context.Context, id int) (*Action, error) { - endpoint := c.BaseURL.JoinPath("actions", strconv.Itoa(id)) +func (c *Client) GetAction(ctx context.Context, id int64) (*Action, error) { + endpoint := c.BaseURL.JoinPath("actions", strconv.FormatInt(id, 10)) req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) if err != nil { diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/client_test.go b/providers/dns/hetzner/internal/hetznerv1/internal/client_test.go index fcbc7636f..6fd3d77a7 100644 --- a/providers/dns/hetzner/internal/hetznerv1/internal/client_test.go +++ b/providers/dns/hetzner/internal/hetznerv1/internal/client_test.go @@ -49,7 +49,7 @@ func TestClient_AddRRSetRecords(t *testing.T) { Command: "add_rrset_records", Status: "running", Progress: 50, - Resources: []Resources{{ID: 42, Type: "zone"}}, + Resources: []Resources{{ID: 590000000000000, Type: "zone"}}, } assert.Equal(t, expected, result) @@ -139,11 +139,11 @@ func TestClient_GetAction(t *testing.T) { require.NoError(t, err) expected := &Action{ - ID: 42, + ID: 590000000000000, Command: "start_resource", Status: "running", Progress: 100, - Resources: []Resources{{ID: 42, Type: "server"}}, + Resources: []Resources{{ID: 590000000000000, Type: "server"}}, ErrorInfo: &ErrorInfo{ Code: "action_failed", Message: "Action failed", diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records.json index 2341c7e6e..7267b02cb 100644 --- a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records.json +++ b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/add_rrset_records.json @@ -8,7 +8,7 @@ "finished": null, "resources": [ { - "id": 42, + "id": 590000000000000, "type": "zone" } ], diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/get_action.json b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/get_action.json index 05f003b1e..19278fc51 100644 --- a/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/get_action.json +++ b/providers/dns/hetzner/internal/hetznerv1/internal/fixtures/get_action.json @@ -1,6 +1,6 @@ { "action": { - "id": 42, + "id": 590000000000000, "command": "start_resource", "status": "running", "started": "2016-01-30T23:55:00+00:00", @@ -8,7 +8,7 @@ "progress": 100, "resources": [ { - "id": 42, + "id": 590000000000000, "type": "server" } ], diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/types.go b/providers/dns/hetzner/internal/hetznerv1/internal/types.go index 08d1684c0..47e8a3f91 100644 --- a/providers/dns/hetzner/internal/hetznerv1/internal/types.go +++ b/providers/dns/hetzner/internal/hetznerv1/internal/types.go @@ -79,7 +79,7 @@ type ActionResponse struct { } type Action struct { - ID int `json:"id,omitempty"` + ID int64 `json:"id,omitempty"` Command string `json:"command,omitempty"` // It can be: `running`, `success`, `error`. @@ -93,6 +93,6 @@ type Action struct { } type Resources struct { - ID int `json:"id,omitempty"` + ID int64 `json:"id,omitempty"` Type string `json:"type,omitempty"` } From aea6afe2d66f403096b96d563ed8c572cc7c3f33 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 24 Nov 2025 18:44:43 +0100 Subject: [PATCH 217/298] Add DNS provider for Gigahost.no (#2723) --- README.md | 57 ++-- cmd/zz_gen_cmd_dnshelp.go | 23 ++ docs/content/dns/zz_gen_gigahostno.md | 70 +++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/gigahostno/gigahostno.go | 233 +++++++++++++++ providers/dns/gigahostno/gigahostno.toml | 25 ++ providers/dns/gigahostno/gigahostno_test.go | 277 ++++++++++++++++++ providers/dns/gigahostno/internal/client.go | 172 +++++++++++ .../dns/gigahostno/internal/client_test.go | 179 +++++++++++ .../fixtures/authenticate-request.json | 4 + .../internal/fixtures/authenticate.json | 23 ++ .../fixtures/create_record-request.json | 6 + .../internal/fixtures/create_record.json | 7 + .../internal/fixtures/delete_record.json | 7 + .../gigahostno/internal/fixtures/error.json | 9 + .../internal/fixtures/zone_records.json | 39 +++ .../gigahostno/internal/fixtures/zones.json | 97 ++++++ providers/dns/gigahostno/internal/identity.go | 122 ++++++++ .../dns/gigahostno/internal/identity_test.go | 108 +++++++ providers/dns/gigahostno/internal/types.go | 83 ++++++ providers/dns/zz_gen_dns_providers.go | 3 + 21 files changed, 1519 insertions(+), 27 deletions(-) create mode 100644 docs/content/dns/zz_gen_gigahostno.md create mode 100644 providers/dns/gigahostno/gigahostno.go create mode 100644 providers/dns/gigahostno/gigahostno.toml create mode 100644 providers/dns/gigahostno/gigahostno_test.go create mode 100644 providers/dns/gigahostno/internal/client.go create mode 100644 providers/dns/gigahostno/internal/client_test.go create mode 100644 providers/dns/gigahostno/internal/fixtures/authenticate-request.json create mode 100644 providers/dns/gigahostno/internal/fixtures/authenticate.json create mode 100644 providers/dns/gigahostno/internal/fixtures/create_record-request.json create mode 100644 providers/dns/gigahostno/internal/fixtures/create_record.json create mode 100644 providers/dns/gigahostno/internal/fixtures/delete_record.json create mode 100644 providers/dns/gigahostno/internal/fixtures/error.json create mode 100644 providers/dns/gigahostno/internal/fixtures/zone_records.json create mode 100644 providers/dns/gigahostno/internal/fixtures/zones.json create mode 100644 providers/dns/gigahostno/internal/identity.go create mode 100644 providers/dns/gigahostno/internal/identity_test.go create mode 100644 providers/dns/gigahostno/internal/types.go diff --git a/README.md b/README.md index aadf28eb3..d40afc579 100644 --- a/README.md +++ b/README.md @@ -136,138 +136,143 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Gandi Gandi Live DNS (v5) + Gigahost.no Glesys - Go Daddy + Go Daddy Google Cloud Google Domains Hetzner - Hosting.de + Hosting.de Hostinger Hosttech HTTP request - http.net + http.net Huawei Cloud Hurricane Electric DNS HyperOne - IBM Cloud (SoftLayer) + IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox Infomaniak - Internet Initiative Japan + Internet Initiative Japan Internet.bs INWX Ionos - IPv64 + IPv64 iwantmyname (Deprecated) Joker Joohoi's ACME-DNS - KeyHelp + KeyHelp Liara Lima-City Linode (v4) - Liquid Web + Liquid Web Loopia LuaDNS Mail-in-a-Box - ManageEngine CloudDNS + ManageEngine CloudDNS Manual Metaname Metaregistrar - mijn.host + mijn.host Mittwald myaddr.{tools,dev,io} MyDNS.jp - MythicBeasts + MythicBeasts Name.com Namecheap Namesilo - NearlyFreeSpeech.NET + NearlyFreeSpeech.NET Netcup Netlify Nicmanager - NIFCloud + NIFCloud Njalla Nodion NS1 - Octenium + Octenium Open Telekom Cloud Oracle Cloud OVH - plesk.com + plesk.com Porkbun PowerDNS Rackspace - Rain Yun/雨云 + Rain Yun/雨云 RcodeZero reg.ru Regfish - RFC2136 + RFC2136 RimuHosting RU CENTER Sakura Cloud - Scaleway + Scaleway Selectel Selectel v2 SelfHost.(de|eu) - Servercow + Servercow Shellrent Simply.com Sonic - Spaceship + Spaceship Stackpath Technitium Tencent Cloud DNS - Tencent EdgeOne + Tencent EdgeOne Timeweb Cloud TransIP UKFast SafeDNS - Ultradns + Ultradns Variomedia VegaDNS Vercel - Versio.[nl|eu|uk] + Versio.[nl|eu|uk] VinylDNS VK Cloud Volcano Engine/火山引擎 - Vscale + Vscale Vultr webnames.ca webnames.ru - Websupport + Websupport WEDOS West.cn/西部数码 Yandex 360 - Yandex Cloud + Yandex Cloud Yandex PDD Zone.ee ZoneEdit + Zonomi + + + diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 77f9a4176..deb93e6eb 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -76,6 +76,7 @@ func allDNSCodes() string { "gandiv5", "gcloud", "gcore", + "gigahostno", "glesys", "godaddy", "googledomains", @@ -1550,6 +1551,28 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/gcore`) + case "gigahostno": + // generated from: providers/dns/gigahostno/gigahostno.toml + ew.writeln(`Configuration for Gigahost.no.`) + ew.writeln(`Code: 'gigahostno'`) + ew.writeln(`Since: 'v4.29.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "GIGAHOSTNO_PASSWORD": Password`) + ew.writeln(` - "GIGAHOSTNO_USERNAME": Username`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "GIGAHOSTNO_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "GIGAHOSTNO_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "GIGAHOSTNO_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "GIGAHOSTNO_SECRET": TOTP secret`) + ew.writeln(` - "GIGAHOSTNO_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/gigahostno`) + case "glesys": // generated from: providers/dns/glesys/glesys.toml ew.writeln(`Configuration for Glesys.`) diff --git a/docs/content/dns/zz_gen_gigahostno.md b/docs/content/dns/zz_gen_gigahostno.md new file mode 100644 index 000000000..afb7c64c9 --- /dev/null +++ b/docs/content/dns/zz_gen_gigahostno.md @@ -0,0 +1,70 @@ +--- +title: "Gigahost.no" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: gigahostno +dnsprovider: + since: "v4.29.0" + code: "gigahostno" + url: "https://gigahost.no/" +--- + + + + + + +Configuration for [Gigahost.no](https://gigahost.no/). + + + + +- Code: `gigahostno` +- Since: v4.29.0 + + +Here is an example bash command using the Gigahost.no provider: + +```bash +GIGAHOSTNO_USERNAME="xxxxxxxxxxxxxxxxxxxxx" \ +GIGAHOSTNO_PASSWORD="yyyyyyyyyyyyyyyyyyyyy" \ +lego --email you@example.com --dns gigahostno -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `GIGAHOSTNO_PASSWORD` | Password | +| `GIGAHOSTNO_USERNAME` | Username | + +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 | +|--------------------------------|-------------| +| `GIGAHOSTNO_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `GIGAHOSTNO_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `GIGAHOSTNO_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `GIGAHOSTNO_SECRET` | TOTP secret | +| `GIGAHOSTNO_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://gigahost.no/api-dokumentasjon) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 62ed20102..552539fb0 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/gigahostno/gigahostno.go b/providers/dns/gigahostno/gigahostno.go new file mode 100644 index 000000000..b9ed23f3f --- /dev/null +++ b/providers/dns/gigahostno/gigahostno.go @@ -0,0 +1,233 @@ +// Package gigahostno implements a DNS provider for solving the DNS-01 challenge using Gigahost.no. +package gigahostno + +import ( + "context" + "errors" + "fmt" + "net/http" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/gigahostno/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" +) + +// Environment variables names. +const ( + envNamespace = "GIGAHOSTNO_" + + EnvUsername = envNamespace + "USERNAME" + EnvPassword = envNamespace + "PASSWORD" + EnvSecret = envNamespace + "SECRET" + + 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 + Secret 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 + + identifier *internal.Identifier + client *internal.Client + + tokenMu sync.Mutex + token *internal.Token +} + +// NewDNSProvider returns a DNSProvider instance configured for Gigahost. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvUsername, EnvPassword) + if err != nil { + return nil, fmt.Errorf("gigahostno: %w", err) + } + + config := NewDefaultConfig() + config.Username = values[EnvUsername] + config.Password = values[EnvPassword] + config.Secret = env.GetOrFile(EnvSecret) + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Gigahost. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("gigahostno: the configuration of the DNS provider is nil") + } + + identifier, err := internal.NewIdentifier(config.Username, config.Password, config.Secret) + if err != nil { + return nil, fmt.Errorf("gigahostno: %w", err) + } + + if config.HTTPClient != nil { + identifier.HTTPClient = config.HTTPClient + } + + identifier.HTTPClient = clientdebug.Wrap(identifier.HTTPClient) + + client := internal.NewClient() + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + identifier: identifier, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + err := d.authenticate(ctx) + if err != nil { + return fmt.Errorf("gigahostno: %w", err) + } + + ctx = internal.WithContext(ctx, d.token.Token) + + zone, err := d.findZone(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("gigahostno: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone.Name) + if err != nil { + return fmt.Errorf("gigahostno: %w", err) + } + + record := internal.Record{ + Name: subDomain, + Type: "TXT", + Value: info.Value, + TTL: d.config.TTL, + } + + err = d.client.CreateNewRecord(ctx, zone.ID, record) + if err != nil { + return fmt.Errorf("gigahostno: create new record: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + err := d.authenticate(ctx) + if err != nil { + return fmt.Errorf("gigahostno: %w", err) + } + + ctx = internal.WithContext(ctx, d.token.Token) + + zone, err := d.findZone(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("gigahostno: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone.Name) + if err != nil { + return fmt.Errorf("gigahostno: %w", err) + } + + records, err := d.client.GetZoneRecords(ctx, zone.ID) + if err != nil { + return fmt.Errorf("gigahostno: get zone records: %w", err) + } + + for _, record := range records { + if record.Type == "TXT" && record.Name == subDomain && record.Value == info.Value { + err := d.client.DeleteRecord(ctx, zone.ID, record.ID, record.Name, record.Type) + if err != nil { + return fmt.Errorf("gigahostno: delete record: %w", err) + } + + break + } + } + + 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 +} + +func (d *DNSProvider) authenticate(ctx context.Context) error { + d.tokenMu.Lock() + defer d.tokenMu.Unlock() + + if !d.token.IsExpired() { + return nil + } + + tok, err := d.identifier.Authenticate(ctx) + if err != nil { + return fmt.Errorf("authenticate: %w", err) + } + + d.token = tok + + return nil +} + +func (d *DNSProvider) findZone(ctx context.Context, fqdn string) (*internal.Zone, error) { + zones, err := d.client.GetZones(ctx) + if err != nil { + return nil, fmt.Errorf("get zones: %w", err) + } + + for d := range dns01.UnFqdnDomainsSeq(fqdn) { + for _, zone := range zones { + if zone.Name == d && zone.Active == "1" { + return &zone, nil + } + } + } + + return nil, fmt.Errorf("zone not found for %q", fqdn) +} diff --git a/providers/dns/gigahostno/gigahostno.toml b/providers/dns/gigahostno/gigahostno.toml new file mode 100644 index 000000000..689b96569 --- /dev/null +++ b/providers/dns/gigahostno/gigahostno.toml @@ -0,0 +1,25 @@ +Name = "Gigahost.no" +Description = '''''' +URL = "https://gigahost.no/" +Code = "gigahostno" +Since = "v4.29.0" + +Example = ''' +GIGAHOSTNO_USERNAME="xxxxxxxxxxxxxxxxxxxxx" \ +GIGAHOSTNO_PASSWORD="yyyyyyyyyyyyyyyyyyyyy" \ +lego --email you@example.com --dns gigahostno -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + GIGAHOSTNO_USERNAME = "Username" + GIGAHOSTNO_PASSWORD = "Password" + [Configuration.Additional] + GIGAHOSTNO_SECRET = "TOTP secret" + GIGAHOSTNO_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + GIGAHOSTNO_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + GIGAHOSTNO_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + GIGAHOSTNO_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://gigahost.no/api-dokumentasjon" diff --git a/providers/dns/gigahostno/gigahostno_test.go b/providers/dns/gigahostno/gigahostno_test.go new file mode 100644 index 000000000..7aaac0159 --- /dev/null +++ b/providers/dns/gigahostno/gigahostno_test.go @@ -0,0 +1,277 @@ +package gigahostno + +import ( + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/go-acme/lego/v4/providers/dns/gigahostno/internal" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest( + EnvUsername, + EnvPassword, + EnvSecret, +).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvUsername: "user", + EnvPassword: "secret", + EnvSecret: "super-secret", + }, + }, + { + desc: "missing GIGAHOSTNO_USERNAME", + envVars: map[string]string{ + EnvPassword: "secret", + }, + expected: "gigahostno: some credentials information are missing: GIGAHOSTNO_USERNAME", + }, + { + desc: "missing GIGAHOSTNO_PASSWORD", + envVars: map[string]string{ + EnvUsername: "user", + }, + expected: "gigahostno: some credentials information are missing: GIGAHOSTNO_PASSWORD", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "gigahostno: some credentials information are missing: GIGAHOSTNO_USERNAME,GIGAHOSTNO_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 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 + username string + password string + secret string + expected string + }{ + { + desc: "success", + username: "user", + password: "secret", + secret: "super-secret", + }, + { + desc: "missing username", + password: "secret", + expected: "gigahostno: credentials missing", + }, + { + desc: "missing password", + username: "user", + expected: "gigahostno: credentials missing", + }, + { + desc: "missing credentials", + expected: "gigahostno: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Username = test.username + config.Password = test.password + config.Secret = test.secret + + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.Username = "user" + config.Password = "secret" + config.Secret = "JBSWY3DPEHPK3PXP" + + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BaseURL, _ = url.Parse(server.URL) + p.identifier.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("POST /authenticate", + servermock.ResponseFromInternal("authenticate.json")). + Route("GET /dns/zones", + servermock.ResponseFromInternal("zones.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer secrettoken")). + Route("POST /dns/zones/123/records", + servermock.ResponseFromInternal("create_record.json"), + servermock.CheckRequestJSONBodyFromInternal("create_record-request.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer secrettoken")). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_Present_token_not_expired(t *testing.T) { + provider := mockBuilder(). + Route("GET /dns/zones", + servermock.ResponseFromInternal("zones.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer secret-token")). + Route("POST /dns/zones/123/records", + servermock.ResponseFromInternal("create_record.json"), + servermock.CheckRequestJSONBodyFromInternal("create_record-request.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer secret-token")). + Build(t) + + provider.token = &internal.Token{ + Token: "secret-token", + TokenExpire: 65322892800, // 2040-01-01 + CustomerID: "123", + } + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("POST /authenticate", + servermock.ResponseFromInternal("authenticate.json")). + Route("GET /dns/zones", + servermock.ResponseFromInternal("zones.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer secrettoken")). + Route("GET /dns/zones/123/records", + servermock.ResponseFromInternal("zone_records.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer secrettoken")). + Route("DELETE /dns/zones/123/records/jkl012", + servermock.ResponseFromInternal("delete_record.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "_acme-challenge"). + With("type", "TXT"), + servermock.CheckHeader(). + WithAuthorization("Bearer secrettoken")). + Build(t) + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp_token_not_expired(t *testing.T) { + provider := mockBuilder(). + Route("GET /dns/zones", + servermock.ResponseFromInternal("zones.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer secret-token")). + Route("GET /dns/zones/123/records", + servermock.ResponseFromInternal("zone_records.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer secret-token")). + Route("DELETE /dns/zones/123/records/jkl012", + servermock.ResponseFromInternal("delete_record.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "_acme-challenge"). + With("type", "TXT"), + servermock.CheckHeader(). + WithAuthorization("Bearer secret-token")). + Build(t) + + provider.token = &internal.Token{ + Token: "secret-token", + TokenExpire: 65322892800, // 2040-01-01 + CustomerID: "123", + } + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/gigahostno/internal/client.go b/providers/dns/gigahostno/internal/client.go new file mode 100644 index 000000000..cfff3a7b8 --- /dev/null +++ b/providers/dns/gigahostno/internal/client.go @@ -0,0 +1,172 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" +) + +const defaultBaseURL = "https://api.gigahost.no/api/v0" + +const authorizationHeader = "Authorization" + +// Client the Gigahost.no API client. +type Client struct { + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient() *Client { + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + } +} + +// GetZones returns all zones. +func (c *Client) GetZones(ctx context.Context) ([]Zone, error) { + endpoint := c.BaseURL.JoinPath("dns", "zones") + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var result APIResponse[[]Zone] + + err = c.do(ctx, req, &result) + if err != nil { + return nil, err + } + + return result.Data, nil +} + +// GetZoneRecords returns all records for a zone. +func (c *Client) GetZoneRecords(ctx context.Context, zoneID string) ([]Record, error) { + endpoint := c.BaseURL.JoinPath("dns", "zones", zoneID, "records") + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var result APIResponse[[]Record] + + err = c.do(ctx, req, &result) + if err != nil { + return nil, err + } + + return result.Data, nil +} + +// CreateNewRecord creates a new record. +func (c *Client) CreateNewRecord(ctx context.Context, zoneID string, record Record) error { + endpoint := c.BaseURL.JoinPath("dns", "zones", zoneID, "records") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) + if err != nil { + return err + } + + return c.do(ctx, req, nil) +} + +// DeleteRecord deletes a record. +func (c *Client) DeleteRecord(ctx context.Context, zoneID, recordID, name, recordType string) error { + endpoint := c.BaseURL.JoinPath("dns", "zones", zoneID, "records", recordID) + + query := endpoint.Query() + query.Set("name", name) + query.Set("type", recordType) + endpoint.RawQuery = query.Encode() + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + return c.do(ctx, req, nil) +} + +func (c *Client) do(ctx context.Context, req *http.Request, result any) error { + useragent.SetHeader(req.Header) + + req.Header.Set(authorizationHeader, "Bearer "+getToken(ctx)) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/gigahostno/internal/client_test.go b/providers/dns/gigahostno/internal/client_test.go new file mode 100644 index 000000000..aac65bceb --- /dev/null +++ b/providers/dns/gigahostno/internal/client_test.go @@ -0,0 +1,179 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client := NewClient() + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + WithAuthorization("Bearer secret"), + ) +} + +func TestClient_GetZones(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/zones", + servermock.ResponseFromFixture("zones.json")). + Build(t) + + zones, err := client.GetZones(mockContext(t)) + require.NoError(t, err) + + expected := []Zone{ + { + ID: "123", + Name: "example.com", + NameDisplay: "example.com", + Type: "NATIVE", + Active: "1", + Protected: "1", + IsRegistered: "1", + Updated: false, + CustomerID: "16030", + DomainRegistrar: "norid", + DomainStatus: "active", + DomainExpiryDate: "2026-11-23 15:17:38", + DomainAutoRenew: "1", + ExternalDNS: "0", + RecordCount: 4, + }, + { + ID: "226", + Name: "example.org", + NameDisplay: "example.org", + Type: "NATIVE", + Active: "1", + Protected: "1", + IsRegistered: "1", + Updated: false, + CustomerID: "16030", + DomainRegistrar: "norid", + DomainStatus: "active", + DomainExpiryDate: "2026-11-23 14:15:01", + DomainAutoRenew: "1", + ExternalDNS: "0", + RecordCount: 5, + }, + { + ID: "229", + Name: "example.xn--zckzah", + NameDisplay: "example.テスト", + Type: "NATIVE", + Active: "1", + Protected: "1", + IsRegistered: "1", + Updated: false, + CustomerID: "16030", + DomainRegistrar: "norid", + DomainStatus: "active", + DomainExpiryDate: "2026-12-01 12:40:48", + DomainAutoRenew: "1", + ExternalDNS: "0", + RecordCount: 4, + }, + } + + assert.Equal(t, expected, zones) +} + +func TestClient_GetZones_error(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/zones", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + _, err := client.GetZones(mockContext(t)) + require.EqualError(t, err, "401: 401 Unauthorized: 401 Unauthorized") +} + +func TestClient_GetZoneRecords(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/zones/123/records", + servermock.ResponseFromFixture("zone_records.json")). + Build(t) + + zones, err := client.GetZoneRecords(mockContext(t), "123") + require.NoError(t, err) + + expected := []Record{ + { + ID: "abc123", + Name: "@", + Type: "A", + Value: "185.125.168.166", + TTL: 3600, + }, + { + ID: "def456", + Name: "www", + Type: "A", + Value: "185.125.168.166", + TTL: 3600, + }, + { + ID: "ghi789", + Name: "@", + Type: "MX", + Value: "mail.example.no", + TTL: 3600, + }, + { + ID: "jkl012", + Name: "_acme-challenge", + Type: "TXT", + Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 120, + }, + } + + assert.Equal(t, expected, zones) +} + +func TestClient_CreateNewRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /dns/zones/example.com/records", + servermock.ResponseFromFixture("create_record.json"), + servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). + Build(t) + + record := Record{ + Name: "_acme-challenge", + Type: "TXT", + Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 120, + } + + err := client.CreateNewRecord(mockContext(t), "example.com", record) + require.NoError(t, err) +} + +func TestClient_DeleteRecord(t *testing.T) { + client := mockBuilder(). + Route("/dns/zones/123/records/abc123", + servermock.ResponseFromFixture("delete_record.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "_acme-challenge"). + With("type", "TXT")). + Build(t) + + err := client.DeleteRecord(mockContext(t), "123", "abc123", "_acme-challenge", "TXT") + require.NoError(t, err) +} diff --git a/providers/dns/gigahostno/internal/fixtures/authenticate-request.json b/providers/dns/gigahostno/internal/fixtures/authenticate-request.json new file mode 100644 index 000000000..c641cd3e5 --- /dev/null +++ b/providers/dns/gigahostno/internal/fixtures/authenticate-request.json @@ -0,0 +1,4 @@ +{ + "username": "user", + "password": "secret" +} diff --git a/providers/dns/gigahostno/internal/fixtures/authenticate.json b/providers/dns/gigahostno/internal/fixtures/authenticate.json new file mode 100644 index 000000000..2c43ccbfe --- /dev/null +++ b/providers/dns/gigahostno/internal/fixtures/authenticate.json @@ -0,0 +1,23 @@ +{ + "meta": { + "status": 200, + "status_message": "200 OK", + "maintenance": false + }, + "data": { + "token": "secrettoken", + "token_expire": 1577836800, + "customer_id": "16030", + "contact_id": "15182", + "customer_name": "Cloudline AS", + "contact_username": "test@example.com", + "contact_access_level": "admin", + "customer_address": "Grønland 14", + "customer_zipcode": "5918", + "customer_city": "Frekhaug", + "customer_province": "Vestland", + "ga_secret": "ga_secret", + "ga_enabled": "1", + "vat": 1 + } +} diff --git a/providers/dns/gigahostno/internal/fixtures/create_record-request.json b/providers/dns/gigahostno/internal/fixtures/create_record-request.json new file mode 100644 index 000000000..f8f0b5b11 --- /dev/null +++ b/providers/dns/gigahostno/internal/fixtures/create_record-request.json @@ -0,0 +1,6 @@ +{ + "record_name": "_acme-challenge", + "record_type": "TXT", + "record_value": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "record_ttl": 120 +} diff --git a/providers/dns/gigahostno/internal/fixtures/create_record.json b/providers/dns/gigahostno/internal/fixtures/create_record.json new file mode 100644 index 000000000..9232677d7 --- /dev/null +++ b/providers/dns/gigahostno/internal/fixtures/create_record.json @@ -0,0 +1,7 @@ +{ + "meta": { + "status": 201, + "status_message": "201 Created", + "message": "Record created successfully." + } +} diff --git a/providers/dns/gigahostno/internal/fixtures/delete_record.json b/providers/dns/gigahostno/internal/fixtures/delete_record.json new file mode 100644 index 000000000..9d87f2f42 --- /dev/null +++ b/providers/dns/gigahostno/internal/fixtures/delete_record.json @@ -0,0 +1,7 @@ +{ + "meta": { + "status": 200, + "status_message": "200 OK", + "message": "Record deleted successfully." + } +} diff --git a/providers/dns/gigahostno/internal/fixtures/error.json b/providers/dns/gigahostno/internal/fixtures/error.json new file mode 100644 index 000000000..f2fcfd437 --- /dev/null +++ b/providers/dns/gigahostno/internal/fixtures/error.json @@ -0,0 +1,9 @@ +{ + "meta": { + "status": 401, + "status_message": "401 Unauthorized", + "maintenance": false, + "message": "401 Unauthorized" + }, + "data": [] +} diff --git a/providers/dns/gigahostno/internal/fixtures/zone_records.json b/providers/dns/gigahostno/internal/fixtures/zone_records.json new file mode 100644 index 000000000..e67ff83f4 --- /dev/null +++ b/providers/dns/gigahostno/internal/fixtures/zone_records.json @@ -0,0 +1,39 @@ +{ + "meta": { + "status": 200, + "status_message": "200 OK" + }, + "data": [ + { + "record_id": "abc123", + "record_name": "@", + "record_type": "A", + "record_value": "185.125.168.166", + "record_ttl": 3600, + "record_priority": null + }, + { + "record_id": "def456", + "record_name": "www", + "record_type": "A", + "record_value": "185.125.168.166", + "record_ttl": 3600, + "record_priority": null + }, + { + "record_id": "ghi789", + "record_name": "@", + "record_type": "MX", + "record_value": "mail.example.no", + "record_ttl": 3600, + "record_priority": 10 + }, + { + "record_id": "jkl012", + "record_name": "_acme-challenge", + "record_type": "TXT", + "record_value": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "record_ttl": 120 + } + ] +} diff --git a/providers/dns/gigahostno/internal/fixtures/zones.json b/providers/dns/gigahostno/internal/fixtures/zones.json new file mode 100644 index 000000000..f4d927335 --- /dev/null +++ b/providers/dns/gigahostno/internal/fixtures/zones.json @@ -0,0 +1,97 @@ +{ + "meta": { + "status": 200, + "status_message": "200 OK", + "maintenance": false, + "message": "200 OK" + }, + "data": [ + { + "zone_id": "123", + "cust_id": "16030", + "order_id": "26117", + "zone_name": "example.com", + "zone_type": "NATIVE", + "zone_active": "1", + "zone_protected": "1", + "zone_is_registered": "1", + "domain_registrar": "norid", + "domain_status": "active", + "domain_registered_date": "2025-11-23 15:17:38", + "domain_expiry_date": "2026-11-23 15:17:38", + "domain_updated_date": "2025-11-23 16:17:38", + "domain_auto_renew": "1", + "domain_epp_id": "LEG2175D-NORID", + "domain_registrant_id": "CA19777O", + "domain_tech_id": "GH295R", + "domain_auth_info": "XXXXXXXXXXXXXXX", + "domain_locked": "0", + "domain_dnssec": "0", + "domain_dnssec_data": null, + "domain_protected_email": null, + "zone_created": "2025-11-23 16:17:29", + "zone_updated": false, + "external_dns": "0", + "record_count": 4, + "zone_name_display": "example.com" + }, + { + "zone_id": "226", + "cust_id": "16030", + "order_id": "26114", + "zone_name": "example.org", + "zone_type": "NATIVE", + "zone_active": "1", + "zone_protected": "1", + "zone_is_registered": "1", + "domain_registrar": "norid", + "domain_status": "active", + "domain_registered_date": "2025-11-23 14:15:01", + "domain_expiry_date": "2026-11-23 14:15:01", + "domain_updated_date": "2025-11-23 15:15:02", + "domain_auto_renew": "1", + "domain_epp_id": "TEO218D-NORID", + "domain_registrant_id": "CA19774O", + "domain_tech_id": "GH295R", + "domain_auth_info": "XXXXXXXXXXXXXX", + "domain_locked": "0", + "domain_dnssec": "0", + "domain_dnssec_data": null, + "domain_protected_email": null, + "zone_created": "2025-11-23 15:13:27", + "zone_updated": false, + "external_dns": "0", + "record_count": 5, + "zone_name_display": "example.org" + }, + { + "zone_id": "229", + "cust_id": "16030", + "order_id": "26119", + "zone_name": "example.xn--zckzah", + "zone_type": "NATIVE", + "zone_active": "1", + "zone_protected": "1", + "zone_is_registered": "1", + "domain_registrar": "norid", + "domain_status": "active", + "domain_registered_date": "2014-12-01 12:40:48", + "domain_expiry_date": "2026-12-01 12:40:48", + "domain_updated_date": "2025-11-23 15:37:36", + "domain_auto_renew": "1", + "domain_epp_id": "DIT1003D-NORID", + "domain_registrant_id": "DCA822O", + "domain_tech_id": "GH295R", + "domain_auth_info": "XXXXXXXXXXXXXX", + "domain_locked": "0", + "domain_dnssec": "0", + "domain_dnssec_data": null, + "domain_protected_email": null, + "zone_created": "2025-11-23 16:37:15", + "zone_updated": false, + "external_dns": "0", + "record_count": 4, + "zone_name_display": "example.\u30C6\u30B9\u30C8" + } + ] +} diff --git a/providers/dns/gigahostno/internal/identity.go b/providers/dns/gigahostno/internal/identity.go new file mode 100644 index 000000000..262dfabdd --- /dev/null +++ b/providers/dns/gigahostno/internal/identity.go @@ -0,0 +1,122 @@ +package internal + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" + "github.com/pquerna/otp/totp" +) + +type token string + +const tokenKey token = "token" + +type Identifier struct { + username string + password string + Secret string + + BaseURL *url.URL + HTTPClient *http.Client +} + +func NewIdentifier(username, password, secret string) (*Identifier, error) { + if username == "" || password == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Identifier{ + username: username, + password: password, + Secret: secret, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Identifier) Authenticate(ctx context.Context) (*Token, error) { + endpoint := c.BaseURL.JoinPath("authenticate") + + auth := Auth{Username: c.username, Password: c.password} + + if c.Secret != "" { + tan, err := totp.GenerateCode(c.Secret, time.Now()) + if err != nil { + return nil, fmt.Errorf("generate TOTP: %w", err) + } + + auth.Code, err = strconv.Atoi(tan) + if err != nil { + return nil, fmt.Errorf("parse TOTP: %w", err) + } + } + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, auth) + if err != nil { + return nil, err + } + + var result APIResponse[*Token] + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result.Data, nil +} + +func (c *Identifier) do(req *http.Request, result any) error { + useragent.SetHeader(req.Header) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func WithContext(ctx context.Context, credential string) context.Context { + return context.WithValue(ctx, tokenKey, credential) +} + +func getToken(ctx context.Context) string { + credential, ok := ctx.Value(tokenKey).(string) + if !ok { + return "" + } + + return credential +} diff --git a/providers/dns/gigahostno/internal/identity_test.go b/providers/dns/gigahostno/internal/identity_test.go new file mode 100644 index 000000000..09d72746a --- /dev/null +++ b/providers/dns/gigahostno/internal/identity_test.go @@ -0,0 +1,108 @@ +package internal + +import ( + "context" + "net/http/httptest" + "net/url" + "testing" + "time" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupIdentifierClient(server *httptest.Server) (*Identifier, error) { + client, err := NewIdentifier("user", "secret", "") + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil +} + +func mockContext(t *testing.T) context.Context { + t.Helper() + + return context.WithValue(t.Context(), tokenKey, "secret") +} + +func TestIdentifier_Authenticate(t *testing.T) { + identifier := servermock.NewBuilder[*Identifier](setupIdentifierClient). + Route("POST /authenticate", + servermock.ResponseFromFixture("authenticate.json"), + servermock.CheckRequestJSONBodyFromFixture("authenticate-request.json")). + Build(t) + + token, err := identifier.Authenticate(context.Background()) + require.NoError(t, err) + + expected := &Token{ + Token: "secrettoken", + TokenExpire: 1577836800, + CustomerID: "16030", + ContactID: "15182", + CustomerName: "Cloudline AS", + ContactUsername: "test@example.com", + ContactAccessLevel: "admin", + CustomerAddress: "Grønland 14", + CustomerZipcode: "5918", + CustomerCity: "Frekhaug", + CustomerProvince: "Vestland", + GASecret: "ga_secret", + GAEnabled: "1", + VAT: 1, + } + + assert.Equal(t, expected, token) +} + +func TestToken_IsExpired(t *testing.T) { + testCases := []struct { + desc string + token *Token + assert assert.BoolAssertionFunc + }{ + { + desc: "nil", + assert: assert.True, + }, + { + desc: "empty", + token: &Token{}, + assert: assert.True, + }, + { + desc: "not expired", + token: &Token{ + TokenExpire: 65322892800, // 2040-01-01 + }, + assert: assert.False, + }, + { + desc: "now", + token: &Token{ + TokenExpire: time.Now().Unix(), + }, + assert: assert.True, + }, + { + desc: "now + 2 minutes", + token: &Token{ + TokenExpire: time.Now().Add(2 * time.Minute).Unix(), + }, + assert: assert.False, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + test.assert(t, test.token.IsExpired()) + }) + } +} diff --git a/providers/dns/gigahostno/internal/types.go b/providers/dns/gigahostno/internal/types.go new file mode 100644 index 000000000..cbb7b8b23 --- /dev/null +++ b/providers/dns/gigahostno/internal/types.go @@ -0,0 +1,83 @@ +package internal + +import ( + "fmt" + "time" +) + +type APIError struct { + Meta MetaData `json:"meta"` +} + +func (a *APIError) Error() string { + return fmt.Sprintf("%d: %s: %s", a.Meta.Status, a.Meta.StatusMessage, a.Meta.Message) +} + +type MetaData struct { + Status int `json:"status,omitempty"` + StatusMessage string `json:"status_message,omitempty"` + Maintenance bool `json:"maintenance"` + Message string `json:"message,omitempty"` +} + +type APIResponse[T any] struct { + Meta MetaData `json:"meta"` + Data T `json:"data,omitempty"` +} + +type Zone struct { + ID string `json:"zone_id,omitempty"` + Name string `json:"zone_name,omitempty"` + NameDisplay string `json:"zone_name_display,omitempty"` + Type string `json:"zone_type,omitempty"` + Active string `json:"zone_active,omitempty"` + Protected string `json:"zone_protected,omitempty"` + IsRegistered string `json:"zone_is_registered,omitempty"` + Updated bool `json:"zone_updated,omitempty"` + CustomerID string `json:"cust_id,omitempty"` + DomainRegistrar string `json:"domain_registrar,omitempty"` + DomainStatus string `json:"domain_status,omitempty"` + DomainExpiryDate string `json:"domain_expiry_date,omitempty"` + DomainAutoRenew string `json:"domain_auto_renew,omitempty"` + ExternalDNS string `json:"external_dns,omitempty"` + RecordCount int `json:"record_count,omitempty"` +} + +type Record struct { + ID string `json:"record_id,omitempty"` + Name string `json:"record_name,omitempty"` + Type string `json:"record_type,omitempty"` + Value string `json:"record_value,omitempty"` + TTL int `json:"record_ttl,omitempty"` +} + +type Auth struct { + Username string `json:"username"` + Password string `json:"password"` + Code int `json:"code,omitempty"` +} + +type Token struct { + Token string `json:"token,omitempty"` + TokenExpire int64 `json:"token_expire,omitempty"` + CustomerID string `json:"customer_id,omitempty"` + ContactID string `json:"contact_id,omitempty"` + CustomerName string `json:"customer_name,omitempty"` + ContactUsername string `json:"contact_username,omitempty"` + ContactAccessLevel string `json:"contact_access_level,omitempty"` + CustomerAddress string `json:"customer_address,omitempty"` + CustomerZipcode string `json:"customer_zipcode,omitempty"` + CustomerCity string `json:"customer_city,omitempty"` + CustomerProvince string `json:"customer_province,omitempty"` + GASecret string `json:"ga_secret,omitempty"` + GAEnabled string `json:"ga_enabled,omitempty"` + VAT int `json:"vat,omitempty"` +} + +func (t *Token) IsExpired() bool { + if t == nil { + return true + } + + return time.Now().UTC().Add(1 * time.Minute).After(time.Unix(t.TokenExpire, 0).UTC()) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index ff6ab0c28..8ecbb76cc 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -70,6 +70,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/gandiv5" "github.com/go-acme/lego/v4/providers/dns/gcloud" "github.com/go-acme/lego/v4/providers/dns/gcore" + "github.com/go-acme/lego/v4/providers/dns/gigahostno" "github.com/go-acme/lego/v4/providers/dns/glesys" "github.com/go-acme/lego/v4/providers/dns/godaddy" "github.com/go-acme/lego/v4/providers/dns/googledomains" @@ -307,6 +308,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return gcloud.NewDNSProvider() case "gcore": return gcore.NewDNSProvider() + case "gigahostno": + return gigahostno.NewDNSProvider() case "glesys": return glesys.NewDNSProvider() case "godaddy": From 56cb356ef2d95275cc0bc5c7fa599396a32d23bb Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 25 Nov 2025 19:29:47 +0100 Subject: [PATCH 218/298] edgeone: add zones mapping (#2728) --- cmd/zz_gen_cmd_dnshelp.go | 1 + docs/content/dns/zz_gen_edgeone.md | 1 + providers/dns/dnspod/dnspod.go | 2 +- providers/dns/edgeone/edgeone.go | 19 +++++++++++---- providers/dns/edgeone/edgeone.toml | 1 + providers/dns/edgeone/edgeone_test.go | 24 +++++++++++++++++-- providers/dns/edgeone/wrapper.go | 33 ++++++++++++++++----------- 7 files changed, 61 insertions(+), 20 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index deb93e6eb..2cba1b73a 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -1342,6 +1342,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(` - "EDGEONE_REGION": Region`) ew.writeln(` - "EDGEONE_SESSION_TOKEN": Access Key token`) ew.writeln(` - "EDGEONE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) + ew.writeln(` - "EDGEONE_ZONES_MAPPING": Mapping between DNS zones and site IDs. (ex: 'example.org:id1,example.com:id2')`) ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/edgeone`) diff --git a/docs/content/dns/zz_gen_edgeone.md b/docs/content/dns/zz_gen_edgeone.md index b7b5b1eec..227127d65 100644 --- a/docs/content/dns/zz_gen_edgeone.md +++ b/docs/content/dns/zz_gen_edgeone.md @@ -55,6 +55,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | `EDGEONE_REGION` | Region | | `EDGEONE_SESSION_TOKEN` | Access Key token | | `EDGEONE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | +| `EDGEONE_ZONES_MAPPING` | Mapping between DNS zones and site IDs. (ex: 'example.org:id1,example.com:id2') | 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" %}}). diff --git a/providers/dns/dnspod/dnspod.go b/providers/dns/dnspod/dnspod.go index c9376b956..52a873c7b 100644 --- a/providers/dns/dnspod/dnspod.go +++ b/providers/dns/dnspod/dnspod.go @@ -165,7 +165,7 @@ func (d *DNSProvider) getHostedZone(domain string) (string, string, error) { } if hostedZone.ID == "" || hostedZone.ID == "0" { - return "", "", fmt.Errorf("zone %s not found in dnspod for domain %s", authZone, domain) + return "", "", fmt.Errorf("zone %s not found for domain %s", authZone, domain) } return hostedZone.ID.String(), hostedZone.Name, nil diff --git a/providers/dns/edgeone/edgeone.go b/providers/dns/edgeone/edgeone.go index 3402122bb..509a75c77 100644 --- a/providers/dns/edgeone/edgeone.go +++ b/providers/dns/edgeone/edgeone.go @@ -26,6 +26,7 @@ const ( EnvSecretKey = envNamespace + "SECRET_KEY" EnvRegion = envNamespace + "REGION" EnvSessionToken = envNamespace + "SESSION_TOKEN" + EnvZonesMapping = envNamespace + "ZONES_MAPPING" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -40,6 +41,8 @@ type Config struct { Region string SessionToken string + ZonesMapping map[string]string + PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -78,6 +81,14 @@ func NewDNSProvider() (*DNSProvider, error) { config.Region = env.GetOrDefaultString(EnvRegion, "") config.SessionToken = env.GetOrDefaultString(EnvSessionToken, "") + mapping := env.GetOrDefaultString(EnvZonesMapping, "") + if mapping != "" { + config.ZonesMapping, err = env.ParsePairs(mapping) + if err != nil { + return nil, fmt.Errorf("edgeone: zones mapping: %w", err) + } + } + return NewDNSProviderConfig(config) } @@ -121,7 +132,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { ctx := context.Background() - zone, err := d.getHostedZone(ctx, info.EffectiveFQDN) + zoneID, err := d.getHostedZoneID(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("edgeone: failed to get hosted zone: %w", err) } @@ -133,7 +144,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { request := teo.NewCreateDnsRecordRequest() request.Name = ptr.Pointer(punnyCoded) - request.ZoneId = zone.ZoneId + request.ZoneId = zoneID request.Type = ptr.Pointer("TXT") request.Content = ptr.Pointer(info.Value) request.TTL = ptr.Pointer(int64(d.config.TTL)) @@ -156,7 +167,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { ctx := context.Background() - zone, err := d.getHostedZone(ctx, info.EffectiveFQDN) + zoneID, err := d.getHostedZoneID(ctx, info.EffectiveFQDN) if err != nil { return fmt.Errorf("edgeone: failed to get hosted zone: %w", err) } @@ -171,7 +182,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } request := teo.NewDeleteDnsRecordsRequest() - request.ZoneId = zone.ZoneId + request.ZoneId = zoneID request.RecordIds = []*string{recordID} _, err = teo.DeleteDnsRecordsWithContext(ctx, d.client, request) diff --git a/providers/dns/edgeone/edgeone.toml b/providers/dns/edgeone/edgeone.toml index 120756da6..a33af75b2 100644 --- a/providers/dns/edgeone/edgeone.toml +++ b/providers/dns/edgeone/edgeone.toml @@ -17,6 +17,7 @@ lego --email you@example.com --dns edgeone -d '*.example.com' -d example.com run [Configuration.Additional] EDGEONE_SESSION_TOKEN = "Access Key token" EDGEONE_REGION = "Region" + EDGEONE_ZONES_MAPPING = "Mapping between DNS zones and site IDs. (ex: 'example.org:id1,example.com:id2')" EDGEONE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 30)" EDGEONE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 1200)" EDGEONE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" diff --git a/providers/dns/edgeone/edgeone_test.go b/providers/dns/edgeone/edgeone_test.go index 1c92118dc..7bd4f6f6d 100644 --- a/providers/dns/edgeone/edgeone_test.go +++ b/providers/dns/edgeone/edgeone_test.go @@ -9,8 +9,11 @@ import ( const envDomain = envNamespace + "DOMAIN" -var envTest = tester.NewEnvTest(EnvSecretID, EnvSecretKey). - WithDomain(envDomain) +var envTest = tester.NewEnvTest( + EnvSecretID, + EnvSecretKey, + EnvZonesMapping, +).WithDomain(envDomain) func TestNewDNSProvider(t *testing.T) { testCases := []struct { @@ -25,6 +28,14 @@ func TestNewDNSProvider(t *testing.T) { EnvSecretKey: "456", }, }, + { + desc: "success with zones mapping", + envVars: map[string]string{ + EnvSecretID: "123", + EnvSecretKey: "456", + EnvZonesMapping: "example.org:id1,example.com:id2", + }, + }, { desc: "missing credentials", envVars: map[string]string{ @@ -49,6 +60,15 @@ func TestNewDNSProvider(t *testing.T) { }, expected: "edgeone: some credentials information are missing: EDGEONE_SECRET_KEY", }, + { + desc: "invalid mapping", + envVars: map[string]string{ + EnvSecretID: "123", + EnvSecretKey: "456", + EnvZonesMapping: "example.org:id1,example.com", + }, + expected: "edgeone: zones mapping: incorrect pair: example.com", + }, } for _, test := range testCases { diff --git a/providers/dns/edgeone/wrapper.go b/providers/dns/edgeone/wrapper.go index c3e9d965b..53fae9427 100644 --- a/providers/dns/edgeone/wrapper.go +++ b/providers/dns/edgeone/wrapper.go @@ -9,10 +9,22 @@ import ( teo "github.com/go-acme/tencentedgdeone/v20220901" ) -func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*teo.Zone, error) { +func (d *DNSProvider) getHostedZoneID(ctx context.Context, domain string) (*string, error) { + authZone, err := dns01.FindZoneByFqdn(domain) + if err != nil { + return nil, fmt.Errorf("could not find zone: %w", err) + } + + if d.config.ZonesMapping != nil { + zoneID, ok := d.config.ZonesMapping[authZone] + if ok { + return ptr.Pointer(zoneID), nil + } + } + request := teo.NewDescribeZonesRequest() - var domains []*teo.Zone + var zones []*teo.Zone for { response, err := teo.DescribeZonesWithContext(ctx, d.client, request) @@ -20,23 +32,18 @@ func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*teo.Zo return nil, fmt.Errorf("API call failed: %w", err) } - domains = append(domains, response.Response.Zones...) + zones = append(zones, response.Response.Zones...) - if int64(len(domains)) >= ptr.Deref(response.Response.TotalCount) { + if int64(len(zones)) >= ptr.Deref(response.Response.TotalCount) { break } - request.Offset = ptr.Pointer(int64(len(domains))) - } - - authZone, err := dns01.FindZoneByFqdn(domain) - if err != nil { - return nil, fmt.Errorf("could not find zone: %w", err) + request.Offset = ptr.Pointer(int64(len(zones))) } var hostedZone *teo.Zone - for _, zone := range domains { + for _, zone := range zones { unfqdn := dns01.UnFqdn(authZone) if ptr.Deref(zone.ZoneName) == unfqdn { hostedZone = zone @@ -44,8 +51,8 @@ func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*teo.Zo } if hostedZone == nil { - return nil, fmt.Errorf("zone %s not found in dnspod for domain %s", authZone, domain) + return nil, fmt.Errorf("zone %s not found for domain %s", authZone, domain) } - return hostedZone, nil + return hostedZone.ZoneId, nil } From ad6adbffd4eddb2e15a250af437850a7ec11551c Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 25 Nov 2025 19:30:22 +0100 Subject: [PATCH 219/298] tests: fix flaky test (#2729) --- providers/dns/oraclecloud/oraclecloud_test.go | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/providers/dns/oraclecloud/oraclecloud_test.go b/providers/dns/oraclecloud/oraclecloud_test.go index c646e90f2..74ee06eac 100644 --- a/providers/dns/oraclecloud/oraclecloud_test.go +++ b/providers/dns/oraclecloud/oraclecloud_test.go @@ -61,7 +61,7 @@ func TestNewDNSProvider(t *testing.T) { { desc: "success file", envVars: map[string]string{ - EnvPrivKeyFile: mustGeneratePrivateKeyFile("secret1"), + EnvPrivKeyFile: mustGeneratePrivateKeyFile(t, "secret1"), EnvPrivKeyPass: "secret1", EnvTenancyOCID: "ocid1.tenancy.oc1..secret", EnvUserOCID: "ocid1.user.oc1..secret", @@ -383,21 +383,21 @@ func mustGeneratePrivateKey(pwd string) string { return base64.StdEncoding.EncodeToString(pem.EncodeToMemory(block)) } -func mustGeneratePrivateKeyFile(pwd string) string { - block, err := generatePrivateKey(pwd) - if err != nil { - panic(err) - } +func mustGeneratePrivateKeyFile(t *testing.T, pwd string) string { + t.Helper() - file, err := os.CreateTemp("", "lego_oci_*.pem") - if err != nil { - panic(err) - } + block, err := generatePrivateKey(pwd) + require.NoError(t, err) + + file, err := os.CreateTemp(t.TempDir(), "lego_oci_*.pem") + require.NoError(t, err) + + defer func() { + _ = file.Close() + }() err = pem.Encode(file, block) - if err != nil { - panic(err) - } + require.NoError(t, err) return file.Name() } From 42fb4346e258b9c3361394341f6e1adf6a612317 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 27 Nov 2025 20:40:23 +0100 Subject: [PATCH 220/298] chore: update dependencies (#2733) --- go.mod | 103 ++++++------- go.sum | 214 ++++++++++++++-------------- providers/dns/gcloud/googlecloud.go | 2 +- providers/dns/inwx/inwx.go | 4 +- 4 files changed, 162 insertions(+), 161 deletions(-) diff --git a/go.mod b/go.mod index 702da550f..f68c2c9eb 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.24.0 require ( cloud.google.com/go/compute/metadata v0.9.0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 @@ -18,21 +18,21 @@ require ( github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 github.com/alibabacloud-go/tea v1.3.13 github.com/aliyun/credentials-go v1.4.7 - github.com/aws/aws-sdk-go-v2 v1.39.4 - github.com/aws/aws-sdk-go-v2/config v1.31.15 - github.com/aws/aws-sdk-go-v2/credentials v1.18.19 - github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.2 - github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1 - github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 - github.com/aziontech/azionapi-go-sdk v0.143.0 - github.com/baidubce/bce-sdk-go v0.9.250 + github.com/aws/aws-sdk-go-v2 v1.40.0 + github.com/aws/aws-sdk-go-v2/config v1.32.2 + github.com/aws/aws-sdk-go-v2/credentials v1.19.2 + github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.8 + github.com/aws/aws-sdk-go-v2/service/route53 v1.61.0 + github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 + github.com/aziontech/azionapi-go-sdk v0.144.0 + github.com/baidubce/bce-sdk-go v0.9.252 github.com/cenkalti/backoff/v5 v5.0.3 github.com/dnsimple/dnsimple-go/v4 v4.0.0 - github.com/exoscale/egoscale/v3 v3.1.27 - github.com/go-acme/alidns-20150109/v4 v4.6.1 + github.com/exoscale/egoscale/v3 v3.1.31 + github.com/go-acme/alidns-20150109/v4 v4.7.0 github.com/go-acme/esa-20240910/v2 v2.40.1 - github.com/go-acme/tencentclouddnspod v1.1.10 + github.com/go-acme/tencentclouddnspod v1.1.25 github.com/go-acme/tencentedgdeone v1.1.48 github.com/go-jose/go-jose/v4 v4.1.3 github.com/go-viper/mapstructure/v2 v2.4.0 @@ -42,12 +42,12 @@ require ( github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/go-version v1.7.0 - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.173 + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.178 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 github.com/labbsr0x/bindman-dns-webhook v1.0.2 github.com/ldez/grignotin v0.10.1 - github.com/linode/linodego v1.60.0 + github.com/linode/linodego v1.61.0 github.com/liquidweb/liquidweb-go v1.6.4 github.com/mattn/go-isatty v0.0.20 github.com/miekg/dns v1.1.68 @@ -59,12 +59,12 @@ require ( github.com/nrdcg/dnspod-go v0.4.0 github.com/nrdcg/freemyip v0.3.0 github.com/nrdcg/goacmedns v0.2.0 - github.com/nrdcg/goinwx v0.11.0 + github.com/nrdcg/goinwx v0.12.0 github.com/nrdcg/mailinabox v0.3.0 github.com/nrdcg/namesilo v0.5.0 github.com/nrdcg/nodion v0.1.0 - github.com/nrdcg/oci-go-sdk/common/v1065 v1065.103.0 - github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.103.0 + github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.0 + github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.0 github.com/nrdcg/porkbun v0.4.0 github.com/nrdcg/vegadns v0.3.0 github.com/nzdjb/go-metaname v1.0.0 @@ -73,29 +73,29 @@ require ( 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.3.3 - github.com/sacloud/iaas-api-go v1.20.0 + github.com/sacloud/iaas-api-go v1.22.0 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35 github.com/selectel/domains-go v1.1.0 github.com/selectel/go-selvpcclient/v4 v4.1.0 github.com/softlayer/softlayer-go v1.2.1 github.com/stretchr/testify v1.11.1 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.3 github.com/transip/gotransip/v6 v6.26.1 github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 github.com/urfave/cli/v2 v2.27.7 github.com/vinyldns/go-vinyldns v0.9.16 - github.com/volcengine/volc-sdk-golang v1.0.224 - github.com/vultr/govultr/v3 v3.24.0 - github.com/yandex-cloud/go-genproto v0.34.0 - github.com/yandex-cloud/go-sdk/services/dns v0.0.16 - github.com/yandex-cloud/go-sdk/v2 v2.24.0 - golang.org/x/crypto v0.43.0 - golang.org/x/net v0.46.0 - golang.org/x/oauth2 v0.32.0 - golang.org/x/text v0.30.0 + github.com/volcengine/volc-sdk-golang v1.0.229 + github.com/vultr/govultr/v3 v3.25.0 + github.com/yandex-cloud/go-genproto v0.38.0 + github.com/yandex-cloud/go-sdk/services/dns v0.0.20 + github.com/yandex-cloud/go-sdk/v2 v2.28.0 + golang.org/x/crypto v0.45.0 + golang.org/x/net v0.47.0 + golang.org/x/oauth2 v0.33.0 + golang.org/x/text v0.31.0 golang.org/x/time v0.14.0 - google.golang.org/api v0.254.0 - gopkg.in/ns1/ns1-go.v2 v2.15.1 + google.golang.org/api v0.256.0 + gopkg.in/ns1/ns1-go.v2 v2.15.2 gopkg.in/yaml.v2 v2.4.0 software.sslmate.com/src/go-pkcs12 v0.6.0 ) @@ -111,24 +111,25 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect github.com/alibabacloud-go/debug v1.0.1 // indirect github.com/alibabacloud-go/openapi-util v0.1.1 // indirect github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 // indirect - github.com/aws/smithy-go v1.23.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.14 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 // indirect + github.com/aws/smithy-go v1.23.2 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -157,7 +158,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect @@ -210,13 +211,13 @@ require ( go.uber.org/ratelimit v0.3.1 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect - golang.org/x/mod v0.28.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.37.0 // indirect - golang.org/x/tools v0.37.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/tools v0.38.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect google.golang.org/grpc v1.76.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 337db59b3..af9e95e02 100644 --- a/go.sum +++ b/go.sum @@ -42,10 +42,10 @@ github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYs github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 h1:KpMC6LFL7mqpExyMC9jVOYRiVhLmamjeZfRsUpB7l4s= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= @@ -85,8 +85,8 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 h1:XkkQbfMyuH2jTSjQjSoihryI8GINRcs4xp8lNawg0FI= -github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= @@ -121,7 +121,6 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= -github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.12/go.mod h1:f2wDpbM7hK9SvLIH09zSKVU1TsyemUNOqErMscMMl7c= github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 h1:Q00FU3H94Ts0ZIHDmY+fYGgB7dV9D/YX6FGsgorQPgw= github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE= github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= @@ -144,7 +143,6 @@ github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/Ke github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= -github.com/alibabacloud-go/tea v1.3.12/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= github.com/alibabacloud-go/tea v1.3.13 h1:WhGy6LIXaMbBM6VBYcsDCz6K/TPsT1Ri2hPmmZffZ94= github.com/alibabacloud-go/tea v1.3.13/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= @@ -170,52 +168,54 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.39.4 h1:qTsQKcdQPHnfGYBBs+Btl8QwxJeoWcOcPcixK90mRhg= -github.com/aws/aws-sdk-go-v2 v1.39.4/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 h1:t9yYsydLYNBk9cJ73rgPhPWqOh/52fcWDQB5b1JsKSY= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2/go.mod h1:IusfVNTmiSN3t4rhxWFaBAqn+mcNdwKtPcV16eYdgko= -github.com/aws/aws-sdk-go-v2/config v1.31.15 h1:gE3M4xuNXfC/9bG4hyowGm/35uQTi7bUKeYs5e/6uvU= -github.com/aws/aws-sdk-go-v2/config v1.31.15/go.mod h1:HvnvGJoE2I95KAIW8kkWVPJ4XhdrlvwJpV6pEzFQa8o= -github.com/aws/aws-sdk-go-v2/credentials v1.18.19 h1:Jc1zzwkSY1QbkEcLujwqRTXOdvW8ppND3jRBb/VhBQc= -github.com/aws/aws-sdk-go-v2/credentials v1.18.19/go.mod h1:DIfQ9fAk5H0pGtnqfqkbSIzky82qYnGvh06ASQXXg6A= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 h1:X7X4YKb+c0rkI6d4uJ5tEMxXgCZ+jZ/D6mvkno8c8Uw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11/go.mod h1:EqM6vPZQsZHYvC4Cai35UDg/f5NCEU+vp0WfbVqVcZc= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 h1:7AANQZkF3ihM8fbdftpjhken0TP9sBzFbV/Ze/Y4HXA= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11/go.mod h1:NTF4QCGkm6fzVwncpkFQqoquQyOolcyXfbpC98urj+c= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 h1:ShdtWUZT37LCAA4Mw2kJAJtzaszfSHFb5n25sdcv4YE= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11/go.mod h1:7bUb2sSr2MZ3M/N+VyETLTQtInemHXb/Fl3s8CLzm0Y= +github.com/aws/aws-sdk-go-v2 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc= +github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 h1:DHctwEM8P8iTXFxC/QK0MRjwEpWQeM9yzidCRjldUz0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3/go.mod h1:xdCzcZEtnSTKVDOmUZs4l/j3pSV6rpo1WXl5ugNsL8Y= +github.com/aws/aws-sdk-go-v2/config v1.32.2 h1:4liUsdEpUUPZs5WVapsJLx5NPmQhQdez7nYFcovrytk= +github.com/aws/aws-sdk-go-v2/config v1.32.2/go.mod h1:l0hs06IFz1eCT+jTacU/qZtC33nvcnLADAPL/XyrkZI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.2 h1:qZry8VUyTK4VIo5aEdUcBjPZHL2v4FyQ3QEOaWcFLu4= +github.com/aws/aws-sdk-go-v2/credentials v1.19.2/go.mod h1:YUqm5a1/kBnoK+/NY5WEiMocZihKSo15/tJdmdXnM5g= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14/go.mod h1:VymhrMJUWs69D8u0/lZ7jSB6WgaG/NqHi3gX0aYf6U0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs0ESzr1XCOU2KXJCWcq3E2vfjY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11 h1:bKgSxk1TW//00PGQqYmrq83c+2myGidEclp+t9pPqVI= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11/go.mod h1:vrPYCQ6rFHL8jzQA8ppu3gWX18zxjLIDGTeqDxkBmSI= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.14 h1:ITi7qiDSv/mSGDSWNpZ4k4Ve0DQR6Ug2SJQ8zEHoDXg= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.14/go.mod h1:k1xtME53H1b6YpZt74YmwlONMWf4ecM+lut1WQLAF/U= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 h1:xtuxji5CS0JknaXoACOunXOYOQzgfTvGAc9s2QdCJA4= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2/go.mod h1:zxwi0DIR0rcRcgdbl7E2MSOvxDyyXGBlScvBkARFaLQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.2 h1:DGFpGybmutVsCuF6vSuLZ25Vh55E3VmsnJmFfjeBx4M= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.2/go.mod h1:hm/wU1HDvXCFEDzOLorQnZZ/CVvPXvWEmHMSmqgQRuA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 h1:GpMf3z2KJa4RnJ0ew3Hac+hRFYLZ9DDjfgXjuW+pB54= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11/go.mod h1:6MZP3ZI4QQsgUCFTwMZA2V0sEriNQ8k2hmoHF3qjimQ= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11 h1:weapBOuuFIBEQ9OX/NVW3tFQCvSutyjZYk/ga5jDLPo= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11/go.mod h1:3C1gN4FmIVLwYSh8etngUS+f1viY6nLCDVtZmrFbDy0= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.2 h1:pr1dQ9vamhAf2mYOgiRRC/w9Ht4POFhy6+xXw7hOqwY= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.2/go.mod h1:A4Ch93K7Wam4Qe0Wl0XbPgcgoL5KIJtFIe7wHw6OPWE= -github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1 h1:KuoA/cmy/yK8n9v/d6WH36cZwGxFOrn0TmZ4lNN3MKQ= -github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1/go.mod h1:BymbICXBfXQHO6i+yTBhocA9a6DM0uMDQqYelqa9wzs= -github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0 h1:JbCUlVDEjmhpvpIgXP9QN+/jW61WWWj99cGmxMC49hM= -github.com/aws/aws-sdk-go-v2/service/s3 v1.89.0/go.mod h1:UHKgcRSx8PVtvsc1Poxb/Co3PD3wL7P+f49P0+cWtuY= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 h1:M5nimZmugcZUO9wG7iVtROxPhiqyZX6ejS1lxlDPbTU= -github.com/aws/aws-sdk-go-v2/service/sso v1.29.8/go.mod h1:mbef/pgKhtKRwrigPPs7SSSKZgytzP8PQ6P6JAAdqyM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 h1:S5GuJZpYxE0lKeMHKn+BRTz6PTFpgThyJ+5mYfux7BM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3/go.mod h1:X4OF+BTd7HIb3L+tc4UlWHVrpgwZZIVENU15pRDVTI0= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 h1:Ekml5vGg6sHSZLZJQJagefnVe6PmqC2oiRkBq4F7fU0= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.9/go.mod h1:/e15V+o1zFHWdH3u7lpI3rVBcxszktIKuHKCY2/py+k= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5 h1:Hjkh7kE6D81PgrHlE/m9gx+4TyyeLHuY8xJs7yXN5C4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5/go.mod h1:nPRXgyCfAurhyaTMoBMwRBYBhaHI4lNPAnJmjM0Tslc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 h1:FIouAnCE46kyYqyhs0XEBDFFSREtdnr8HQuLPQPLCrY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 h1:FzQE21lNtUor0Fb7QNgnEyiRCBlolLTX/Z1j65S7teM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14/go.mod h1:s1ydyWG9pm3ZwmmYN21HKyG9WzAZhYVW85wMHs5FV6w= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.8 h1:jhwva7OKpYXrTQmCG4L7lF2FvB2irs1oRyGAwmQ4lmA= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.8/go.mod h1:x+omzRoqYYFX+H8/va+Gt2Yg4xGaHZMRowr77Y/UGIA= +github.com/aws/aws-sdk-go-v2/service/route53 v1.61.0 h1:W3+0Cbc9awFBr9Yt7nFUkvB4N4e7vVIGtKD1qDttXn4= +github.com/aws/aws-sdk-go-v2/service/route53 v1.61.0/go.mod h1:Wa3q5R2uwIfIL3HZH+vG1/P9y7CjjfzTgcz5IWXlsZs= +github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1 h1:OgQy/+0+Kc3khtqiEOk23xQAglXi3Tj0y5doOxbi5tg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1/go.mod h1:wYNqY3L02Z3IgRYxOBPH9I1zD9Cjh9hI5QOy/eOjQvw= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 h1:MxMBdKTYBjPQChlJhi4qlEueqB1p1KcbTEa7tD5aqPs= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.2/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 h1:ksUT5KtgpZd3SAiFJNJ0AFEJVva3gjBmN7eXUZjzUwQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.5/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 h1:GtsxyiF3Nd3JahRBJbxLCCdYW9ltGQYrFWg8XdkGDd8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 h1:a5UTtD4mHBU3t0o6aHQZFJTNKVfxFWfPX7J0Lr7G+uY= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.2/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= -github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= -github.com/aziontech/azionapi-go-sdk v0.143.0 h1:4eEBlYT10prgeCVTNR9FIc7f59Crbl2zrH1a4D1BUqU= -github.com/aziontech/azionapi-go-sdk v0.143.0/go.mod h1:cA5DY/VP4X5Eu11LpQNzNn83ziKjja7QVMIl4J45feA= -github.com/baidubce/bce-sdk-go v0.9.250 h1:fnvV5clsNCAP6pCauj0eNaUnoLVmjQGnco7rcMqp984= -github.com/baidubce/bce-sdk-go v0.9.250/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM= +github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/aziontech/azionapi-go-sdk v0.144.0 h1:T+/w18o+FCiZsk3Z0ACBVVe7c/5EGLG15S3P8JfuPfo= +github.com/aziontech/azionapi-go-sdk v0.144.0/go.mod h1:OKxP/R0iVXnJJakYwMhh2BGAXnud8Ruy55Ak9ANuWoU= +github.com/baidubce/bce-sdk-go v0.9.252 h1:TONS/utgfEkDjvHllVZFBrTsjsNhk51rhWuj3ppcL4s= +github.com/baidubce/bce-sdk-go v0.9.252/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -284,8 +284,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/exoscale/egoscale/v3 v3.1.27 h1:vKdWZG8QFDc7rY7lCfcuudO+ovyp5psYjFwKVqmkhCE= -github.com/exoscale/egoscale/v3 v3.1.27/go.mod h1:0iY8OxgHJCS5TKqDNhwOW95JBKCnBZl3YGU4Yt+NqkU= +github.com/exoscale/egoscale/v3 v3.1.31 h1:/dySEUSAxU+hlAS/eLxAoY8ZYmtOtaoL1P+lDwH7ojY= +github.com/exoscale/egoscale/v3 v3.1.31/go.mod h1:0iY8OxgHJCS5TKqDNhwOW95JBKCnBZl3YGU4Yt+NqkU= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= @@ -312,12 +312,12 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-acme/alidns-20150109/v4 v4.6.1 h1:Dch3aWRcw4U62+jKPjPQN3iW3TPvgIywATbvHzojXeo= -github.com/go-acme/alidns-20150109/v4 v4.6.1/go.mod h1:RBcqBA5IvUWtlpjx6dC6EkPVyBNLQ+mR18XoaP38BFY= +github.com/go-acme/alidns-20150109/v4 v4.7.0 h1:PqJ/wR0JTpL4v0Owu1uM7bPQ1Yww0eQLAuuSdLjjQaQ= +github.com/go-acme/alidns-20150109/v4 v4.7.0/go.mod h1:btQvB6xZoN6ykKB74cPhiR+uvhrEE2AFVXm6RDmCHm0= github.com/go-acme/esa-20240910/v2 v2.40.1 h1:pog3UlF5d+3LPoo1L8u8PqB189recIXX8T7pGoEz18A= github.com/go-acme/esa-20240910/v2 v2.40.1/go.mod h1:ZYdN9EN9ikn26SNapxCVjZ65pHT/1qm4fzuJ7QGVX6g= -github.com/go-acme/tencentclouddnspod v1.1.10 h1:ERVJ4mc3cT4Nb3+n6H/c1AwZnChGBqLoymE0NVYscKI= -github.com/go-acme/tencentclouddnspod v1.1.10/go.mod h1:Bo/0YQJ/99FM+44HmCQkByuptX1tJsJ9V14MGV/2Qco= +github.com/go-acme/tencentclouddnspod v1.1.25 h1:7H3ZKshkaHzCXfRpAHVB5nvxeDDl2XLeNZfrNHiZj/s= +github.com/go-acme/tencentclouddnspod v1.1.25/go.mod h1:XXfzp0AYV7UAUsHKT6R0KAUJFhqAUXmWGF07Elpa5cE= github.com/go-acme/tencentedgdeone v1.1.48 h1:WLyLBsRVhSLFmtbEFXk0naLODSQn7X6J0Fc/qR8xVUk= github.com/go-acme/tencentedgdeone v1.1.48/go.mod h1:mu6tA+bPhlSd+CKUfzRikE0mfxmTlBI6dVTn9LY9dRI= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= @@ -461,8 +461,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= -github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= @@ -531,8 +531,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.173 h1:Y4ixGadyrK9xHw6Z+cyiiME3SBXepEcUoiT+B8C5FoQ= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.173/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.178 h1:eNVkjzdPMgM2qih9aECiFUI8S9zgpOwXxeBPAwQqtvU= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.178/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -606,8 +606,8 @@ github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufp github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/linode/linodego v1.60.0 h1:SgsebJFRCi+lSmYy+C40wmKZeJllGGm+W12Qw4+yVdI= -github.com/linode/linodego v1.60.0/go.mod h1:1+Bt0oTz5rBnDOJbGhccxn7LYVytXTIIfAy7QYmijDs= +github.com/linode/linodego v1.61.0 h1:9g20NWl+/SbhDFj6X5EOZXtM2hBm1Mx8I9h8+F3l1LM= +github.com/linode/linodego v1.61.0/go.mod h1:64o30geLNwR0NeYh5HM/WrVCBXcSqkKnRK3x9xoRuJI= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= @@ -697,18 +697,18 @@ github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc= github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM= github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0= github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg= -github.com/nrdcg/goinwx v0.11.0 h1:GER0SE3POub7rxARt3Y3jRy1OON1hwF1LRxHz5xsFBw= -github.com/nrdcg/goinwx v0.11.0/go.mod h1:0BXSC0FxVtU4aTjX0Zw3x0DK32tjugLzeNIAGtwXvPQ= +github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4= +github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0= github.com/nrdcg/mailinabox v0.3.0 h1:PHkC1elKXKAjEvdx2HHFMgcEGZFqudAl7aU3L2JDhM4= github.com/nrdcg/mailinabox v0.3.0/go.mod h1:1eFIGcM4lI+AfFOUpbs548SFGz1ZWoMOGbECBmkghw4= github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE= github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.103.0 h1:GPwwX9GFIBjV4u1M3Cr8eKCP6drW01IsfQSDIz6SUk8= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.103.0/go.mod h1:SfDIKzNQ5AGNMMOA3LGqSPnn63F6Gc4E4bsKArqymvg= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.103.0 h1:MjHla6lf1jpjGXORLpzMeo/tSmx0ejmjMjdjTByaDGY= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.103.0/go.mod h1:o1/kMADX0SlB4hJjWtcs3M6VIUOGR78yhPyiBv6oBkk= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.0 h1:bppmFqrJ87U4gWilemAW9oa4Qepf2JBTK/mPgaZLP2A= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.0/go.mod h1:SfDIKzNQ5AGNMMOA3LGqSPnn63F6Gc4E4bsKArqymvg= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.0 h1:IHPZs4Mo/lxyo+gYB+baheb2kGmHtNGQk2DKPDHqPjA= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.0/go.mod h1:yELd0uJLiIyv9sGIh5ZRCHEB1B2QFNURWkQIMqb3ZwE= github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= github.com/nrdcg/vegadns v0.3.0 h1:11FQMw7xVIRUWO9o5+Z/5YZhmPWlm4oxUUH3F6EVqQU= @@ -816,8 +816,8 @@ github.com/sacloud/api-client-go v0.3.3 h1:ZpSAyGpITA8UFO3Hq4qMHZLGuNI1FgxAxo4sq github.com/sacloud/api-client-go v0.3.3/go.mod h1:0p3ukcWYXRCc2AUWTl1aA+3sXLvurvvDqhRaLZRLBwo= github.com/sacloud/go-http v0.1.9 h1:Xa5PY8/pb7XWhwG9nAeXSrYXPbtfBWqawgzxD5co3VE= github.com/sacloud/go-http v0.1.9/go.mod h1:DpDG+MSyxYaBwPJ7l3aKLMzwYdTVtC5Bo63HActcgoE= -github.com/sacloud/iaas-api-go v1.20.0 h1:L4TfAzoFSwxrD3QXX8UxJa2o+GZrP9b863K+voTy3tQ= -github.com/sacloud/iaas-api-go v1.20.0/go.mod h1:XV995RM1I7k5AHb7UZrCVyDF/8bZXDxa+uk1EXoj/Zs= +github.com/sacloud/iaas-api-go v1.22.0 h1:nvLQNuxcfxILvoxA6WcnTjU9A8yv8dPI1OSYHAPxBJk= +github.com/sacloud/iaas-api-go v1.22.0/go.mod h1:PLcolyFlby/0ExZTOdUf9xzhkEMBuVzORadXDNN21no= github.com/sacloud/packages-go v0.0.11 h1:hrRWLmfPM9w7GBs6xb5/ue6pEMl8t1UuDKyR/KfteHo= github.com/sacloud/packages-go v0.0.11/go.mod h1:XNF5MCTWcHo9NiqWnYctVbASSSZR3ZOmmQORIzcurJ8= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= @@ -903,9 +903,10 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.10/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48 h1:aoRUrz2ag27jQWcOKHgeE+toSti6/xPqHKMLruOtJuM= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.25/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.3 h1:r05ohLc0LVEpiEQeOJ5QwCiKk6XM9kjTca6+UAbNR/8= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.3/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= @@ -920,10 +921,10 @@ github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= -github.com/volcengine/volc-sdk-golang v1.0.224 h1:k9Vtg64tQAgFTOGWzhyL0b0axuTuExXbLNVlslWlBZI= -github.com/volcengine/volc-sdk-golang v1.0.224/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= -github.com/vultr/govultr/v3 v3.24.0 h1:fTTTj0VBve+Miy+wGhlb90M2NMDfpGFi6Frlj3HVy6M= -github.com/vultr/govultr/v3 v3.24.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= +github.com/volcengine/volc-sdk-golang v1.0.229 h1:gOkDltTS6Fta8OyfYrbeY9bqCHHyiJuGYNJpR5MR+Fo= +github.com/volcengine/volc-sdk-golang v1.0.229/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= +github.com/vultr/govultr/v3 v3.25.0 h1:rS8/Vdy8HlHArwmD4MtLY+hbbpYAbcnZueZrE6b0oUg= +github.com/vultr/govultr/v3 v3.25.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= @@ -932,12 +933,12 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yandex-cloud/go-genproto v0.34.0 h1:qhTJpPxOTKQbV44rIqoZSdzxDtZW27fkFjAcipEy8Zs= -github.com/yandex-cloud/go-genproto v0.34.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= -github.com/yandex-cloud/go-sdk/services/dns v0.0.16 h1:0UYrBlQjTO2ct5xcSx6rqkQB95wRBPMVwxfqLQD1sUE= -github.com/yandex-cloud/go-sdk/services/dns v0.0.16/go.mod h1:HlS3aIAdYEmJu2Ska/nzpcuv9LLVSMMXKGhzyLQwf5s= -github.com/yandex-cloud/go-sdk/v2 v2.24.0 h1:G53N/RB5g/jw2xNN0egspnwd2ByHA1OVH6wbTx/tIlo= -github.com/yandex-cloud/go-sdk/v2 v2.24.0/go.mod h1:ZRdpyOig8c/W3bNhwvkeXWWPeDScd9nmXv4AJzKvOsk= +github.com/yandex-cloud/go-genproto v0.38.0 h1:uB3btG7mLOnu53ehYtRARCk04+80sBpxDrSkP3qC6G8= +github.com/yandex-cloud/go-genproto v0.38.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-sdk/services/dns v0.0.20 h1:xHBRa+IIYpTgMbTbmZf7aEKIqrJMcZGIF8ea4XIyLX0= +github.com/yandex-cloud/go-sdk/services/dns v0.0.20/go.mod h1:8nYQULLJbbe51qdBY7Ay5v8wtDgdH7nHCMZs4XkwzLg= +github.com/yandex-cloud/go-sdk/v2 v2.28.0 h1:KDOrN75xokZBYbgjq6Pjvo+hEpu32xFhErtomLBML5s= +github.com/yandex-cloud/go-sdk/v2 v2.28.0/go.mod h1:6vmAhqoCVYSJEb5OuhHUqIdxDy2b9uUXp1e5sqMhTmo= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= @@ -1029,8 +1030,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1074,8 +1075,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= -golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1133,17 +1134,16 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= -golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1160,8 +1160,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1247,8 +1247,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1263,8 +1263,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= -golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1283,8 +1283,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1350,8 +1350,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= -golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1380,8 +1380,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.254.0 h1:jl3XrGj7lRjnlUvZAbAdhINTLbsg5dbjmR90+pTQvt4= -google.golang.org/api v0.254.0/go.mod h1:5BkSURm3D9kAqjGvBNgf0EcbX6Rnrf6UArKkwBzAyqQ= +google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= +google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1424,8 +1424,8 @@ google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuO google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1477,8 +1477,8 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/ns1/ns1-go.v2 v2.15.1 h1:8rri2TzAPYcVbBGXn48+dz1Xg30PzHfZ4k8A9JOS0Z0= -gopkg.in/ns1/ns1-go.v2 v2.15.1/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= +gopkg.in/ns1/ns1-go.v2 v2.15.2 h1:aBVyKeEH3rBFWwX72xPPjEuRL4+Lp5P9GlAcrzu0Y5M= +gopkg.in/ns1/ns1-go.v2 v2.15.2/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/providers/dns/gcloud/googlecloud.go b/providers/dns/gcloud/googlecloud.go index ff317946d..61e8ee66f 100644 --- a/providers/dns/gcloud/googlecloud.go +++ b/providers/dns/gcloud/googlecloud.go @@ -2,6 +2,7 @@ package gcloud import ( + "context" "encoding/json" "errors" "fmt" @@ -19,7 +20,6 @@ import ( "github.com/go-acme/lego/v4/platform/wait" "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/miekg/dns" - "golang.org/x/net/context" "golang.org/x/oauth2" "golang.org/x/oauth2/google" gdns "google.golang.org/api/dns/v1" diff --git a/providers/dns/inwx/inwx.go b/providers/dns/inwx/inwx.go index 794db84b3..0e79d71e0 100644 --- a/providers/dns/inwx/inwx.go +++ b/providers/dns/inwx/inwx.go @@ -177,7 +177,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("inwx: %w", err) } - var recordID int + var recordID string for _, record := range response.Records { if record.Content != info.Value { @@ -189,7 +189,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { break } - if recordID == 0 { + if recordID == "" { return errors.New("inwx: TXT record not found") } From dc0a595a9f7c3e861bc244bc9021e704c774b381 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 27 Nov 2025 20:40:55 +0100 Subject: [PATCH 221/298] Add DNS provider for United-Domains (#2731) --- README.md | 12 +- cmd/zz_gen_cmd_dnshelp.go | 21 ++ docs/content/dns/zz_gen_uniteddomains.md | 67 ++++++ docs/data/zz_cli_help.toml | 2 +- .../internal => internal/ionos}/client.go | 10 +- .../ionos}/client_test.go | 2 +- .../ionos}/fixtures/get_records.json | 0 .../ionos}/fixtures/get_records_error.json | 0 .../ionos}/fixtures/list_zones.json | 0 .../ionos}/fixtures/list_zones_error.json | 0 .../ionos}/fixtures/remove_record_error.json | 0 .../fixtures/replace_records_error.json | 0 .../internal => internal/ionos}/types.go | 2 +- providers/dns/ionos/ionos.go | 16 +- providers/dns/uniteddomains/uniteddomains.go | 210 ++++++++++++++++++ .../dns/uniteddomains/uniteddomains.toml | 22 ++ .../dns/uniteddomains/uniteddomains_test.go | 130 +++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 18 files changed, 476 insertions(+), 21 deletions(-) create mode 100644 docs/content/dns/zz_gen_uniteddomains.md rename providers/dns/{ionos/internal => internal/ionos}/client.go (95%) rename providers/dns/{ionos/internal => internal/ionos}/client_test.go (99%) rename providers/dns/{ionos/internal => internal/ionos}/fixtures/get_records.json (100%) rename providers/dns/{ionos/internal => internal/ionos}/fixtures/get_records_error.json (100%) rename providers/dns/{ionos/internal => internal/ionos}/fixtures/list_zones.json (100%) rename providers/dns/{ionos/internal => internal/ionos}/fixtures/list_zones_error.json (100%) rename providers/dns/{ionos/internal => internal/ionos}/fixtures/remove_record_error.json (100%) rename providers/dns/{ionos/internal => internal/ionos}/fixtures/replace_records_error.json (100%) rename providers/dns/{ionos/internal => internal/ionos}/types.go (99%) create mode 100644 providers/dns/uniteddomains/uniteddomains.go create mode 100644 providers/dns/uniteddomains/uniteddomains.toml create mode 100644 providers/dns/uniteddomains/uniteddomains_test.go diff --git a/README.md b/README.md index d40afc579..42bf26c73 100644 --- a/README.md +++ b/README.md @@ -245,34 +245,34 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). UKFast SafeDNS Ultradns + United-Domains Variomedia VegaDNS - Vercel + Vercel Versio.[nl|eu|uk] VinylDNS VK Cloud - Volcano Engine/火山引擎 + Volcano Engine/火山引擎 Vscale Vultr webnames.ca - webnames.ru + webnames.ru Websupport WEDOS West.cn/西部数码 - Yandex 360 + Yandex 360 Yandex Cloud Yandex PDD Zone.ee - ZoneEdit + ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 2cba1b73a..11cf01280 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -161,6 +161,7 @@ func allDNSCodes() string { "timewebcloud", "transip", "ultradns", + "uniteddomains", "variomedia", "vegadns", "vercel", @@ -3383,6 +3384,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/ultradns`) + case "uniteddomains": + // generated from: providers/dns/uniteddomains/uniteddomains.toml + ew.writeln(`Configuration for United-Domains.`) + ew.writeln(`Code: 'uniteddomains'`) + ew.writeln(`Since: 'v4.29.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "UNITEDDOMAINS_API_KEY": API key '.' https://www.united-domains.de/help/faq-article/getting-started-with-the-united-domains-dns-api/`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "UNITEDDOMAINS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "UNITEDDOMAINS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "UNITEDDOMAINS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 900)`) + ew.writeln(` - "UNITEDDOMAINS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/uniteddomains`) + case "variomedia": // generated from: providers/dns/variomedia/variomedia.toml ew.writeln(`Configuration for Variomedia.`) diff --git a/docs/content/dns/zz_gen_uniteddomains.md b/docs/content/dns/zz_gen_uniteddomains.md new file mode 100644 index 000000000..7f94dd09f --- /dev/null +++ b/docs/content/dns/zz_gen_uniteddomains.md @@ -0,0 +1,67 @@ +--- +title: "United-Domains" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: uniteddomains +dnsprovider: + since: "v4.29.0" + code: "uniteddomains" + url: "https://www.united-domains.de/" +--- + + + + + + +Configuration for [United-Domains](https://www.united-domains.de/). + + + + +- Code: `uniteddomains` +- Since: v4.29.0 + + +Here is an example bash command using the United-Domains provider: + +```bash +UNITEDDOMAINS_API_KEY=xxxxxxxx \ +lego --email you@example.com --dns uniteddomains -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `UNITEDDOMAINS_API_KEY` | API key `.` https://www.united-domains.de/help/faq-article/getting-started-with-the-united-domains-dns-api/ | + +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 | +|--------------------------------|-------------| +| `UNITEDDOMAINS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `UNITEDDOMAINS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `UNITEDDOMAINS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 900) | +| `UNITEDDOMAINS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | + +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://www.united-domains.de/dns-apidoc/) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 552539fb0..f9401b0e3 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/ionos/internal/client.go b/providers/dns/internal/ionos/client.go similarity index 95% rename from providers/dns/ionos/internal/client.go rename to providers/dns/internal/ionos/client.go index 935b6bbad..8ab6f15b9 100644 --- a/providers/dns/ionos/internal/client.go +++ b/providers/dns/internal/ionos/client.go @@ -1,4 +1,4 @@ -package internal +package ionos import ( "bytes" @@ -14,8 +14,10 @@ import ( querystring "github.com/google/go-querystring/query" ) -// defaultBaseURL represents the API endpoint to call. -const defaultBaseURL = "https://api.hosting.ionos.com/dns" +const ( + DefaultIonosBaseURL = "https://api.hosting.ionos.com/dns" + DefaultUnitedDomainsBaseURL = "https://dnsapi.united-domains.de/dns" +) // APIKeyHeader API key header. const APIKeyHeader = "X-Api-Key" @@ -30,7 +32,7 @@ type Client struct { // NewClient creates a new Client. func NewClient(apiKey string) (*Client, error) { - baseURL, err := url.Parse(defaultBaseURL) + baseURL, err := url.Parse(DefaultIonosBaseURL) if err != nil { return nil, err } diff --git a/providers/dns/ionos/internal/client_test.go b/providers/dns/internal/ionos/client_test.go similarity index 99% rename from providers/dns/ionos/internal/client_test.go rename to providers/dns/internal/ionos/client_test.go index 008d153bc..81e4ff289 100644 --- a/providers/dns/ionos/internal/client_test.go +++ b/providers/dns/internal/ionos/client_test.go @@ -1,4 +1,4 @@ -package internal +package ionos import ( "net/http" diff --git a/providers/dns/ionos/internal/fixtures/get_records.json b/providers/dns/internal/ionos/fixtures/get_records.json similarity index 100% rename from providers/dns/ionos/internal/fixtures/get_records.json rename to providers/dns/internal/ionos/fixtures/get_records.json diff --git a/providers/dns/ionos/internal/fixtures/get_records_error.json b/providers/dns/internal/ionos/fixtures/get_records_error.json similarity index 100% rename from providers/dns/ionos/internal/fixtures/get_records_error.json rename to providers/dns/internal/ionos/fixtures/get_records_error.json diff --git a/providers/dns/ionos/internal/fixtures/list_zones.json b/providers/dns/internal/ionos/fixtures/list_zones.json similarity index 100% rename from providers/dns/ionos/internal/fixtures/list_zones.json rename to providers/dns/internal/ionos/fixtures/list_zones.json diff --git a/providers/dns/ionos/internal/fixtures/list_zones_error.json b/providers/dns/internal/ionos/fixtures/list_zones_error.json similarity index 100% rename from providers/dns/ionos/internal/fixtures/list_zones_error.json rename to providers/dns/internal/ionos/fixtures/list_zones_error.json diff --git a/providers/dns/ionos/internal/fixtures/remove_record_error.json b/providers/dns/internal/ionos/fixtures/remove_record_error.json similarity index 100% rename from providers/dns/ionos/internal/fixtures/remove_record_error.json rename to providers/dns/internal/ionos/fixtures/remove_record_error.json diff --git a/providers/dns/ionos/internal/fixtures/replace_records_error.json b/providers/dns/internal/ionos/fixtures/replace_records_error.json similarity index 100% rename from providers/dns/ionos/internal/fixtures/replace_records_error.json rename to providers/dns/internal/ionos/fixtures/replace_records_error.json diff --git a/providers/dns/ionos/internal/types.go b/providers/dns/internal/ionos/types.go similarity index 99% rename from providers/dns/ionos/internal/types.go rename to providers/dns/internal/ionos/types.go index 35bfe0966..3fc74c054 100644 --- a/providers/dns/ionos/internal/types.go +++ b/providers/dns/internal/ionos/types.go @@ -1,4 +1,4 @@ -package internal +package ionos import ( "fmt" diff --git a/providers/dns/ionos/ionos.go b/providers/dns/ionos/ionos.go index a512e8bfd..fd35f502e 100644 --- a/providers/dns/ionos/ionos.go +++ b/providers/dns/ionos/ionos.go @@ -14,7 +14,7 @@ import ( "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/ionos/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/ionos" ) // Environment variables names. @@ -57,7 +57,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { config *Config - client *internal.Client + client *ionos.Client } // NewDNSProvider returns a DNSProvider instance configured for Ionos. @@ -88,7 +88,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, fmt.Errorf("ionos: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) } - client, err := internal.NewClient(config.APIKey) + client, err := ionos.NewClient(config.APIKey) if err != nil { return nil, fmt.Errorf("ionos: %w", err) } @@ -126,7 +126,7 @@ func (d *DNSProvider) Present(domain, _, keyAuth string) error { return errors.New("ionos: no matching zone found for domain") } - filter := &internal.RecordsFilter{ + filter := &ionos.RecordsFilter{ Suffix: name, RecordType: "TXT", } @@ -136,7 +136,7 @@ func (d *DNSProvider) Present(domain, _, keyAuth string) error { return fmt.Errorf("ionos: failed to get records (zone=%s): %w", zone.ID, err) } - records = append(records, internal.Record{ + records = append(records, ionos.Record{ Name: name, Content: info.Value, TTL: d.config.TTL, @@ -169,7 +169,7 @@ func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { return errors.New("ionos: no matching zone found for domain") } - filter := &internal.RecordsFilter{ + filter := &ionos.RecordsFilter{ Suffix: name, RecordType: "TXT", } @@ -193,8 +193,8 @@ func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { return fmt.Errorf("ionos: failed to remove record, record not found (zone=%s, domain=%s, fqdn=%s, value=%s)", zone.ID, domain, info.EffectiveFQDN, info.Value) } -func findZone(zones []internal.Zone, domain string) *internal.Zone { - var result *internal.Zone +func findZone(zones []ionos.Zone, domain string) *ionos.Zone { + var result *ionos.Zone for _, zone := range zones { if zone.Name != "" && strings.HasSuffix(domain, zone.Name) { diff --git a/providers/dns/uniteddomains/uniteddomains.go b/providers/dns/uniteddomains/uniteddomains.go new file mode 100644 index 000000000..0cb50c2af --- /dev/null +++ b/providers/dns/uniteddomains/uniteddomains.go @@ -0,0 +1,210 @@ +// Package uniteddomains implements a DNS provider for solving the DNS-01 challenge using United-Domains. +package uniteddomains + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/internal/ionos" +) + +// Environment variables names. +const ( + envNamespace = "UNITEDDOMAINS_" + + EnvAPIKey = envNamespace + "API_KEY" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +const minTTL = 300 + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// 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, minTTL), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 15*time.Minute), + 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 *ionos.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for United-Domains. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIKey) + if err != nil { + return nil, fmt.Errorf("uniteddomains: %w", err) + } + + config := NewDefaultConfig() + config.APIKey = values[EnvAPIKey] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for United-Domains. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("uniteddomains: the configuration of the DNS provider is nil") + } + + if config.APIKey == "" { + return nil, errors.New("uniteddomains: credentials missing") + } + + if config.TTL < minTTL { + return nil, fmt.Errorf("uniteddomains: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) + } + + client, err := ionos.NewClient(config.APIKey) + if err != nil { + return nil, fmt.Errorf("uniteddomains: %w", err) + } + + client.BaseURL, _ = url.Parse(ionos.DefaultUnitedDomainsBaseURL) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{config: config, client: client}, 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 +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, _, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + zones, err := d.client.ListZones(ctx) + if err != nil { + return fmt.Errorf("uniteddomains: failed to get zones: %w", err) + } + + name := dns01.UnFqdn(info.EffectiveFQDN) + + zone := findZone(zones, name) + if zone == nil { + return errors.New("uniteddomains: no matching zone found for domain") + } + + filter := &ionos.RecordsFilter{ + Suffix: name, + RecordType: "TXT", + } + + records, err := d.client.GetRecords(ctx, zone.ID, filter) + if err != nil { + return fmt.Errorf("uniteddomains: failed to get records (zone=%s): %w", zone.ID, err) + } + + records = append(records, ionos.Record{ + Name: name, + Content: info.Value, + TTL: d.config.TTL, + Type: "TXT", + }) + + err = d.client.ReplaceRecords(ctx, zone.ID, records) + if err != nil { + return fmt.Errorf("uniteddomains: failed to create/update records (zone=%s): %w", zone.ID, err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + zones, err := d.client.ListZones(ctx) + if err != nil { + return fmt.Errorf("uniteddomains: failed to get zones: %w", err) + } + + name := dns01.UnFqdn(info.EffectiveFQDN) + + zone := findZone(zones, name) + if zone == nil { + return errors.New("uniteddomains: no matching zone found for domain") + } + + filter := &ionos.RecordsFilter{ + Suffix: name, + RecordType: "TXT", + } + + records, err := d.client.GetRecords(ctx, zone.ID, filter) + if err != nil { + return fmt.Errorf("uniteddomains: failed to get records (zone=%s): %w", zone.ID, err) + } + + for _, record := range records { + if record.Name == name && record.Content == strconv.Quote(info.Value) { + err = d.client.RemoveRecord(ctx, zone.ID, record.ID) + if err != nil { + return fmt.Errorf("uniteddomains: failed to remove record (zone=%s, record=%s): %w", zone.ID, record.ID, err) + } + + return nil + } + } + + return fmt.Errorf("uniteddomains: failed to remove record, record not found (zone=%s, domain=%s, fqdn=%s, value=%s)", zone.ID, domain, info.EffectiveFQDN, info.Value) +} + +func findZone(zones []ionos.Zone, domain string) *ionos.Zone { + var result *ionos.Zone + + for _, zone := range zones { + if zone.Name != "" && strings.HasSuffix(domain, zone.Name) { + if result == nil || len(zone.Name) > len(result.Name) { + result = &zone + } + } + } + + return result +} diff --git a/providers/dns/uniteddomains/uniteddomains.toml b/providers/dns/uniteddomains/uniteddomains.toml new file mode 100644 index 000000000..3663cb867 --- /dev/null +++ b/providers/dns/uniteddomains/uniteddomains.toml @@ -0,0 +1,22 @@ +Name = "United-Domains" +Description = '''''' +URL = "https://www.united-domains.de/" +Code = "uniteddomains" +Since = "v4.29.0" + +Example = ''' +UNITEDDOMAINS_API_KEY=xxxxxxxx \ +lego --email you@example.com --dns uniteddomains -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + UNITEDDOMAINS_API_KEY = "API key `.` https://www.united-domains.de/help/faq-article/getting-started-with-the-united-domains-dns-api/" + [Configuration.Additional] + UNITEDDOMAINS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + UNITEDDOMAINS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 900)" + UNITEDDOMAINS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + UNITEDDOMAINS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://www.united-domains.de/dns-apidoc/" diff --git a/providers/dns/uniteddomains/uniteddomains_test.go b/providers/dns/uniteddomains/uniteddomains_test.go new file mode 100644 index 000000000..841268ca2 --- /dev/null +++ b/providers/dns/uniteddomains/uniteddomains_test.go @@ -0,0 +1,130 @@ +package uniteddomains + +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: "123", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{ + EnvAPIKey: "", + }, + expected: "uniteddomains: some credentials information are missing: UNITEDDOMAINS_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 + tll int + expected string + }{ + { + desc: "success", + apiKey: "123", + tll: minTTL, + }, + { + desc: "missing credentials", + tll: minTTL, + expected: "uniteddomains: credentials missing", + }, + { + desc: "invalid TTL", + apiKey: "123", + tll: 30, + expected: "uniteddomains: invalid TTL, TTL (30) must be greater than 300", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIKey = test.apiKey + config.TTL = test.tll + + 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 8ecbb76cc..2add1f75f 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -155,6 +155,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/timewebcloud" "github.com/go-acme/lego/v4/providers/dns/transip" "github.com/go-acme/lego/v4/providers/dns/ultradns" + "github.com/go-acme/lego/v4/providers/dns/uniteddomains" "github.com/go-acme/lego/v4/providers/dns/variomedia" "github.com/go-acme/lego/v4/providers/dns/vegadns" "github.com/go-acme/lego/v4/providers/dns/vercel" @@ -478,6 +479,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return transip.NewDNSProvider() case "ultradns": return ultradns.NewDNSProvider() + case "uniteddomains": + return uniteddomains.NewDNSProvider() case "variomedia": return variomedia.NewDNSProvider() case "vegadns": From 1757cdeaee03b534e4952c22aa1705c879730069 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sat, 29 Nov 2025 14:20:42 +0100 Subject: [PATCH 222/298] chore: use common implementations of the providers instead of the API clients (#2734) --- providers/dns/active24/active24.go | 136 +----------- providers/dns/active24/active24_test.go | 6 +- providers/dns/edgecenter/edgecenter.go | 91 ++------ providers/dns/edgecenter/edgecenter_test.go | 6 +- providers/dns/gcore/gcore.go | 89 ++------ providers/dns/gcore/gcore_test.go | 6 +- providers/dns/hostingde/hostingde.go | 154 ++------------ providers/dns/hostingde/hostingde_test.go | 6 +- providers/dns/httpnet/httpnet.go | 158 ++------------ providers/dns/httpnet/httpnet_test.go | 6 +- .../active24/{ => internal}/client.go | 2 +- .../active24/{ => internal}/client_test.go | 2 +- .../{ => internal}/fixtures/error_403.json | 0 .../{ => internal}/fixtures/error_422.json | 0 .../{ => internal}/fixtures/error_v1.json | 0 .../{ => internal}/fixtures/records.json | 0 .../{ => internal}/fixtures/services.json | 0 .../internal/active24/{ => internal}/types.go | 2 +- providers/dns/internal/active24/provider.go | 179 ++++++++++++++++ .../dns/internal/active24/provider_test.go | 57 +++++ .../internal/gcore/{ => internal}/client.go | 9 +- .../gcore/{ => internal}/client_test.go | 2 +- .../internal/gcore/{ => internal}/types.go | 2 +- providers/dns/internal/gcore/provider.go | 126 +++++++++++ providers/dns/internal/gcore/provider_test.go | 42 ++++ .../hostingde/{ => internal}/client.go | 9 +- .../hostingde/{ => internal}/client_test.go | 2 +- .../fixtures/zoneConfigsFind-request.json | 0 .../fixtures/zoneConfigsFind.json | 0 .../fixtures/zoneConfigsFind_error.json | 0 .../fixtures/zoneUpdate-request.json | 0 .../{ => internal}/fixtures/zoneUpdate.json | 0 .../fixtures/zoneUpdate_error.json | 0 .../hostingde/{ => internal}/types.go | 2 +- providers/dns/internal/hostingde/provider.go | 196 ++++++++++++++++++ .../dns/internal/hostingde/provider_test.go | 50 +++++ .../internal/ionos/{ => internal}/client.go | 9 +- .../ionos/{ => internal}/client_test.go | 2 +- .../{ => internal}/fixtures/get_records.json | 0 .../fixtures/get_records_error.json | 0 .../{ => internal}/fixtures/list_zones.json | 0 .../fixtures/list_zones_error.json | 0 .../fixtures/remove_record_error.json | 0 .../fixtures/replace_records_error.json | 0 .../internal/ionos/{ => internal}/types.go | 2 +- providers/dns/internal/ionos/provider.go | 173 ++++++++++++++++ providers/dns/internal/ionos/provider_test.go | 52 +++++ .../rimuhosting/{ => internal}/client.go | 10 +- .../rimuhosting/{ => internal}/client_test.go | 2 +- .../{ => internal}/fixtures/add_record.xml | 0 .../fixtures/add_record_error.xml | 0 .../fixtures/add_record_same_domain.xml | 0 .../{ => internal}/fixtures/delete_record.xml | 0 .../fixtures/delete_record_error.xml | 0 .../fixtures/delete_record_nothing.xml | 0 .../{ => internal}/fixtures/find_records.xml | 0 .../fixtures/find_records_empty.xml | 0 .../fixtures/find_records_pattern.xml | 0 .../rimuhosting/{ => internal}/types.go | 2 +- .../dns/internal/rimuhosting/provider.go | 107 ++++++++++ .../dns/internal/rimuhosting/provider_test.go | 46 ++++ .../selectel/{ => internal}/client.go | 12 +- .../selectel/{ => internal}/client_test.go | 2 +- .../fixtures/add_record-request.json | 0 .../{ => internal}/fixtures/add_record.json | 0 .../{ => internal}/fixtures/domains.json | 0 .../{ => internal}/fixtures/error.json | 0 .../{ => internal}/fixtures/list_records.json | 0 .../internal/selectel/{ => internal}/types.go | 2 +- providers/dns/internal/selectel/provider.go | 137 ++++++++++++ .../dns/internal/selectel/provider_test.go | 55 +++++ providers/dns/ionos/ionos.go | 130 ++---------- providers/dns/ionos/ionos_test.go | 10 +- providers/dns/rimuhosting/rimuhosting.go | 73 ++----- providers/dns/rimuhosting/rimuhosting_test.go | 4 +- providers/dns/selectel/selectel.go | 106 ++-------- providers/dns/selectel/selectel_test.go | 9 +- providers/dns/uniteddomains/uniteddomains.go | 133 ++---------- .../dns/uniteddomains/uniteddomains_test.go | 10 +- providers/dns/vscale/vscale.go | 106 ++-------- providers/dns/vscale/vscale_test.go | 9 +- providers/dns/websupport/websupport.go | 136 +----------- providers/dns/websupport/websupport_test.go | 6 +- providers/dns/zonomi/zonomi.go | 75 ++----- providers/dns/zonomi/zonomi_test.go | 4 +- 85 files changed, 1458 insertions(+), 1306 deletions(-) rename providers/dns/internal/active24/{ => internal}/client.go (99%) rename providers/dns/internal/active24/{ => internal}/client_test.go (99%) rename providers/dns/internal/active24/{ => internal}/fixtures/error_403.json (100%) rename providers/dns/internal/active24/{ => internal}/fixtures/error_422.json (100%) rename providers/dns/internal/active24/{ => internal}/fixtures/error_v1.json (100%) rename providers/dns/internal/active24/{ => internal}/fixtures/records.json (100%) rename providers/dns/internal/active24/{ => internal}/fixtures/services.json (100%) rename providers/dns/internal/active24/{ => internal}/types.go (99%) create mode 100644 providers/dns/internal/active24/provider.go create mode 100644 providers/dns/internal/active24/provider_test.go rename providers/dns/internal/gcore/{ => internal}/client.go (96%) rename providers/dns/internal/gcore/{ => internal}/client_test.go (99%) rename providers/dns/internal/gcore/{ => internal}/types.go (96%) create mode 100644 providers/dns/internal/gcore/provider.go create mode 100644 providers/dns/internal/gcore/provider_test.go rename providers/dns/internal/hostingde/{ => internal}/client.go (94%) rename providers/dns/internal/hostingde/{ => internal}/client_test.go (99%) rename providers/dns/internal/hostingde/{ => internal}/fixtures/zoneConfigsFind-request.json (100%) rename providers/dns/internal/hostingde/{ => internal}/fixtures/zoneConfigsFind.json (100%) rename providers/dns/internal/hostingde/{ => internal}/fixtures/zoneConfigsFind_error.json (100%) rename providers/dns/internal/hostingde/{ => internal}/fixtures/zoneUpdate-request.json (100%) rename providers/dns/internal/hostingde/{ => internal}/fixtures/zoneUpdate.json (100%) rename providers/dns/internal/hostingde/{ => internal}/fixtures/zoneUpdate_error.json (100%) rename providers/dns/internal/hostingde/{ => internal}/types.go (99%) create mode 100644 providers/dns/internal/hostingde/provider.go create mode 100644 providers/dns/internal/hostingde/provider_test.go rename providers/dns/internal/ionos/{ => internal}/client.go (95%) rename providers/dns/internal/ionos/{ => internal}/client_test.go (99%) rename providers/dns/internal/ionos/{ => internal}/fixtures/get_records.json (100%) rename providers/dns/internal/ionos/{ => internal}/fixtures/get_records_error.json (100%) rename providers/dns/internal/ionos/{ => internal}/fixtures/list_zones.json (100%) rename providers/dns/internal/ionos/{ => internal}/fixtures/list_zones_error.json (100%) rename providers/dns/internal/ionos/{ => internal}/fixtures/remove_record_error.json (100%) rename providers/dns/internal/ionos/{ => internal}/fixtures/replace_records_error.json (100%) rename providers/dns/internal/ionos/{ => internal}/types.go (99%) create mode 100644 providers/dns/internal/ionos/provider.go create mode 100644 providers/dns/internal/ionos/provider_test.go rename providers/dns/internal/rimuhosting/{ => internal}/client.go (94%) rename providers/dns/internal/rimuhosting/{ => internal}/client_test.go (99%) rename providers/dns/internal/rimuhosting/{ => internal}/fixtures/add_record.xml (100%) rename providers/dns/internal/rimuhosting/{ => internal}/fixtures/add_record_error.xml (100%) rename providers/dns/internal/rimuhosting/{ => internal}/fixtures/add_record_same_domain.xml (100%) rename providers/dns/internal/rimuhosting/{ => internal}/fixtures/delete_record.xml (100%) rename providers/dns/internal/rimuhosting/{ => internal}/fixtures/delete_record_error.xml (100%) rename providers/dns/internal/rimuhosting/{ => internal}/fixtures/delete_record_nothing.xml (100%) rename providers/dns/internal/rimuhosting/{ => internal}/fixtures/find_records.xml (100%) rename providers/dns/internal/rimuhosting/{ => internal}/fixtures/find_records_empty.xml (100%) rename providers/dns/internal/rimuhosting/{ => internal}/fixtures/find_records_pattern.xml (100%) rename providers/dns/internal/rimuhosting/{ => internal}/types.go (98%) create mode 100644 providers/dns/internal/rimuhosting/provider.go create mode 100644 providers/dns/internal/rimuhosting/provider_test.go rename providers/dns/internal/selectel/{ => internal}/client.go (93%) rename providers/dns/internal/selectel/{ => internal}/client_test.go (99%) rename providers/dns/internal/selectel/{ => internal}/fixtures/add_record-request.json (100%) rename providers/dns/internal/selectel/{ => internal}/fixtures/add_record.json (100%) rename providers/dns/internal/selectel/{ => internal}/fixtures/domains.json (100%) rename providers/dns/internal/selectel/{ => internal}/fixtures/error.json (100%) rename providers/dns/internal/selectel/{ => internal}/fixtures/list_records.json (100%) rename providers/dns/internal/selectel/{ => internal}/types.go (98%) create mode 100644 providers/dns/internal/selectel/provider.go create mode 100644 providers/dns/internal/selectel/provider_test.go diff --git a/providers/dns/active24/active24.go b/providers/dns/active24/active24.go index c8107cab6..0b925de6a 100644 --- a/providers/dns/active24/active24.go +++ b/providers/dns/active24/active24.go @@ -2,17 +2,15 @@ package active24 import ( - "context" "errors" "fmt" "net/http" - "strconv" "time" + "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/internal/active24" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) const baseAPIDomain = "active24.cz" @@ -31,15 +29,7 @@ const ( ) // Config is used to configure the creation of the DNSProvider. -type Config struct { - APIKey string - Secret string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} +type Config = active24.Config // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { @@ -55,8 +45,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *active24.Client + prv challenge.ProviderTimeout } // NewDNSProvider returns a DNSProvider instance configured for Active24. @@ -79,83 +68,29 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("active24: the configuration of the DNS provider is nil") } - client, err := active24.NewClient(baseAPIDomain, config.APIKey, config.Secret) + provider, err := active24.NewDNSProviderConfig(config, baseAPIDomain) if err != nil { return nil, fmt.Errorf("active24: %w", err) } - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - }, nil + return &DNSProvider{prv: provider}, nil } // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("active24: could not find zone for domain %q: %w", domain, err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + err := d.prv.Present(domain, token, keyAuth) if err != nil { return fmt.Errorf("active24: %w", err) } - serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) - if err != nil { - return fmt.Errorf("active24: find service ID: %w", err) - } - - record := active24.Record{ - Type: "TXT", - Name: subDomain, - Content: info.Value, - TTL: d.config.TTL, - } - - err = d.client.CreateRecord(ctx, strconv.Itoa(serviceID), record) - if err != nil { - return fmt.Errorf("active24: create record: %w", err) - } - return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + err := d.prv.CleanUp(domain, token, keyAuth) if err != nil { - return fmt.Errorf("active24: could not find zone for domain %q: %w", domain, err) - } - - serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) - if err != nil { - return fmt.Errorf("active24: find service ID: %w", err) - } - - recordID, err := d.findRecordID(ctx, strconv.Itoa(serviceID), info) - if err != nil { - return fmt.Errorf("active24: find record ID: %w", err) - } - - err = d.client.DeleteRecord(ctx, strconv.Itoa(serviceID), strconv.Itoa(recordID)) - if err != nil { - return fmt.Errorf("active24: delete record %w", err) + return fmt.Errorf("active24: %w", err) } return nil @@ -164,58 +99,5 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // 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 -} - -func (d *DNSProvider) findServiceID(ctx context.Context, domain string) (int, error) { - services, err := d.client.GetServices(ctx) - if err != nil { - return 0, fmt.Errorf("get services: %w", err) - } - - for _, service := range services { - if service.ServiceName != "domain" { - continue - } - - if service.Name != domain { - continue - } - - return service.ID, nil - } - - return 0, fmt.Errorf("service not found for domain: %s", domain) -} - -func (d *DNSProvider) findRecordID(ctx context.Context, serviceID string, info dns01.ChallengeInfo) (int, error) { - // NOTE(ldez): Despite the API documentation, the filter doesn't seem to work. - filter := active24.RecordFilter{ - Name: dns01.UnFqdn(info.EffectiveFQDN), - Type: []string{"TXT"}, - Content: info.Value, - } - - records, err := d.client.GetRecords(ctx, serviceID, filter) - if err != nil { - return 0, fmt.Errorf("get records: %w", err) - } - - for _, record := range records { - if record.Type != "TXT" { - continue - } - - if record.Name != dns01.UnFqdn(info.EffectiveFQDN) { - continue - } - - if record.Content != info.Value { - continue - } - - return record.ID, nil - } - - return 0, errors.New("no record found") + return d.prv.Timeout() } diff --git a/providers/dns/active24/active24_test.go b/providers/dns/active24/active24_test.go index 363e0229a..2987fb27b 100644 --- a/providers/dns/active24/active24_test.go +++ b/providers/dns/active24/active24_test.go @@ -60,8 +60,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } @@ -110,8 +109,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/edgecenter/edgecenter.go b/providers/dns/edgecenter/edgecenter.go index 2040a304c..cfc75b521 100644 --- a/providers/dns/edgecenter/edgecenter.go +++ b/providers/dns/edgecenter/edgecenter.go @@ -1,17 +1,15 @@ +// Package edgecenter implements a DNS provider for solving the DNS-01 challenge using EdgeCenter. package edgecenter import ( - "context" "errors" "fmt" "net/http" - "net/url" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/gcore" ) @@ -27,28 +25,19 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) -const ( - defaultPropagationTimeout = 360 * time.Second - defaultPollingInterval = 20 * time.Second -) +const defaultBaseURL = "https://api.edgecenter.ru/dns" var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config for DNSProvider. -type Config struct { - APIToken string - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} +type Config = gcore.Config // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, defaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, defaultPollingInterval), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, gcore.DefaultPropagationTimeout), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, gcore.DefaultPollingInterval), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second), }, @@ -57,8 +46,7 @@ func NewDefaultConfig() *Config { // DNSProvider an implementation of challenge.Provider contract. type DNSProvider struct { - config *Config - client *gcore.Client + prv challenge.ProviderTimeout } // NewDNSProvider returns an instance of DNSProvider configured for G-Core DNS API. @@ -80,81 +68,36 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("edgecenter: the configuration of the DNS provider is nil") } - if config.APIToken == "" { - return nil, errors.New("edgecenter: incomplete credentials provided") + provider, err := gcore.NewDNSProviderConfig(config, defaultBaseURL) + if err != nil { + return nil, fmt.Errorf("edgecenter: %w", err) } - client := gcore.NewClient(config.APIToken) - client.BaseURL, _ = url.Parse(gcore.DefaultEdgeCenterBaseURL) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - }, nil + return &DNSProvider{prv: provider}, nil } -// Present creates a TXT record to fulfill the dns-01 challenge. -func (d *DNSProvider) Present(domain, _, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - zone, err := d.guessZone(ctx, info.EffectiveFQDN) +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + err := d.prv.Present(domain, token, keyAuth) if err != nil { return fmt.Errorf("edgecenter: %w", err) } - err = d.client.AddRRSet(ctx, zone, dns01.UnFqdn(info.EffectiveFQDN), info.Value, d.config.TTL) - if err != nil { - return fmt.Errorf("edgecenter: add txt record: %w", err) - } - return nil } -// CleanUp removes the record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - zone, err := d.guessZone(ctx, info.EffectiveFQDN) +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + err := d.prv.CleanUp(domain, token, keyAuth) if err != nil { return fmt.Errorf("edgecenter: %w", err) } - err = d.client.DeleteRRSet(ctx, zone, dns01.UnFqdn(info.EffectiveFQDN)) - if err != nil { - return fmt.Errorf("edgecenter: remove txt record: %w", err) - } - 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 -} - -func (d *DNSProvider) guessZone(ctx context.Context, fqdn string) (string, error) { - var lastErr error - - for zone := range dns01.UnFqdnDomainsSeq(fqdn) { - dnsZone, err := d.client.GetZone(ctx, zone) - if err != nil { - lastErr = err - continue - } - - return dnsZone.Name, nil - } - - return "", fmt.Errorf("zone %q not found: %w", fqdn, lastErr) + return d.prv.Timeout() } diff --git a/providers/dns/edgecenter/edgecenter_test.go b/providers/dns/edgecenter/edgecenter_test.go index 79814680d..e3ec43981 100644 --- a/providers/dns/edgecenter/edgecenter_test.go +++ b/providers/dns/edgecenter/edgecenter_test.go @@ -43,8 +43,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } @@ -78,8 +77,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/gcore/gcore.go b/providers/dns/gcore/gcore.go index 6400dc0b3..9b98f28d4 100644 --- a/providers/dns/gcore/gcore.go +++ b/providers/dns/gcore/gcore.go @@ -1,7 +1,7 @@ +// Package gcore implements a DNS provider for solving the DNS-01 challenge using G-Core. package gcore import ( - "context" "errors" "fmt" "net/http" @@ -10,7 +10,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/gcore" ) @@ -26,28 +25,17 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) -const ( - defaultPropagationTimeout = 360 * time.Second - defaultPollingInterval = 20 * time.Second -) - var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config for DNSProvider. -type Config struct { - APIToken string - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} +type Config = gcore.Config // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), - PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, defaultPropagationTimeout), - PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, defaultPollingInterval), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, gcore.DefaultPropagationTimeout), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, gcore.DefaultPollingInterval), HTTPClient: &http.Client{ Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second), }, @@ -56,8 +44,7 @@ func NewDefaultConfig() *Config { // DNSProvider an implementation of challenge.Provider contract. type DNSProvider struct { - config *Config - client *gcore.Client + prv challenge.ProviderTimeout } // NewDNSProvider returns an instance of DNSProvider configured for G-Core DNS API. @@ -79,80 +66,36 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("gcore: the configuration of the DNS provider is nil") } - if config.APIToken == "" { - return nil, errors.New("gcore: incomplete credentials provided") + provider, err := gcore.NewDNSProviderConfig(config, "") + if err != nil { + return nil, fmt.Errorf("gcore: %w", err) } - client := gcore.NewClient(config.APIToken) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - }, nil + return &DNSProvider{prv: provider}, nil } -// Present creates a TXT record to fulfill the dns-01 challenge. -func (d *DNSProvider) Present(domain, _, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - zone, err := d.guessZone(ctx, info.EffectiveFQDN) +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + err := d.prv.Present(domain, token, keyAuth) if err != nil { return fmt.Errorf("gcore: %w", err) } - err = d.client.AddRRSet(ctx, zone, dns01.UnFqdn(info.EffectiveFQDN), info.Value, d.config.TTL) - if err != nil { - return fmt.Errorf("gcore: add txt record: %w", err) - } - return nil } -// CleanUp removes the record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - zone, err := d.guessZone(ctx, info.EffectiveFQDN) +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + err := d.prv.CleanUp(domain, token, keyAuth) if err != nil { return fmt.Errorf("gcore: %w", err) } - err = d.client.DeleteRRSet(ctx, zone, dns01.UnFqdn(info.EffectiveFQDN)) - if err != nil { - return fmt.Errorf("gcore: remove txt record: %w", err) - } - 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 -} - -func (d *DNSProvider) guessZone(ctx context.Context, fqdn string) (string, error) { - var lastErr error - - for zone := range dns01.UnFqdnDomainsSeq(fqdn) { - dnsZone, err := d.client.GetZone(ctx, zone) - if err != nil { - lastErr = err - continue - } - - return dnsZone.Name, nil - } - - return "", fmt.Errorf("zone %q not found: %w", fqdn, lastErr) + return d.prv.Timeout() } diff --git a/providers/dns/gcore/gcore_test.go b/providers/dns/gcore/gcore_test.go index 88c1d02a5..6f8e38c12 100644 --- a/providers/dns/gcore/gcore_test.go +++ b/providers/dns/gcore/gcore_test.go @@ -43,8 +43,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } @@ -78,8 +77,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/hostingde/hostingde.go b/providers/dns/hostingde/hostingde.go index 48c44998f..1e022b630 100644 --- a/providers/dns/hostingde/hostingde.go +++ b/providers/dns/hostingde/hostingde.go @@ -2,17 +2,14 @@ package hostingde import ( - "context" "errors" "fmt" "net/http" - "sync" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/hostingde" ) @@ -32,14 +29,7 @@ const ( var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. -type Config struct { - APIKey string - ZoneName string - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} +type Config = hostingde.Config // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { @@ -56,11 +46,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *hostingde.Client - - recordIDs map[string]string - recordIDsMu sync.Mutex + prv challenge.ProviderTimeout } // NewDNSProvider returns a DNSProvider instance configured for hosting.de. @@ -84,130 +70,27 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("hostingde: the configuration of the DNS provider is nil") } - if config.APIKey == "" { - return nil, errors.New("hostingde: API key missing") + provider, err := hostingde.NewDNSProviderConfig(config, "") + if err != nil { + return nil, fmt.Errorf("hostingde: %w", err) } - client := hostingde.NewClient(config.APIKey) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - recordIDs: make(map[string]string), - }, nil + return &DNSProvider{prv: provider}, 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 -} - -// Present creates a TXT record to fulfill the dns-01 challenge. +// Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - zoneName, err := d.getZoneName(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("hostingde: could not find zone for domain %q: %w", domain, err) - } - - ctx := context.Background() - - // get the ZoneConfig for that domain - zonesFind := hostingde.ZoneConfigsFindRequest{ - Filter: hostingde.Filter{Field: "zoneName", Value: zoneName}, - Limit: 1, - Page: 1, - } - - zoneConfig, err := d.client.GetZone(ctx, zonesFind) + err := d.prv.Present(domain, token, keyAuth) if err != nil { return fmt.Errorf("hostingde: %w", err) } - zoneConfig.Name = zoneName - - rec := []hostingde.DNSRecord{{ - Type: "TXT", - Name: dns01.UnFqdn(info.EffectiveFQDN), - Content: info.Value, - TTL: d.config.TTL, - }} - - req := hostingde.ZoneUpdateRequest{ - ZoneConfig: *zoneConfig, - RecordsToAdd: rec, - } - - response, err := d.client.UpdateZone(ctx, req) - if err != nil { - return fmt.Errorf("hostingde: %w", err) - } - - for _, record := range response.Records { - if record.Name == dns01.UnFqdn(info.EffectiveFQDN) && record.Content == fmt.Sprintf(`%q`, info.Value) { - d.recordIDsMu.Lock() - d.recordIDs[info.EffectiveFQDN] = record.ID - d.recordIDsMu.Unlock() - } - } - - if d.recordIDs[info.EffectiveFQDN] == "" { - return fmt.Errorf("hostingde: error getting ID of just created record, for domain %s", domain) - } - 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) - - zoneName, err := d.getZoneName(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("hostingde: could not find zone for domain %q: %w", domain, err) - } - - ctx := context.Background() - - // get the ZoneConfig for that domain - zonesFind := hostingde.ZoneConfigsFindRequest{ - Filter: hostingde.Filter{Field: "zoneName", Value: zoneName}, - Limit: 1, - Page: 1, - } - - zoneConfig, err := d.client.GetZone(ctx, zonesFind) - if err != nil { - return fmt.Errorf("hostingde: %w", err) - } - - zoneConfig.Name = zoneName - - rec := []hostingde.DNSRecord{{ - Type: "TXT", - Name: dns01.UnFqdn(info.EffectiveFQDN), - Content: `"` + info.Value + `"`, - }} - - req := hostingde.ZoneUpdateRequest{ - ZoneConfig: *zoneConfig, - RecordsToDelete: rec, - } - - // Delete record ID from map - d.recordIDsMu.Lock() - delete(d.recordIDs, info.EffectiveFQDN) - d.recordIDsMu.Unlock() - - _, err = d.client.UpdateZone(ctx, req) + err := d.prv.CleanUp(domain, token, keyAuth) if err != nil { return fmt.Errorf("hostingde: %w", err) } @@ -215,19 +98,8 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } -func (d *DNSProvider) getZoneName(fqdn string) (string, error) { - if d.config.ZoneName != "" { - return d.config.ZoneName, nil - } - - zoneName, err := dns01.FindZoneByFqdn(fqdn) - if err != nil { - return "", fmt.Errorf("could not find zone for %s: %w", fqdn, err) - } - - if zoneName == "" { - return "", errors.New("empty zone name") - } - - return dns01.UnFqdn(zoneName), 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.prv.Timeout() } diff --git a/providers/dns/hostingde/hostingde_test.go b/providers/dns/hostingde/hostingde_test.go index 1611cb51b..a92006f81 100644 --- a/providers/dns/hostingde/hostingde_test.go +++ b/providers/dns/hostingde/hostingde_test.go @@ -59,8 +59,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.recordIDs) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } @@ -102,8 +101,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.recordIDs) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/httpnet/httpnet.go b/providers/dns/httpnet/httpnet.go index f18eefd97..4a88f1092 100644 --- a/providers/dns/httpnet/httpnet.go +++ b/providers/dns/httpnet/httpnet.go @@ -2,18 +2,14 @@ package httpnet import ( - "context" "errors" "fmt" "net/http" - "net/url" - "sync" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/hostingde" ) @@ -30,17 +26,12 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) +const defaultBaseURL = "https://partner.http.net/api/dns/v1/json" + var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. -type Config struct { - APIKey string - ZoneName string - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} +type Config = hostingde.Config // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { @@ -57,11 +48,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *hostingde.Client - - recordIDs map[string]string - recordIDsMu sync.Mutex + prv challenge.ProviderTimeout } // NewDNSProvider returns a DNSProvider instance configured for http.net. @@ -85,131 +72,27 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("httpnet: the configuration of the DNS provider is nil") } - if config.APIKey == "" { - return nil, errors.New("httpnet: API key missing") + provider, err := hostingde.NewDNSProviderConfig(config, defaultBaseURL) + if err != nil { + return nil, fmt.Errorf("httpnet: %w", err) } - client := hostingde.NewClient(config.APIKey) - client.BaseURL, _ = url.Parse(hostingde.DefaultHTTPNetBaseURL) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - recordIDs: make(map[string]string), - }, nil + return &DNSProvider{prv: provider}, 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 -} - -// Present creates a TXT record to fulfill the dns-01 challenge. +// Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - zoneName, err := d.getZoneName(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("httpnet: could not find zone for domain %q: %w", domain, err) - } - - ctx := context.Background() - - // get the ZoneConfig for that domain - zonesFind := hostingde.ZoneConfigsFindRequest{ - Filter: hostingde.Filter{Field: "zoneName", Value: zoneName}, - Limit: 1, - Page: 1, - } - - zoneConfig, err := d.client.GetZone(ctx, zonesFind) + err := d.prv.Present(domain, token, keyAuth) if err != nil { return fmt.Errorf("httpnet: %w", err) } - zoneConfig.Name = zoneName - - rec := []hostingde.DNSRecord{{ - Type: "TXT", - Name: dns01.UnFqdn(info.EffectiveFQDN), - Content: info.Value, - TTL: d.config.TTL, - }} - - req := hostingde.ZoneUpdateRequest{ - ZoneConfig: *zoneConfig, - RecordsToAdd: rec, - } - - response, err := d.client.UpdateZone(ctx, req) - if err != nil { - return fmt.Errorf("httpnet: %w", err) - } - - for _, record := range response.Records { - if record.Name == dns01.UnFqdn(info.EffectiveFQDN) && record.Content == fmt.Sprintf(`%q`, info.Value) { - d.recordIDsMu.Lock() - d.recordIDs[info.EffectiveFQDN] = record.ID - d.recordIDsMu.Unlock() - } - } - - if d.recordIDs[info.EffectiveFQDN] == "" { - return fmt.Errorf("httpnet: error getting ID of just created record, for domain %s", domain) - } - 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) - - zoneName, err := d.getZoneName(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("httpnet: could not find zone for domain %q: %w", domain, err) - } - - ctx := context.Background() - - // get the ZoneConfig for that domain - zonesFind := hostingde.ZoneConfigsFindRequest{ - Filter: hostingde.Filter{Field: "zoneName", Value: zoneName}, - Limit: 1, - Page: 1, - } - - zoneConfig, err := d.client.GetZone(ctx, zonesFind) - if err != nil { - return fmt.Errorf("httpnet: %w", err) - } - - zoneConfig.Name = zoneName - - rec := []hostingde.DNSRecord{{ - Type: "TXT", - Name: dns01.UnFqdn(info.EffectiveFQDN), - Content: `"` + info.Value + `"`, - }} - - req := hostingde.ZoneUpdateRequest{ - ZoneConfig: *zoneConfig, - RecordsToDelete: rec, - } - - // Delete record ID from map - d.recordIDsMu.Lock() - delete(d.recordIDs, info.EffectiveFQDN) - d.recordIDsMu.Unlock() - - _, err = d.client.UpdateZone(ctx, req) + err := d.prv.CleanUp(domain, token, keyAuth) if err != nil { return fmt.Errorf("httpnet: %w", err) } @@ -217,19 +100,8 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } -func (d *DNSProvider) getZoneName(fqdn string) (string, error) { - if d.config.ZoneName != "" { - return d.config.ZoneName, nil - } - - zoneName, err := dns01.FindZoneByFqdn(fqdn) - if err != nil { - return "", fmt.Errorf("could not find zone for %s: %w", fqdn, err) - } - - if zoneName == "" { - return "", errors.New("empty zone name") - } - - return dns01.UnFqdn(zoneName), 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.prv.Timeout() } diff --git a/providers/dns/httpnet/httpnet_test.go b/providers/dns/httpnet/httpnet_test.go index 64a94f80c..ef1d2a1b7 100644 --- a/providers/dns/httpnet/httpnet_test.go +++ b/providers/dns/httpnet/httpnet_test.go @@ -59,8 +59,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.recordIDs) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } @@ -102,8 +101,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.recordIDs) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/internal/active24/client.go b/providers/dns/internal/active24/internal/client.go similarity index 99% rename from providers/dns/internal/active24/client.go rename to providers/dns/internal/active24/internal/client.go index 10aaa4666..69e94b367 100644 --- a/providers/dns/internal/active24/client.go +++ b/providers/dns/internal/active24/internal/client.go @@ -1,4 +1,4 @@ -package active24 +package internal import ( "bytes" diff --git a/providers/dns/internal/active24/client_test.go b/providers/dns/internal/active24/internal/client_test.go similarity index 99% rename from providers/dns/internal/active24/client_test.go rename to providers/dns/internal/active24/internal/client_test.go index ad2a8126b..f62f78785 100644 --- a/providers/dns/internal/active24/client_test.go +++ b/providers/dns/internal/active24/internal/client_test.go @@ -1,4 +1,4 @@ -package active24 +package internal import ( "net/http" diff --git a/providers/dns/internal/active24/fixtures/error_403.json b/providers/dns/internal/active24/internal/fixtures/error_403.json similarity index 100% rename from providers/dns/internal/active24/fixtures/error_403.json rename to providers/dns/internal/active24/internal/fixtures/error_403.json diff --git a/providers/dns/internal/active24/fixtures/error_422.json b/providers/dns/internal/active24/internal/fixtures/error_422.json similarity index 100% rename from providers/dns/internal/active24/fixtures/error_422.json rename to providers/dns/internal/active24/internal/fixtures/error_422.json diff --git a/providers/dns/internal/active24/fixtures/error_v1.json b/providers/dns/internal/active24/internal/fixtures/error_v1.json similarity index 100% rename from providers/dns/internal/active24/fixtures/error_v1.json rename to providers/dns/internal/active24/internal/fixtures/error_v1.json diff --git a/providers/dns/internal/active24/fixtures/records.json b/providers/dns/internal/active24/internal/fixtures/records.json similarity index 100% rename from providers/dns/internal/active24/fixtures/records.json rename to providers/dns/internal/active24/internal/fixtures/records.json diff --git a/providers/dns/internal/active24/fixtures/services.json b/providers/dns/internal/active24/internal/fixtures/services.json similarity index 100% rename from providers/dns/internal/active24/fixtures/services.json rename to providers/dns/internal/active24/internal/fixtures/services.json diff --git a/providers/dns/internal/active24/types.go b/providers/dns/internal/active24/internal/types.go similarity index 99% rename from providers/dns/internal/active24/types.go rename to providers/dns/internal/active24/internal/types.go index b9a7ea427..ed8dfc9d3 100644 --- a/providers/dns/internal/active24/types.go +++ b/providers/dns/internal/active24/internal/types.go @@ -1,4 +1,4 @@ -package active24 +package internal import "fmt" diff --git a/providers/dns/internal/active24/provider.go b/providers/dns/internal/active24/provider.go new file mode 100644 index 000000000..ae79b8b17 --- /dev/null +++ b/providers/dns/internal/active24/provider.go @@ -0,0 +1,179 @@ +// Package active24 implements a DNS provider for solving the DNS-01 challenge using Active24. +package active24 + +import ( + "context" + "errors" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/providers/dns/internal/active24/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" +) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + APIKey string + Secret string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Active24. +func NewDNSProviderConfig(config *Config, baseAPIDomain string) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(baseAPIDomain, config.APIKey, config.Secret) + if err != nil { + return nil, err + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return err + } + + serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("find service ID: %w", err) + } + + record := internal.Record{ + Type: "TXT", + Name: subDomain, + Content: info.Value, + TTL: d.config.TTL, + } + + err = d.client.CreateRecord(ctx, strconv.Itoa(serviceID), record) + if err != nil { + return fmt.Errorf("create record: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("could not find zone for domain %q: %w", domain, err) + } + + serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("find service ID: %w", err) + } + + recordID, err := d.findRecordID(ctx, strconv.Itoa(serviceID), info) + if err != nil { + return fmt.Errorf("find record ID: %w", err) + } + + err = d.client.DeleteRecord(ctx, strconv.Itoa(serviceID), strconv.Itoa(recordID)) + if err != nil { + return fmt.Errorf("delete record %w", err) + } + + 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 +} + +func (d *DNSProvider) findServiceID(ctx context.Context, domain string) (int, error) { + services, err := d.client.GetServices(ctx) + if err != nil { + return 0, fmt.Errorf("get services: %w", err) + } + + for _, service := range services { + if service.ServiceName != "domain" { + continue + } + + if service.Name != domain { + continue + } + + return service.ID, nil + } + + return 0, fmt.Errorf("service not found for domain: %s", domain) +} + +func (d *DNSProvider) findRecordID(ctx context.Context, serviceID string, info dns01.ChallengeInfo) (int, error) { + // NOTE(ldez): Despite the API documentation, the filter doesn't seem to work. + filter := internal.RecordFilter{ + Name: dns01.UnFqdn(info.EffectiveFQDN), + Type: []string{"TXT"}, + Content: info.Value, + } + + records, err := d.client.GetRecords(ctx, serviceID, filter) + if err != nil { + return 0, fmt.Errorf("get records: %w", err) + } + + for _, record := range records { + if record.Type != "TXT" { + continue + } + + if record.Name != dns01.UnFqdn(info.EffectiveFQDN) { + continue + } + + if record.Content != info.Value { + continue + } + + return record.ID, nil + } + + return 0, errors.New("no record found") +} diff --git a/providers/dns/internal/active24/provider_test.go b/providers/dns/internal/active24/provider_test.go new file mode 100644 index 000000000..e2959fd6e --- /dev/null +++ b/providers/dns/internal/active24/provider_test.go @@ -0,0 +1,57 @@ +package active24 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + apiKey string + secret string + expected string + }{ + { + desc: "success", + apiKey: "user", + secret: "secret", + }, + { + desc: "missing API key", + apiKey: "", + secret: "secret", + expected: "credentials missing", + }, + { + desc: "missing secret", + apiKey: "user", + secret: "", + expected: "credentials missing", + }, + { + desc: "missing credentials", + expected: "credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := &Config{} + config.APIKey = test.apiKey + config.Secret = test.secret + + p, err := NewDNSProviderConfig(config, "example.com") + + 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) + } + }) + } +} diff --git a/providers/dns/internal/gcore/client.go b/providers/dns/internal/gcore/internal/client.go similarity index 96% rename from providers/dns/internal/gcore/client.go rename to providers/dns/internal/gcore/internal/client.go index 93f17c0d2..f3ad4e461 100644 --- a/providers/dns/internal/gcore/client.go +++ b/providers/dns/internal/gcore/internal/client.go @@ -1,4 +1,4 @@ -package gcore +package internal import ( "bytes" @@ -14,10 +14,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) -const ( - DefaultGCoreBaseURL = "https://api.gcore.com/dns" - DefaultEdgeCenterBaseURL = "https://api.edgecenter.ru/dns" -) +const defaultBaseURL = "https://api.gcore.com/dns" const ( authorizationHeader = "Authorization" @@ -36,7 +33,7 @@ type Client struct { // NewClient constructor of Client. func NewClient(token string) *Client { - baseURL, _ := url.Parse(DefaultGCoreBaseURL) + baseURL, _ := url.Parse(defaultBaseURL) return &Client{ token: token, diff --git a/providers/dns/internal/gcore/client_test.go b/providers/dns/internal/gcore/internal/client_test.go similarity index 99% rename from providers/dns/internal/gcore/client_test.go rename to providers/dns/internal/gcore/internal/client_test.go index 79289ef42..7d70c9308 100644 --- a/providers/dns/internal/gcore/client_test.go +++ b/providers/dns/internal/gcore/internal/client_test.go @@ -1,4 +1,4 @@ -package gcore +package internal import ( "net/http" diff --git a/providers/dns/internal/gcore/types.go b/providers/dns/internal/gcore/internal/types.go similarity index 96% rename from providers/dns/internal/gcore/types.go rename to providers/dns/internal/gcore/internal/types.go index 1c4fbd502..4245f5ba8 100644 --- a/providers/dns/internal/gcore/types.go +++ b/providers/dns/internal/gcore/internal/types.go @@ -1,4 +1,4 @@ -package gcore +package internal import "fmt" diff --git a/providers/dns/internal/gcore/provider.go b/providers/dns/internal/gcore/provider.go new file mode 100644 index 000000000..b2078eba5 --- /dev/null +++ b/providers/dns/internal/gcore/provider.go @@ -0,0 +1,126 @@ +// Package gcore implements a DNS provider for solving the DNS-01 challenge using G-Core. +package gcore + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/internal/gcore/internal" +) + +const ( + DefaultPropagationTimeout = 360 * time.Second + DefaultPollingInterval = 20 * time.Second +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config for DNSProvider. +type Config struct { + APIToken string + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} + +// DNSProvider an implementation of challenge.Provider contract. +type DNSProvider struct { + config *Config + client *internal.Client +} + +// NewDNSProviderConfig return a DNSProvider instance configured for G-Core DNS API. +func NewDNSProviderConfig(config *Config, baseURL string) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("the configuration of the DNS provider is nil") + } + + if config.APIToken == "" { + return nil, errors.New("incomplete credentials provided") + } + + client := internal.NewClient(config.APIToken) + + if baseURL != "" { + client.BaseURL, _ = url.Parse(baseURL) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record to fulfill the dns-01 challenge. +func (d *DNSProvider) Present(domain, _, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + zone, err := d.guessZone(ctx, info.EffectiveFQDN) + if err != nil { + return err + } + + err = d.client.AddRRSet(ctx, zone, dns01.UnFqdn(info.EffectiveFQDN), info.Value, d.config.TTL) + if err != nil { + return fmt.Errorf("add txt record: %w", err) + } + + return nil +} + +// CleanUp removes the record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + zone, err := d.guessZone(ctx, info.EffectiveFQDN) + if err != nil { + return err + } + + err = d.client.DeleteRRSet(ctx, zone, dns01.UnFqdn(info.EffectiveFQDN)) + if err != nil { + return fmt.Errorf("remove txt record: %w", err) + } + + 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 +} + +func (d *DNSProvider) guessZone(ctx context.Context, fqdn string) (string, error) { + var lastErr error + + for zone := range dns01.UnFqdnDomainsSeq(fqdn) { + dnsZone, err := d.client.GetZone(ctx, zone) + if err != nil { + lastErr = err + continue + } + + return dnsZone.Name, nil + } + + return "", fmt.Errorf("zone %q not found: %w", fqdn, lastErr) +} diff --git a/providers/dns/internal/gcore/provider_test.go b/providers/dns/internal/gcore/provider_test.go new file mode 100644 index 000000000..f29dadff9 --- /dev/null +++ b/providers/dns/internal/gcore/provider_test.go @@ -0,0 +1,42 @@ +package gcore + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + apiToken string + expected string + }{ + { + desc: "success", + apiToken: "A", + }, + { + desc: "missing credentials", + expected: "incomplete credentials provided", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := &Config{} + config.APIToken = test.apiToken + + 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) + } + }) + } +} diff --git a/providers/dns/internal/hostingde/client.go b/providers/dns/internal/hostingde/internal/client.go similarity index 94% rename from providers/dns/internal/hostingde/client.go rename to providers/dns/internal/hostingde/internal/client.go index 43354384f..133c3479c 100644 --- a/providers/dns/internal/hostingde/client.go +++ b/providers/dns/internal/hostingde/internal/client.go @@ -1,4 +1,4 @@ -package hostingde +package internal import ( "bytes" @@ -14,10 +14,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) -const ( - DefaultHostingdeBaseURL = "https://secure.hosting.de/api/dns/v1/json" - DefaultHTTPNetBaseURL = "https://partner.http.net/api/dns/v1/json" -) +const defaultBaseURL = "https://secure.hosting.de/api/dns/v1/json" // Client the API client for Hosting.de. type Client struct { @@ -29,7 +26,7 @@ type Client struct { // NewClient creates new Client. func NewClient(apiKey string) *Client { - baseURL, _ := url.Parse(DefaultHostingdeBaseURL) + baseURL, _ := url.Parse(defaultBaseURL) return &Client{ apiKey: apiKey, diff --git a/providers/dns/internal/hostingde/client_test.go b/providers/dns/internal/hostingde/internal/client_test.go similarity index 99% rename from providers/dns/internal/hostingde/client_test.go rename to providers/dns/internal/hostingde/internal/client_test.go index 93e0c76e1..d55bbf690 100644 --- a/providers/dns/internal/hostingde/client_test.go +++ b/providers/dns/internal/hostingde/internal/client_test.go @@ -1,4 +1,4 @@ -package hostingde +package internal import ( "encoding/json" diff --git a/providers/dns/internal/hostingde/fixtures/zoneConfigsFind-request.json b/providers/dns/internal/hostingde/internal/fixtures/zoneConfigsFind-request.json similarity index 100% rename from providers/dns/internal/hostingde/fixtures/zoneConfigsFind-request.json rename to providers/dns/internal/hostingde/internal/fixtures/zoneConfigsFind-request.json diff --git a/providers/dns/internal/hostingde/fixtures/zoneConfigsFind.json b/providers/dns/internal/hostingde/internal/fixtures/zoneConfigsFind.json similarity index 100% rename from providers/dns/internal/hostingde/fixtures/zoneConfigsFind.json rename to providers/dns/internal/hostingde/internal/fixtures/zoneConfigsFind.json diff --git a/providers/dns/internal/hostingde/fixtures/zoneConfigsFind_error.json b/providers/dns/internal/hostingde/internal/fixtures/zoneConfigsFind_error.json similarity index 100% rename from providers/dns/internal/hostingde/fixtures/zoneConfigsFind_error.json rename to providers/dns/internal/hostingde/internal/fixtures/zoneConfigsFind_error.json diff --git a/providers/dns/internal/hostingde/fixtures/zoneUpdate-request.json b/providers/dns/internal/hostingde/internal/fixtures/zoneUpdate-request.json similarity index 100% rename from providers/dns/internal/hostingde/fixtures/zoneUpdate-request.json rename to providers/dns/internal/hostingde/internal/fixtures/zoneUpdate-request.json diff --git a/providers/dns/internal/hostingde/fixtures/zoneUpdate.json b/providers/dns/internal/hostingde/internal/fixtures/zoneUpdate.json similarity index 100% rename from providers/dns/internal/hostingde/fixtures/zoneUpdate.json rename to providers/dns/internal/hostingde/internal/fixtures/zoneUpdate.json diff --git a/providers/dns/internal/hostingde/fixtures/zoneUpdate_error.json b/providers/dns/internal/hostingde/internal/fixtures/zoneUpdate_error.json similarity index 100% rename from providers/dns/internal/hostingde/fixtures/zoneUpdate_error.json rename to providers/dns/internal/hostingde/internal/fixtures/zoneUpdate_error.json diff --git a/providers/dns/internal/hostingde/types.go b/providers/dns/internal/hostingde/internal/types.go similarity index 99% rename from providers/dns/internal/hostingde/types.go rename to providers/dns/internal/hostingde/internal/types.go index 86b69ec42..330eab27d 100644 --- a/providers/dns/internal/hostingde/types.go +++ b/providers/dns/internal/hostingde/internal/types.go @@ -1,4 +1,4 @@ -package hostingde +package internal import "encoding/json" diff --git a/providers/dns/internal/hostingde/provider.go b/providers/dns/internal/hostingde/provider.go new file mode 100644 index 000000000..644dc8aaf --- /dev/null +++ b/providers/dns/internal/hostingde/provider.go @@ -0,0 +1,196 @@ +// Package hostingde implements a DNS provider for solving the DNS-01 challenge using hosting.de. +package hostingde + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/internal/hostingde/internal" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + APIKey string + ZoneName string + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client + + recordIDs map[string]string + recordIDsMu sync.Mutex +} + +// NewDNSProviderConfig return a DNSProvider instance configured for hosting.de. +func NewDNSProviderConfig(config *Config, baseURL string) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("the configuration of the DNS provider is nil") + } + + if config.APIKey == "" { + return nil, errors.New("API key missing") + } + + client := internal.NewClient(config.APIKey) + + if baseURL != "" { + client.BaseURL, _ = url.Parse(baseURL) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + recordIDs: make(map[string]string), + }, 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 +} + +// Present creates a TXT record to fulfill the dns-01 challenge. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + zoneName, err := d.getZoneName(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("could not find zone for domain %q: %w", domain, err) + } + + ctx := context.Background() + + // get the ZoneConfig for that domain + zonesFind := internal.ZoneConfigsFindRequest{ + Filter: internal.Filter{Field: "zoneName", Value: zoneName}, + Limit: 1, + Page: 1, + } + + zoneConfig, err := d.client.GetZone(ctx, zonesFind) + if err != nil { + return err + } + + zoneConfig.Name = zoneName + + rec := []internal.DNSRecord{{ + Type: "TXT", + Name: dns01.UnFqdn(info.EffectiveFQDN), + Content: info.Value, + TTL: d.config.TTL, + }} + + req := internal.ZoneUpdateRequest{ + ZoneConfig: *zoneConfig, + RecordsToAdd: rec, + } + + response, err := d.client.UpdateZone(ctx, req) + if err != nil { + return err + } + + for _, record := range response.Records { + if record.Name == dns01.UnFqdn(info.EffectiveFQDN) && record.Content == fmt.Sprintf(`%q`, info.Value) { + d.recordIDsMu.Lock() + d.recordIDs[info.EffectiveFQDN] = record.ID + d.recordIDsMu.Unlock() + } + } + + if d.recordIDs[info.EffectiveFQDN] == "" { + return fmt.Errorf("error getting ID of just created record, for domain %s", domain) + } + + 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) + + zoneName, err := d.getZoneName(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("could not find zone for domain %q: %w", domain, err) + } + + ctx := context.Background() + + // get the ZoneConfig for that domain + zonesFind := internal.ZoneConfigsFindRequest{ + Filter: internal.Filter{Field: "zoneName", Value: zoneName}, + Limit: 1, + Page: 1, + } + + zoneConfig, err := d.client.GetZone(ctx, zonesFind) + if err != nil { + return err + } + + zoneConfig.Name = zoneName + + rec := []internal.DNSRecord{{ + Type: "TXT", + Name: dns01.UnFqdn(info.EffectiveFQDN), + Content: `"` + info.Value + `"`, + }} + + req := internal.ZoneUpdateRequest{ + ZoneConfig: *zoneConfig, + RecordsToDelete: rec, + } + + // Delete record ID from map + d.recordIDsMu.Lock() + delete(d.recordIDs, info.EffectiveFQDN) + d.recordIDsMu.Unlock() + + _, err = d.client.UpdateZone(ctx, req) + if err != nil { + return err + } + + return nil +} + +func (d *DNSProvider) getZoneName(fqdn string) (string, error) { + if d.config.ZoneName != "" { + return d.config.ZoneName, nil + } + + zoneName, err := dns01.FindZoneByFqdn(fqdn) + if err != nil { + return "", fmt.Errorf("could not find zone for %s: %w", fqdn, err) + } + + if zoneName == "" { + return "", errors.New("empty zone name") + } + + return dns01.UnFqdn(zoneName), nil +} diff --git a/providers/dns/internal/hostingde/provider_test.go b/providers/dns/internal/hostingde/provider_test.go new file mode 100644 index 000000000..3cdabf702 --- /dev/null +++ b/providers/dns/internal/hostingde/provider_test.go @@ -0,0 +1,50 @@ +package hostingde + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + apiKey string + zoneName string + expected string + }{ + { + desc: "success", + apiKey: "123", + zoneName: "example.org", + }, + { + desc: "missing credentials", + expected: "API key missing", + }, + { + desc: "missing api key", + zoneName: "456", + expected: "API key missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := &Config{} + config.APIKey = test.apiKey + config.ZoneName = test.zoneName + + p, err := NewDNSProviderConfig(config, "") + + if test.expected == "" { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.config) + require.NotNil(t, p.recordIDs) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} diff --git a/providers/dns/internal/ionos/client.go b/providers/dns/internal/ionos/internal/client.go similarity index 95% rename from providers/dns/internal/ionos/client.go rename to providers/dns/internal/ionos/internal/client.go index 8ab6f15b9..2a556a49b 100644 --- a/providers/dns/internal/ionos/client.go +++ b/providers/dns/internal/ionos/internal/client.go @@ -1,4 +1,4 @@ -package ionos +package internal import ( "bytes" @@ -14,10 +14,7 @@ import ( querystring "github.com/google/go-querystring/query" ) -const ( - DefaultIonosBaseURL = "https://api.hosting.ionos.com/dns" - DefaultUnitedDomainsBaseURL = "https://dnsapi.united-domains.de/dns" -) +const defaultBaseURL = "https://api.hosting.ionos.com/dns" // APIKeyHeader API key header. const APIKeyHeader = "X-Api-Key" @@ -32,7 +29,7 @@ type Client struct { // NewClient creates a new Client. func NewClient(apiKey string) (*Client, error) { - baseURL, err := url.Parse(DefaultIonosBaseURL) + baseURL, err := url.Parse(defaultBaseURL) if err != nil { return nil, err } diff --git a/providers/dns/internal/ionos/client_test.go b/providers/dns/internal/ionos/internal/client_test.go similarity index 99% rename from providers/dns/internal/ionos/client_test.go rename to providers/dns/internal/ionos/internal/client_test.go index 81e4ff289..008d153bc 100644 --- a/providers/dns/internal/ionos/client_test.go +++ b/providers/dns/internal/ionos/internal/client_test.go @@ -1,4 +1,4 @@ -package ionos +package internal import ( "net/http" diff --git a/providers/dns/internal/ionos/fixtures/get_records.json b/providers/dns/internal/ionos/internal/fixtures/get_records.json similarity index 100% rename from providers/dns/internal/ionos/fixtures/get_records.json rename to providers/dns/internal/ionos/internal/fixtures/get_records.json diff --git a/providers/dns/internal/ionos/fixtures/get_records_error.json b/providers/dns/internal/ionos/internal/fixtures/get_records_error.json similarity index 100% rename from providers/dns/internal/ionos/fixtures/get_records_error.json rename to providers/dns/internal/ionos/internal/fixtures/get_records_error.json diff --git a/providers/dns/internal/ionos/fixtures/list_zones.json b/providers/dns/internal/ionos/internal/fixtures/list_zones.json similarity index 100% rename from providers/dns/internal/ionos/fixtures/list_zones.json rename to providers/dns/internal/ionos/internal/fixtures/list_zones.json diff --git a/providers/dns/internal/ionos/fixtures/list_zones_error.json b/providers/dns/internal/ionos/internal/fixtures/list_zones_error.json similarity index 100% rename from providers/dns/internal/ionos/fixtures/list_zones_error.json rename to providers/dns/internal/ionos/internal/fixtures/list_zones_error.json diff --git a/providers/dns/internal/ionos/fixtures/remove_record_error.json b/providers/dns/internal/ionos/internal/fixtures/remove_record_error.json similarity index 100% rename from providers/dns/internal/ionos/fixtures/remove_record_error.json rename to providers/dns/internal/ionos/internal/fixtures/remove_record_error.json diff --git a/providers/dns/internal/ionos/fixtures/replace_records_error.json b/providers/dns/internal/ionos/internal/fixtures/replace_records_error.json similarity index 100% rename from providers/dns/internal/ionos/fixtures/replace_records_error.json rename to providers/dns/internal/ionos/internal/fixtures/replace_records_error.json diff --git a/providers/dns/internal/ionos/types.go b/providers/dns/internal/ionos/internal/types.go similarity index 99% rename from providers/dns/internal/ionos/types.go rename to providers/dns/internal/ionos/internal/types.go index 3fc74c054..35bfe0966 100644 --- a/providers/dns/internal/ionos/types.go +++ b/providers/dns/internal/ionos/internal/types.go @@ -1,4 +1,4 @@ -package ionos +package internal import ( "fmt" diff --git a/providers/dns/internal/ionos/provider.go b/providers/dns/internal/ionos/provider.go new file mode 100644 index 000000000..a7d145840 --- /dev/null +++ b/providers/dns/internal/ionos/provider.go @@ -0,0 +1,173 @@ +package ionos + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + ionos "github.com/go-acme/lego/v4/providers/dns/internal/ionos/internal" +) + +const MinTTL = 300 + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// 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 +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *ionos.Client +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Ionos. +func NewDNSProviderConfig(config *Config, baseURL string) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("the configuration of the DNS provider is nil") + } + + if config.APIKey == "" { + return nil, errors.New("credentials missing") + } + + if config.TTL < MinTTL { + return nil, fmt.Errorf("invalid TTL, TTL (%d) must be greater than %d", config.TTL, MinTTL) + } + + client, err := ionos.NewClient(config.APIKey) + if err != nil { + return nil, err + } + + if baseURL != "" { + client.BaseURL, _ = url.Parse(baseURL) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{config: config, client: client}, 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 +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, _, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + zones, err := d.client.ListZones(ctx) + if err != nil { + return fmt.Errorf("failed to get zones: %w", err) + } + + name := dns01.UnFqdn(info.EffectiveFQDN) + + zone := findZone(zones, name) + if zone == nil { + return errors.New("no matching zone found for domain") + } + + filter := &ionos.RecordsFilter{ + Suffix: name, + RecordType: "TXT", + } + + records, err := d.client.GetRecords(ctx, zone.ID, filter) + if err != nil { + return fmt.Errorf("failed to get records (zone=%s): %w", zone.ID, err) + } + + records = append(records, ionos.Record{ + Name: name, + Content: info.Value, + TTL: d.config.TTL, + Type: "TXT", + }) + + err = d.client.ReplaceRecords(ctx, zone.ID, records) + if err != nil { + return fmt.Errorf("failed to create/update records (zone=%s): %w", zone.ID, err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + zones, err := d.client.ListZones(ctx) + if err != nil { + return fmt.Errorf("failed to get zones: %w", err) + } + + name := dns01.UnFqdn(info.EffectiveFQDN) + + zone := findZone(zones, name) + if zone == nil { + return errors.New("no matching zone found for domain") + } + + filter := &ionos.RecordsFilter{ + Suffix: name, + RecordType: "TXT", + } + + records, err := d.client.GetRecords(ctx, zone.ID, filter) + if err != nil { + return fmt.Errorf("failed to get records (zone=%s): %w", zone.ID, err) + } + + for _, record := range records { + if record.Name == name && record.Content == strconv.Quote(info.Value) { + err = d.client.RemoveRecord(ctx, zone.ID, record.ID) + if err != nil { + return fmt.Errorf("failed to remove record (zone=%s, record=%s): %w", zone.ID, record.ID, err) + } + + return nil + } + } + + return fmt.Errorf("failed to remove record, record not found (zone=%s, domain=%s, fqdn=%s, value=%s)", zone.ID, domain, info.EffectiveFQDN, info.Value) +} + +func findZone(zones []ionos.Zone, domain string) *ionos.Zone { + var result *ionos.Zone + + for _, zone := range zones { + if zone.Name != "" && strings.HasSuffix(domain, zone.Name) { + if result == nil || len(zone.Name) > len(result.Name) { + result = &zone + } + } + } + + return result +} diff --git a/providers/dns/internal/ionos/provider_test.go b/providers/dns/internal/ionos/provider_test.go new file mode 100644 index 000000000..6b4df5cc7 --- /dev/null +++ b/providers/dns/internal/ionos/provider_test.go @@ -0,0 +1,52 @@ +package ionos + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + apiKey string + tll int + expected string + }{ + { + desc: "success", + apiKey: "123", + tll: MinTTL, + }, + { + desc: "missing credentials", + tll: MinTTL, + expected: "credentials missing", + }, + { + desc: "invalid TTL", + apiKey: "123", + tll: 30, + expected: "invalid TTL, TTL (30) must be greater than 300", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := &Config{} + config.APIKey = test.apiKey + config.TTL = test.tll + + 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) + } + }) + } +} diff --git a/providers/dns/internal/rimuhosting/client.go b/providers/dns/internal/rimuhosting/internal/client.go similarity index 94% rename from providers/dns/internal/rimuhosting/client.go rename to providers/dns/internal/rimuhosting/internal/client.go index c46afc544..5bf7393e7 100644 --- a/providers/dns/internal/rimuhosting/client.go +++ b/providers/dns/internal/rimuhosting/internal/client.go @@ -1,4 +1,4 @@ -package rimuhosting +package internal import ( "context" @@ -15,11 +15,7 @@ import ( querystring "github.com/google/go-querystring/query" ) -// Base URL for the RimuHosting DNS services. -const ( - DefaultZonomiBaseURL = "https://zonomi.com/app/dns/dyndns.jsp" - DefaultRimuHostingBaseURL = "https://rimuhosting.com/dns/dyndns.jsp" -) +const defaultBaseURL = "https://rimuhosting.com/dns/dyndns.jsp" // Action names. const ( @@ -40,7 +36,7 @@ type Client struct { func NewClient(apiKey string) *Client { return &Client{ apiKey: apiKey, - BaseURL: DefaultZonomiBaseURL, + BaseURL: defaultBaseURL, HTTPClient: &http.Client{Timeout: 5 * time.Second}, } } diff --git a/providers/dns/internal/rimuhosting/client_test.go b/providers/dns/internal/rimuhosting/internal/client_test.go similarity index 99% rename from providers/dns/internal/rimuhosting/client_test.go rename to providers/dns/internal/rimuhosting/internal/client_test.go index 6ee9ea3f7..00126dfbe 100644 --- a/providers/dns/internal/rimuhosting/client_test.go +++ b/providers/dns/internal/rimuhosting/internal/client_test.go @@ -1,4 +1,4 @@ -package rimuhosting +package internal import ( "encoding/xml" diff --git a/providers/dns/internal/rimuhosting/fixtures/add_record.xml b/providers/dns/internal/rimuhosting/internal/fixtures/add_record.xml similarity index 100% rename from providers/dns/internal/rimuhosting/fixtures/add_record.xml rename to providers/dns/internal/rimuhosting/internal/fixtures/add_record.xml diff --git a/providers/dns/internal/rimuhosting/fixtures/add_record_error.xml b/providers/dns/internal/rimuhosting/internal/fixtures/add_record_error.xml similarity index 100% rename from providers/dns/internal/rimuhosting/fixtures/add_record_error.xml rename to providers/dns/internal/rimuhosting/internal/fixtures/add_record_error.xml diff --git a/providers/dns/internal/rimuhosting/fixtures/add_record_same_domain.xml b/providers/dns/internal/rimuhosting/internal/fixtures/add_record_same_domain.xml similarity index 100% rename from providers/dns/internal/rimuhosting/fixtures/add_record_same_domain.xml rename to providers/dns/internal/rimuhosting/internal/fixtures/add_record_same_domain.xml diff --git a/providers/dns/internal/rimuhosting/fixtures/delete_record.xml b/providers/dns/internal/rimuhosting/internal/fixtures/delete_record.xml similarity index 100% rename from providers/dns/internal/rimuhosting/fixtures/delete_record.xml rename to providers/dns/internal/rimuhosting/internal/fixtures/delete_record.xml diff --git a/providers/dns/internal/rimuhosting/fixtures/delete_record_error.xml b/providers/dns/internal/rimuhosting/internal/fixtures/delete_record_error.xml similarity index 100% rename from providers/dns/internal/rimuhosting/fixtures/delete_record_error.xml rename to providers/dns/internal/rimuhosting/internal/fixtures/delete_record_error.xml diff --git a/providers/dns/internal/rimuhosting/fixtures/delete_record_nothing.xml b/providers/dns/internal/rimuhosting/internal/fixtures/delete_record_nothing.xml similarity index 100% rename from providers/dns/internal/rimuhosting/fixtures/delete_record_nothing.xml rename to providers/dns/internal/rimuhosting/internal/fixtures/delete_record_nothing.xml diff --git a/providers/dns/internal/rimuhosting/fixtures/find_records.xml b/providers/dns/internal/rimuhosting/internal/fixtures/find_records.xml similarity index 100% rename from providers/dns/internal/rimuhosting/fixtures/find_records.xml rename to providers/dns/internal/rimuhosting/internal/fixtures/find_records.xml diff --git a/providers/dns/internal/rimuhosting/fixtures/find_records_empty.xml b/providers/dns/internal/rimuhosting/internal/fixtures/find_records_empty.xml similarity index 100% rename from providers/dns/internal/rimuhosting/fixtures/find_records_empty.xml rename to providers/dns/internal/rimuhosting/internal/fixtures/find_records_empty.xml diff --git a/providers/dns/internal/rimuhosting/fixtures/find_records_pattern.xml b/providers/dns/internal/rimuhosting/internal/fixtures/find_records_pattern.xml similarity index 100% rename from providers/dns/internal/rimuhosting/fixtures/find_records_pattern.xml rename to providers/dns/internal/rimuhosting/internal/fixtures/find_records_pattern.xml diff --git a/providers/dns/internal/rimuhosting/types.go b/providers/dns/internal/rimuhosting/internal/types.go similarity index 98% rename from providers/dns/internal/rimuhosting/types.go rename to providers/dns/internal/rimuhosting/internal/types.go index bdb333032..c3df886a2 100644 --- a/providers/dns/internal/rimuhosting/types.go +++ b/providers/dns/internal/rimuhosting/internal/types.go @@ -1,4 +1,4 @@ -package rimuhosting +package internal import "encoding/xml" diff --git a/providers/dns/internal/rimuhosting/provider.go b/providers/dns/internal/rimuhosting/provider.go new file mode 100644 index 000000000..3be764cbf --- /dev/null +++ b/providers/dns/internal/rimuhosting/provider.go @@ -0,0 +1,107 @@ +// Package rimuhosting implements a DNS provider for solving the DNS-01 challenge using RimuHosting DNS. +package rimuhosting + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/internal/rimuhosting/internal" +) + +const DefaultTTL = 3600 + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// 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 +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client +} + +// NewDNSProviderConfig return a DNSProvider instance configured for RimuHosting. +func NewDNSProviderConfig(config *Config, baseURL string) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("the configuration of the DNS provider is nil") + } + + if config.APIKey == "" { + return nil, errors.New("incomplete credentials, missing API key") + } + + client := internal.NewClient(config.APIKey) + + if baseURL != "" { + client.BaseURL = baseURL + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{config: config, client: client}, 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 +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + records, err := d.client.FindTXTRecords(ctx, dns01.UnFqdn(info.EffectiveFQDN)) + if err != nil { + return fmt.Errorf("failed to find record(s) for %s: %w", domain, err) + } + + actions := []internal.ActionParameter{ + internal.NewAddRecordAction(dns01.UnFqdn(info.EffectiveFQDN), info.Value, d.config.TTL), + } + + for _, record := range records { + actions = append(actions, internal.NewAddRecordAction(record.Name, record.Content, d.config.TTL)) + } + + _, err = d.client.DoActions(ctx, actions...) + if err != nil { + return fmt.Errorf("failed to add record(s) for %s: %w", domain, err) + } + + 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) + + action := internal.NewDeleteRecordAction(dns01.UnFqdn(info.EffectiveFQDN), info.Value) + + _, err := d.client.DoActions(context.Background(), action) + if err != nil { + return fmt.Errorf("failed to delete record for %s: %w", domain, err) + } + + return nil +} diff --git a/providers/dns/internal/rimuhosting/provider_test.go b/providers/dns/internal/rimuhosting/provider_test.go new file mode 100644 index 000000000..d1569af31 --- /dev/null +++ b/providers/dns/internal/rimuhosting/provider_test.go @@ -0,0 +1,46 @@ +package rimuhosting + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + expected string + apiKey string + secretKey string + }{ + { + desc: "success", + apiKey: "api_key", + secretKey: "api_secret", + }, + { + desc: "missing api key", + apiKey: "", + secretKey: "api_secret", + expected: "incomplete credentials, missing API key", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := &Config{} + 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) + } + }) + } +} diff --git a/providers/dns/internal/selectel/client.go b/providers/dns/internal/selectel/internal/client.go similarity index 93% rename from providers/dns/internal/selectel/client.go rename to providers/dns/internal/selectel/internal/client.go index fe810ebc5..b17df6d83 100644 --- a/providers/dns/internal/selectel/client.go +++ b/providers/dns/internal/selectel/internal/client.go @@ -1,4 +1,4 @@ -package selectel +package internal import ( "bytes" @@ -15,15 +15,11 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) -// Base URL for the Selectel/VScale DNS services. -const ( - DefaultSelectelBaseURL = "https://api.selectel.ru/domains/v1" - DefaultVScaleBaseURL = "https://api.vscale.io/v1/domains" -) +const defaultBaseURL = "https://api.selectel.ru/domains/v1" const tokenHeader = "X-Token" -// Client represents DNS client. +// Client represents the DNS client. type Client struct { token string @@ -33,7 +29,7 @@ type Client struct { // NewClient returns a client instance. func NewClient(token string) *Client { - baseURL, _ := url.Parse(DefaultVScaleBaseURL) + baseURL, _ := url.Parse(defaultBaseURL) return &Client{ token: token, diff --git a/providers/dns/internal/selectel/client_test.go b/providers/dns/internal/selectel/internal/client_test.go similarity index 99% rename from providers/dns/internal/selectel/client_test.go rename to providers/dns/internal/selectel/internal/client_test.go index 292f70142..edabe0130 100644 --- a/providers/dns/internal/selectel/client_test.go +++ b/providers/dns/internal/selectel/internal/client_test.go @@ -1,4 +1,4 @@ -package selectel +package internal import ( "net/http" diff --git a/providers/dns/internal/selectel/fixtures/add_record-request.json b/providers/dns/internal/selectel/internal/fixtures/add_record-request.json similarity index 100% rename from providers/dns/internal/selectel/fixtures/add_record-request.json rename to providers/dns/internal/selectel/internal/fixtures/add_record-request.json diff --git a/providers/dns/internal/selectel/fixtures/add_record.json b/providers/dns/internal/selectel/internal/fixtures/add_record.json similarity index 100% rename from providers/dns/internal/selectel/fixtures/add_record.json rename to providers/dns/internal/selectel/internal/fixtures/add_record.json diff --git a/providers/dns/internal/selectel/fixtures/domains.json b/providers/dns/internal/selectel/internal/fixtures/domains.json similarity index 100% rename from providers/dns/internal/selectel/fixtures/domains.json rename to providers/dns/internal/selectel/internal/fixtures/domains.json diff --git a/providers/dns/internal/selectel/fixtures/error.json b/providers/dns/internal/selectel/internal/fixtures/error.json similarity index 100% rename from providers/dns/internal/selectel/fixtures/error.json rename to providers/dns/internal/selectel/internal/fixtures/error.json diff --git a/providers/dns/internal/selectel/fixtures/list_records.json b/providers/dns/internal/selectel/internal/fixtures/list_records.json similarity index 100% rename from providers/dns/internal/selectel/fixtures/list_records.json rename to providers/dns/internal/selectel/internal/fixtures/list_records.json diff --git a/providers/dns/internal/selectel/types.go b/providers/dns/internal/selectel/internal/types.go similarity index 98% rename from providers/dns/internal/selectel/types.go rename to providers/dns/internal/selectel/internal/types.go index df7bb3fa7..e6ca792c0 100644 --- a/providers/dns/internal/selectel/types.go +++ b/providers/dns/internal/selectel/internal/types.go @@ -1,4 +1,4 @@ -package selectel +package internal import "fmt" diff --git a/providers/dns/internal/selectel/provider.go b/providers/dns/internal/selectel/provider.go new file mode 100644 index 000000000..495735736 --- /dev/null +++ b/providers/dns/internal/selectel/provider.go @@ -0,0 +1,137 @@ +// Package selectel implements a DNS provider for solving the DNS-01 challenge using Selectel Domains API. +package selectel + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/internal/selectel/internal" +) + +const MinTTL = 60 + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + Token string + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client + + // TODO(ldez): remove in v5? + BaseURL string +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client +} + +// NewDNSProviderConfig return a DNSProvider instance configured for selectel. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("the configuration of the DNS provider is nil") + } + + if config.Token == "" { + return nil, errors.New("credentials missing") + } + + if config.TTL < MinTTL { + return nil, fmt.Errorf("invalid TTL, TTL (%d) must be greater than %d", config.TTL, MinTTL) + } + + client := internal.NewClient(config.Token) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + var err error + + client.BaseURL, err = url.Parse(config.BaseURL) + if err != nil { + return nil, fmt.Errorf("%w", err) + } + + return &DNSProvider{config: config, client: client}, 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 +} + +// Present creates a TXT record to fulfill DNS-01 challenge. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx := context.Background() + + // TODO(ldez) replace domain by FQDN to follow CNAME. + domainObj, err := d.client.GetDomainByName(ctx, domain) + if err != nil { + return fmt.Errorf("get domain by name: %w", err) + } + + txtRecord := internal.Record{ + Type: "TXT", + TTL: d.config.TTL, + Name: info.EffectiveFQDN, + Content: info.Value, + } + + _, err = d.client.AddRecord(ctx, domainObj.ID, txtRecord) + if err != nil { + return fmt.Errorf("add record: %w", err) + } + + return nil +} + +// CleanUp removes a TXT record used for DNS-01 challenge. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + recordName := dns01.UnFqdn(info.EffectiveFQDN) + + ctx := context.Background() + + // TODO(ldez) replace domain by FQDN to follow CNAME. + domainObj, err := d.client.GetDomainByName(ctx, domain) + if err != nil { + return fmt.Errorf("%w", err) + } + + records, err := d.client.ListRecords(ctx, domainObj.ID) + if err != nil { + return fmt.Errorf("list records: %w", err) + } + + // Delete records with specific FQDN + var lastErr error + + for _, record := range records { + if record.Name == recordName { + err = d.client.DeleteRecord(ctx, domainObj.ID, record.ID) + if err != nil { + lastErr = fmt.Errorf("delete record: %w", err) + } + } + } + + return lastErr +} diff --git a/providers/dns/internal/selectel/provider_test.go b/providers/dns/internal/selectel/provider_test.go new file mode 100644 index 000000000..75a032bf4 --- /dev/null +++ b/providers/dns/internal/selectel/provider_test.go @@ -0,0 +1,55 @@ +package selectel + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + token string + ttl int + expected string + }{ + { + desc: "success", + token: "123", + ttl: 60, + }, + { + desc: "missing api key", + token: "", + ttl: 60, + expected: "credentials missing", + }, + { + desc: "bad TTL value", + token: "123", + ttl: 59, + expected: fmt.Sprintf("invalid TTL, TTL (59) must be greater than %d", MinTTL), + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := &Config{} + config.TTL = test.ttl + config.Token = test.token + + p, err := NewDNSProviderConfig(config) + + if test.expected == "" { + require.NoError(t, err) + require.NotNil(t, p) + assert.NotNil(t, p.config) + assert.NotNil(t, p.client) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} diff --git a/providers/dns/ionos/ionos.go b/providers/dns/ionos/ionos.go index fd35f502e..892370f5d 100644 --- a/providers/dns/ionos/ionos.go +++ b/providers/dns/ionos/ionos.go @@ -2,18 +2,14 @@ package ionos import ( - "context" "errors" "fmt" "net/http" - "strconv" - "strings" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/ionos" ) @@ -34,18 +30,12 @@ const minTTL = 300 var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // 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 -} +type Config = ionos.Config // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, minTTL), + TTL: env.GetOrDefaultInt(EnvTTL, ionos.MinTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 15*time.Minute), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ @@ -56,8 +46,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *ionos.Client + prv challenge.ProviderTimeout } // NewDNSProvider returns a DNSProvider instance configured for Ionos. @@ -80,129 +69,36 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("ionos: the configuration of the DNS provider is nil") } - if config.APIKey == "" { - return nil, errors.New("ionos: credentials missing") - } - - if config.TTL < minTTL { - return nil, fmt.Errorf("ionos: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) - } - - client, err := ionos.NewClient(config.APIKey) + provider, err := ionos.NewDNSProviderConfig(config, "") if err != nil { return nil, fmt.Errorf("ionos: %w", err) } - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{config: config, client: client}, nil + return &DNSProvider{prv: provider}, 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 + return d.prv.Timeout() } // Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, _, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - zones, err := d.client.ListZones(ctx) +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + err := d.prv.Present(domain, token, keyAuth) if err != nil { - return fmt.Errorf("ionos: failed to get zones: %w", err) - } - - name := dns01.UnFqdn(info.EffectiveFQDN) - - zone := findZone(zones, name) - if zone == nil { - return errors.New("ionos: no matching zone found for domain") - } - - filter := &ionos.RecordsFilter{ - Suffix: name, - RecordType: "TXT", - } - - records, err := d.client.GetRecords(ctx, zone.ID, filter) - if err != nil { - return fmt.Errorf("ionos: failed to get records (zone=%s): %w", zone.ID, err) - } - - records = append(records, ionos.Record{ - Name: name, - Content: info.Value, - TTL: d.config.TTL, - Type: "TXT", - }) - - err = d.client.ReplaceRecords(ctx, zone.ID, records) - if err != nil { - return fmt.Errorf("ionos: failed to create/update records (zone=%s): %w", zone.ID, err) + return fmt.Errorf("ionos: %w", err) } return nil } // CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - zones, err := d.client.ListZones(ctx) +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + err := d.prv.CleanUp(domain, token, keyAuth) if err != nil { - return fmt.Errorf("ionos: failed to get zones: %w", err) + return fmt.Errorf("ionos: %w", err) } - name := dns01.UnFqdn(info.EffectiveFQDN) - - zone := findZone(zones, name) - if zone == nil { - return errors.New("ionos: no matching zone found for domain") - } - - filter := &ionos.RecordsFilter{ - Suffix: name, - RecordType: "TXT", - } - - records, err := d.client.GetRecords(ctx, zone.ID, filter) - if err != nil { - return fmt.Errorf("ionos: failed to get records (zone=%s): %w", zone.ID, err) - } - - for _, record := range records { - if record.Name == name && record.Content == strconv.Quote(info.Value) { - err = d.client.RemoveRecord(ctx, zone.ID, record.ID) - if err != nil { - return fmt.Errorf("ionos: failed to remove record (zone=%s, record=%s): %w", zone.ID, record.ID, err) - } - - return nil - } - } - - return fmt.Errorf("ionos: failed to remove record, record not found (zone=%s, domain=%s, fqdn=%s, value=%s)", zone.ID, domain, info.EffectiveFQDN, info.Value) -} - -func findZone(zones []ionos.Zone, domain string) *ionos.Zone { - var result *ionos.Zone - - for _, zone := range zones { - if zone.Name != "" && strings.HasSuffix(domain, zone.Name) { - if result == nil || len(zone.Name) > len(result.Name) { - result = &zone - } - } - } - - return result + return nil } diff --git a/providers/dns/ionos/ionos_test.go b/providers/dns/ionos/ionos_test.go index 7b1f5af11..39dc0c511 100644 --- a/providers/dns/ionos/ionos_test.go +++ b/providers/dns/ionos/ionos_test.go @@ -9,9 +9,7 @@ import ( const envDomain = envNamespace + "DOMAIN" -var envTest = tester.NewEnvTest( - EnvAPIKey). - WithDomain(envDomain) +var envTest = tester.NewEnvTest(EnvAPIKey).WithDomain(envDomain) func TestNewDNSProvider(t *testing.T) { testCases := []struct { @@ -47,8 +45,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } @@ -92,8 +89,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/rimuhosting/rimuhosting.go b/providers/dns/rimuhosting/rimuhosting.go index 08d7ad413..7a7e99f60 100644 --- a/providers/dns/rimuhosting/rimuhosting.go +++ b/providers/dns/rimuhosting/rimuhosting.go @@ -2,7 +2,6 @@ package rimuhosting import ( - "context" "errors" "fmt" "net/http" @@ -11,7 +10,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/rimuhosting" ) @@ -30,19 +28,12 @@ const ( var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // 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 -} +type Config = rimuhosting.Config // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, 3600), + TTL: env.GetOrDefaultInt(EnvTTL, rimuhosting.DefaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ @@ -53,8 +44,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *rimuhosting.Client + prv challenge.ProviderTimeout } // NewDNSProvider returns a DNSProvider instance configured for RimuHosting. @@ -77,50 +67,19 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("rimuhosting: the configuration of the DNS provider is nil") } - if config.APIKey == "" { - return nil, errors.New("rimuhosting: incomplete credentials, missing API key") + provider, err := rimuhosting.NewDNSProviderConfig(config, "") + if err != nil { + return nil, fmt.Errorf("rimuhosting: %w", err) } - client := rimuhosting.NewClient(config.APIKey) - client.BaseURL = rimuhosting.DefaultRimuHostingBaseURL - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{config: config, client: client}, 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 + return &DNSProvider{prv: provider}, nil } // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - records, err := d.client.FindTXTRecords(ctx, dns01.UnFqdn(info.EffectiveFQDN)) + err := d.prv.Present(domain, token, keyAuth) if err != nil { - return fmt.Errorf("rimuhosting: failed to find record(s) for %s: %w", domain, err) - } - - actions := []rimuhosting.ActionParameter{ - rimuhosting.NewAddRecordAction(dns01.UnFqdn(info.EffectiveFQDN), info.Value, d.config.TTL), - } - - for _, record := range records { - actions = append(actions, rimuhosting.NewAddRecordAction(record.Name, record.Content, d.config.TTL)) - } - - _, err = d.client.DoActions(ctx, actions...) - if err != nil { - return fmt.Errorf("rimuhosting: failed to add record(s) for %s: %w", domain, err) + return fmt.Errorf("rimuhosting: %w", err) } return nil @@ -128,14 +87,16 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - action := rimuhosting.NewDeleteRecordAction(dns01.UnFqdn(info.EffectiveFQDN), info.Value) - - _, err := d.client.DoActions(context.Background(), action) + err := d.prv.CleanUp(domain, token, keyAuth) if err != nil { - return fmt.Errorf("rimuhosting: failed to delete record for %s: %w", domain, err) + return fmt.Errorf("rimuhosting: %w", err) } 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.prv.Timeout() +} diff --git a/providers/dns/rimuhosting/rimuhosting_test.go b/providers/dns/rimuhosting/rimuhosting_test.go index d8b086e25..878ec14da 100644 --- a/providers/dns/rimuhosting/rimuhosting_test.go +++ b/providers/dns/rimuhosting/rimuhosting_test.go @@ -46,7 +46,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } @@ -84,7 +84,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/selectel/selectel.go b/providers/dns/selectel/selectel.go index 804ef04d5..63ddd81ac 100644 --- a/providers/dns/selectel/selectel.go +++ b/providers/dns/selectel/selectel.go @@ -4,17 +4,14 @@ package selectel import ( - "context" "errors" "fmt" "net/http" - "net/url" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/selectel" ) @@ -31,25 +28,16 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) -const minTTL = 60 - var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. -type Config struct { - BaseURL string - Token string - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} +type Config = selectel.Config // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - BaseURL: env.GetOrDefaultString(EnvBaseURL, selectel.DefaultSelectelBaseURL), - TTL: env.GetOrDefaultInt(EnvTTL, minTTL), + BaseURL: env.GetOrDefaultString(EnvBaseURL, ""), + TTL: env.GetOrDefaultInt(EnvTTL, selectel.MinTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ @@ -60,8 +48,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *selectel.Client + prv challenge.ProviderTimeout } // NewDNSProvider returns a DNSProvider instance configured for Selectel Domains API. @@ -84,58 +71,17 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("selectel: the configuration of the DNS provider is nil") } - if config.Token == "" { - return nil, errors.New("selectel: credentials missing") - } - - if config.TTL < minTTL { - return nil, fmt.Errorf("selectel: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) - } - - client := selectel.NewClient(config.Token) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - var err error - - client.BaseURL, err = url.Parse(config.BaseURL) + provider, err := selectel.NewDNSProviderConfig(config) if err != nil { return nil, fmt.Errorf("selectel: %w", err) } - return &DNSProvider{config: config, client: client}, nil + return &DNSProvider{prv: provider}, 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 -} - -// Present creates a TXT record to fulfill DNS-01 challenge. +// Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - // TODO(ldez) replace domain by FQDN to follow CNAME. - domainObj, err := d.client.GetDomainByName(ctx, domain) - if err != nil { - return fmt.Errorf("selectel: %w", err) - } - - txtRecord := selectel.Record{ - Type: "TXT", - TTL: d.config.TTL, - Name: info.EffectiveFQDN, - Content: info.Value, - } - - _, err = d.client.AddRecord(ctx, domainObj.ID, txtRecord) + err := d.prv.Present(domain, token, keyAuth) if err != nil { return fmt.Errorf("selectel: %w", err) } @@ -143,36 +89,18 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return nil } -// CleanUp removes a TXT record used for DNS-01 challenge. +// CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - recordName := dns01.UnFqdn(info.EffectiveFQDN) - - ctx := context.Background() - - // TODO(ldez) replace domain by FQDN to follow CNAME. - domainObj, err := d.client.GetDomainByName(ctx, domain) + err := d.prv.CleanUp(domain, token, keyAuth) if err != nil { return fmt.Errorf("selectel: %w", err) } - records, err := d.client.ListRecords(ctx, domainObj.ID) - if err != nil { - return fmt.Errorf("selectel: %w", err) - } - - // Delete records with specific FQDN - var lastErr error - - for _, record := range records { - if record.Name == recordName { - err = d.client.DeleteRecord(ctx, domainObj.ID, record.ID) - if err != nil { - lastErr = fmt.Errorf("selectel: %w", err) - } - } - } - - return lastErr + 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.prv.Timeout() } diff --git a/providers/dns/selectel/selectel_test.go b/providers/dns/selectel/selectel_test.go index e3c36e226..a456f1358 100644 --- a/providers/dns/selectel/selectel_test.go +++ b/providers/dns/selectel/selectel_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/providers/dns/internal/selectel" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -46,8 +47,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - assert.NotNil(t, p.config) - assert.NotNil(t, p.client) + assert.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } @@ -77,7 +77,7 @@ func TestNewDNSProviderConfig(t *testing.T) { desc: "bad TTL value", token: "123", ttl: 59, - expected: fmt.Sprintf("selectel: invalid TTL, TTL (59) must be greater than %d", minTTL), + expected: fmt.Sprintf("selectel: invalid TTL, TTL (59) must be greater than %d", selectel.MinTTL), }, } @@ -92,8 +92,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - assert.NotNil(t, p.config) - assert.NotNil(t, p.client) + assert.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/uniteddomains/uniteddomains.go b/providers/dns/uniteddomains/uniteddomains.go index 0cb50c2af..683cab1fe 100644 --- a/providers/dns/uniteddomains/uniteddomains.go +++ b/providers/dns/uniteddomains/uniteddomains.go @@ -2,19 +2,14 @@ package uniteddomains import ( - "context" "errors" "fmt" "net/http" - "net/url" - "strconv" - "strings" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/ionos" ) @@ -30,18 +25,14 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) +const defaultBaseURL = "https://dnsapi.united-domains.de/dns" + const minTTL = 300 var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // 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 -} +type Config = ionos.Config // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { @@ -57,8 +48,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *ionos.Client + prv challenge.ProviderTimeout } // NewDNSProvider returns a DNSProvider instance configured for United-Domains. @@ -80,131 +70,36 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("uniteddomains: the configuration of the DNS provider is nil") } - if config.APIKey == "" { - return nil, errors.New("uniteddomains: credentials missing") - } - - if config.TTL < minTTL { - return nil, fmt.Errorf("uniteddomains: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) - } - - client, err := ionos.NewClient(config.APIKey) + provider, err := ionos.NewDNSProviderConfig(config, defaultBaseURL) if err != nil { return nil, fmt.Errorf("uniteddomains: %w", err) } - client.BaseURL, _ = url.Parse(ionos.DefaultUnitedDomainsBaseURL) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{config: config, client: client}, nil + return &DNSProvider{prv: provider}, 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 + return d.prv.Timeout() } // Present creates a TXT record using the specified parameters. -func (d *DNSProvider) Present(domain, _, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - zones, err := d.client.ListZones(ctx) +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + err := d.prv.Present(domain, token, keyAuth) if err != nil { - return fmt.Errorf("uniteddomains: failed to get zones: %w", err) - } - - name := dns01.UnFqdn(info.EffectiveFQDN) - - zone := findZone(zones, name) - if zone == nil { - return errors.New("uniteddomains: no matching zone found for domain") - } - - filter := &ionos.RecordsFilter{ - Suffix: name, - RecordType: "TXT", - } - - records, err := d.client.GetRecords(ctx, zone.ID, filter) - if err != nil { - return fmt.Errorf("uniteddomains: failed to get records (zone=%s): %w", zone.ID, err) - } - - records = append(records, ionos.Record{ - Name: name, - Content: info.Value, - TTL: d.config.TTL, - Type: "TXT", - }) - - err = d.client.ReplaceRecords(ctx, zone.ID, records) - if err != nil { - return fmt.Errorf("uniteddomains: failed to create/update records (zone=%s): %w", zone.ID, err) + return fmt.Errorf("uniteddomains: %w", err) } return nil } // CleanUp removes the TXT record matching the specified parameters. -func (d *DNSProvider) CleanUp(domain, _, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - zones, err := d.client.ListZones(ctx) +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + err := d.prv.CleanUp(domain, token, keyAuth) if err != nil { - return fmt.Errorf("uniteddomains: failed to get zones: %w", err) + return fmt.Errorf("uniteddomains: %w", err) } - name := dns01.UnFqdn(info.EffectiveFQDN) - - zone := findZone(zones, name) - if zone == nil { - return errors.New("uniteddomains: no matching zone found for domain") - } - - filter := &ionos.RecordsFilter{ - Suffix: name, - RecordType: "TXT", - } - - records, err := d.client.GetRecords(ctx, zone.ID, filter) - if err != nil { - return fmt.Errorf("uniteddomains: failed to get records (zone=%s): %w", zone.ID, err) - } - - for _, record := range records { - if record.Name == name && record.Content == strconv.Quote(info.Value) { - err = d.client.RemoveRecord(ctx, zone.ID, record.ID) - if err != nil { - return fmt.Errorf("uniteddomains: failed to remove record (zone=%s, record=%s): %w", zone.ID, record.ID, err) - } - - return nil - } - } - - return fmt.Errorf("uniteddomains: failed to remove record, record not found (zone=%s, domain=%s, fqdn=%s, value=%s)", zone.ID, domain, info.EffectiveFQDN, info.Value) -} - -func findZone(zones []ionos.Zone, domain string) *ionos.Zone { - var result *ionos.Zone - - for _, zone := range zones { - if zone.Name != "" && strings.HasSuffix(domain, zone.Name) { - if result == nil || len(zone.Name) > len(result.Name) { - result = &zone - } - } - } - - return result + return nil } diff --git a/providers/dns/uniteddomains/uniteddomains_test.go b/providers/dns/uniteddomains/uniteddomains_test.go index 841268ca2..93afb01ab 100644 --- a/providers/dns/uniteddomains/uniteddomains_test.go +++ b/providers/dns/uniteddomains/uniteddomains_test.go @@ -9,9 +9,7 @@ import ( const envDomain = envNamespace + "DOMAIN" -var envTest = tester.NewEnvTest( - EnvAPIKey). - WithDomain(envDomain) +var envTest = tester.NewEnvTest(EnvAPIKey).WithDomain(envDomain) func TestNewDNSProvider(t *testing.T) { testCases := []struct { @@ -47,8 +45,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } @@ -92,8 +89,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/vscale/vscale.go b/providers/dns/vscale/vscale.go index 01fae946d..a159db307 100644 --- a/providers/dns/vscale/vscale.go +++ b/providers/dns/vscale/vscale.go @@ -4,17 +4,14 @@ package vscale import ( - "context" "errors" "fmt" "net/http" - "net/url" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/selectel" ) @@ -31,25 +28,18 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) -const minTTL = 60 +const defaultBaseURL = "https://api.vscale.io/v1/domains" var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. -type Config struct { - BaseURL string - Token string - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} +type Config = selectel.Config // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - BaseURL: env.GetOrDefaultString(EnvBaseURL, selectel.DefaultVScaleBaseURL), - TTL: env.GetOrDefaultInt(EnvTTL, minTTL), + BaseURL: env.GetOrDefaultString(EnvBaseURL, defaultBaseURL), + TTL: env.GetOrDefaultInt(EnvTTL, selectel.MinTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ @@ -60,8 +50,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *selectel.Client + prv challenge.ProviderTimeout } // NewDNSProvider returns a DNSProvider instance configured for Vscale Domains API. @@ -84,58 +73,21 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("vscale: the configuration of the DNS provider is nil") } - if config.Token == "" { - return nil, errors.New("vscale: credentials missing") + if config.BaseURL == "" { + config.BaseURL = defaultBaseURL } - if config.TTL < minTTL { - return nil, fmt.Errorf("vscale: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL) - } - - client := selectel.NewClient(config.Token) - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - var err error - - client.BaseURL, err = url.Parse(config.BaseURL) + provider, err := selectel.NewDNSProviderConfig(config) if err != nil { return nil, fmt.Errorf("vscale: %w", err) } - return &DNSProvider{config: config, client: client}, nil + return &DNSProvider{prv: provider}, 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 -} - -// Present creates a TXT record to fulfill DNS-01 challenge. +// Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - // TODO(ldez) replace domain by FQDN to follow CNAME. - domainObj, err := d.client.GetDomainByName(ctx, domain) - if err != nil { - return fmt.Errorf("vscale: %w", err) - } - - txtRecord := selectel.Record{ - Type: "TXT", - TTL: d.config.TTL, - Name: info.EffectiveFQDN, - Content: info.Value, - } - - _, err = d.client.AddRecord(ctx, domainObj.ID, txtRecord) + err := d.prv.Present(domain, token, keyAuth) if err != nil { return fmt.Errorf("vscale: %w", err) } @@ -143,36 +95,18 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return nil } -// CleanUp removes a TXT record used for DNS-01 challenge. +// CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - recordName := dns01.UnFqdn(info.EffectiveFQDN) - - ctx := context.Background() - - // TODO(ldez) replace domain by FQDN to follow CNAME. - domainObj, err := d.client.GetDomainByName(ctx, domain) + err := d.prv.CleanUp(domain, token, keyAuth) if err != nil { return fmt.Errorf("vscale: %w", err) } - records, err := d.client.ListRecords(ctx, domainObj.ID) - if err != nil { - return fmt.Errorf("vscale: %w", err) - } - - // Delete records with specific FQDN - var lastErr error - - for _, record := range records { - if record.Name == recordName { - err = d.client.DeleteRecord(ctx, domainObj.ID, record.ID) - if err != nil { - lastErr = fmt.Errorf("vscale: %w", err) - } - } - } - - return lastErr + 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.prv.Timeout() } diff --git a/providers/dns/vscale/vscale_test.go b/providers/dns/vscale/vscale_test.go index f3bc15531..9012c7563 100644 --- a/providers/dns/vscale/vscale_test.go +++ b/providers/dns/vscale/vscale_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/providers/dns/internal/selectel" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -46,8 +47,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - assert.NotNil(t, p.config) - assert.NotNil(t, p.client) + assert.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } @@ -77,7 +77,7 @@ func TestNewDNSProviderConfig(t *testing.T) { desc: "bad TTL value", token: "123", ttl: 59, - expected: fmt.Sprintf("vscale: invalid TTL, TTL (59) must be greater than %d", minTTL), + expected: fmt.Sprintf("vscale: invalid TTL, TTL (59) must be greater than %d", selectel.MinTTL), }, } @@ -92,8 +92,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - assert.NotNil(t, p.config) - assert.NotNil(t, p.client) + assert.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/websupport/websupport.go b/providers/dns/websupport/websupport.go index 7f93653c9..4187ba32b 100644 --- a/providers/dns/websupport/websupport.go +++ b/providers/dns/websupport/websupport.go @@ -2,17 +2,15 @@ package websupport import ( - "context" "errors" "fmt" "net/http" - "strconv" "time" + "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/internal/active24" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" ) const baseAPIDomain = "websupport.sk" @@ -31,15 +29,7 @@ const ( ) // Config is used to configure the creation of the DNSProvider. -type Config struct { - APIKey string - Secret string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} +type Config = active24.Config // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { @@ -55,8 +45,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *active24.Client + prv challenge.ProviderTimeout } // NewDNSProvider returns a DNSProvider instance configured for Websupport. @@ -80,83 +69,29 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("websupport: the configuration of the DNS provider is nil") } - client, err := active24.NewClient(baseAPIDomain, config.APIKey, config.Secret) + provider, err := active24.NewDNSProviderConfig(config, baseAPIDomain) if err != nil { return nil, fmt.Errorf("websupport: %w", err) } - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - }, nil + return &DNSProvider{prv: provider}, nil } // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("websupport: could not find zone for domain %q: %w", domain, err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + err := d.prv.Present(domain, token, keyAuth) if err != nil { return fmt.Errorf("websupport: %w", err) } - serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) - if err != nil { - return fmt.Errorf("websupport: find service ID: %w", err) - } - - record := active24.Record{ - Type: "TXT", - Name: subDomain, - Content: info.Value, - TTL: d.config.TTL, - } - - err = d.client.CreateRecord(ctx, strconv.Itoa(serviceID), record) - if err != nil { - return fmt.Errorf("websupport: create record: %w", err) - } - return nil } // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + err := d.prv.CleanUp(domain, token, keyAuth) if err != nil { - return fmt.Errorf("websupport: could not find zone for domain %q: %w", domain, err) - } - - serviceID, err := d.findServiceID(ctx, dns01.UnFqdn(authZone)) - if err != nil { - return fmt.Errorf("websupport: find service ID: %w", err) - } - - recordID, err := d.findRecordID(ctx, strconv.Itoa(serviceID), info) - if err != nil { - return fmt.Errorf("websupport: find record ID: %w", err) - } - - err = d.client.DeleteRecord(ctx, strconv.Itoa(serviceID), strconv.Itoa(recordID)) - if err != nil { - return fmt.Errorf("websupport: delete record %w", err) + return fmt.Errorf("websupport: %w", err) } return nil @@ -165,58 +100,5 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { // 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 -} - -func (d *DNSProvider) findServiceID(ctx context.Context, domain string) (int, error) { - services, err := d.client.GetServices(ctx) - if err != nil { - return 0, fmt.Errorf("get services: %w", err) - } - - for _, service := range services { - if service.ServiceName != "domain" { - continue - } - - if service.Name != domain { - continue - } - - return service.ID, nil - } - - return 0, fmt.Errorf("service not found for domain: %s", domain) -} - -func (d *DNSProvider) findRecordID(ctx context.Context, serviceID string, info dns01.ChallengeInfo) (int, error) { - // NOTE(ldez): Despite the API documentation, the filter doesn't seem to work. - filter := active24.RecordFilter{ - Name: dns01.UnFqdn(info.EffectiveFQDN), - Type: []string{"TXT"}, - Content: info.Value, - } - - records, err := d.client.GetRecords(ctx, serviceID, filter) - if err != nil { - return 0, fmt.Errorf("get records: %w", err) - } - - for _, record := range records { - if record.Type != "TXT" { - continue - } - - if record.Name != dns01.UnFqdn(info.EffectiveFQDN) { - continue - } - - if record.Content != info.Value { - continue - } - - return record.ID, nil - } - - return 0, errors.New("no record found") + return d.prv.Timeout() } diff --git a/providers/dns/websupport/websupport_test.go b/providers/dns/websupport/websupport_test.go index c7b8572b5..196c9bab8 100644 --- a/providers/dns/websupport/websupport_test.go +++ b/providers/dns/websupport/websupport_test.go @@ -60,8 +60,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } @@ -110,8 +109,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/zonomi/zonomi.go b/providers/dns/zonomi/zonomi.go index e6eae08de..fe54b80fc 100644 --- a/providers/dns/zonomi/zonomi.go +++ b/providers/dns/zonomi/zonomi.go @@ -2,7 +2,6 @@ package zonomi import ( - "context" "errors" "fmt" "net/http" @@ -11,7 +10,6 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" "github.com/go-acme/lego/v4/providers/dns/internal/rimuhosting" ) @@ -27,22 +25,17 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) +const defaultBaseURL = "https://zonomi.com/app/dns/dyndns.jsp" + var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // 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 -} +type Config = rimuhosting.Config // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { return &Config{ - TTL: env.GetOrDefaultInt(EnvTTL, 3600), + TTL: env.GetOrDefaultInt(EnvTTL, rimuhosting.DefaultTTL), PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), HTTPClient: &http.Client{ @@ -53,8 +46,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *rimuhosting.Client + prv challenge.ProviderTimeout } // NewDNSProvider returns a DNSProvider instance configured for Zonomi. @@ -77,50 +69,19 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("zonomi: the configuration of the DNS provider is nil") } - if config.APIKey == "" { - return nil, errors.New("zonomi: incomplete credentials, missing API key") + provider, err := rimuhosting.NewDNSProviderConfig(config, defaultBaseURL) + if err != nil { + return nil, fmt.Errorf("zonomi: %w", err) } - client := rimuhosting.NewClient(config.APIKey) - client.BaseURL = rimuhosting.DefaultZonomiBaseURL - - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{config: config, client: client}, 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 + return &DNSProvider{prv: provider}, nil } // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - ctx := context.Background() - - records, err := d.client.FindTXTRecords(ctx, dns01.UnFqdn(info.EffectiveFQDN)) + err := d.prv.Present(domain, token, keyAuth) if err != nil { - return fmt.Errorf("zonomi: failed to find record(s) for %s: %w", domain, err) - } - - actions := []rimuhosting.ActionParameter{ - rimuhosting.NewAddRecordAction(dns01.UnFqdn(info.EffectiveFQDN), info.Value, d.config.TTL), - } - - for _, record := range records { - actions = append(actions, rimuhosting.NewAddRecordAction(record.Name, record.Content, d.config.TTL)) - } - - _, err = d.client.DoActions(ctx, actions...) - if err != nil { - return fmt.Errorf("zonomi: failed to add record(s) for %s: %w", domain, err) + return fmt.Errorf("zonomi: %w", err) } return nil @@ -128,14 +89,16 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { // CleanUp removes the TXT record matching the specified parameters. func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - action := rimuhosting.NewDeleteRecordAction(dns01.UnFqdn(info.EffectiveFQDN), info.Value) - - _, err := d.client.DoActions(context.Background(), action) + err := d.prv.CleanUp(domain, token, keyAuth) if err != nil { - return fmt.Errorf("zonomi: failed to delete record for %s: %w", domain, err) + return fmt.Errorf("zonomi: %w", err) } 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.prv.Timeout() +} diff --git a/providers/dns/zonomi/zonomi_test.go b/providers/dns/zonomi/zonomi_test.go index 0583f4a1c..2e13e937e 100644 --- a/providers/dns/zonomi/zonomi_test.go +++ b/providers/dns/zonomi/zonomi_test.go @@ -46,7 +46,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } @@ -84,7 +84,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } From 3f2ebf7ef1bba374ca8ae225ac3ed82deb6d0fd2 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sat, 29 Nov 2025 14:24:38 +0100 Subject: [PATCH 223/298] chore: improve issue templates --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + .github/ISSUE_TEMPLATE/feature_request.yml | 1 + .github/ISSUE_TEMPLATE/new_dns_provider.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index ecd9cb6a5..ea3fd9a3a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -45,6 +45,7 @@ body: - Through Bitnami - Through 1Panel - Through Zoraxy + - Through Certimate - go install - Other validations: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 33f7be155..7f6793167 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -24,6 +24,7 @@ body: - Through Bitnami - Through 1Panel - Through Zoraxy + - Through Certimate - go install - Other validations: diff --git a/.github/ISSUE_TEMPLATE/new_dns_provider.yml b/.github/ISSUE_TEMPLATE/new_dns_provider.yml index 9e9fe3c03..902951ed8 100644 --- a/.github/ISSUE_TEMPLATE/new_dns_provider.yml +++ b/.github/ISSUE_TEMPLATE/new_dns_provider.yml @@ -34,6 +34,7 @@ body: - Through Bitnami - Through 1Panel - Through Zoraxy + - Through Certimate - go install - Other validations: From fc5e0174b8baa7453a266132a9efded3a0ae7ab7 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sat, 29 Nov 2025 14:25:21 +0100 Subject: [PATCH 224/298] docs: update the number of supported DNS --- docs/content/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/_index.md b/docs/content/_index.md index 229435e7d..ba90ddc97 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -14,7 +14,7 @@ Let's Encrypt client and ACME library written in Go. - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): issues certificates for IP addresses - Support [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html): Renewal Information (ARI) Extension - Support [draft-ietf-acme-profiles-00](https://datatracker.ietf.org/doc/draft-ietf-acme-profiles/): Profiles Extension -- Comes with about [150 DNS providers]({{% ref "dns" %}}) +- Comes with about [170 DNS providers]({{% ref "dns" %}}) - Register with CA - Obtain certificates, both from scratch or with an existing CSR - Renew certificates From 5488fdf856e4ef8070459ba95c8b5285c351fa7c Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sat, 29 Nov 2025 14:29:35 +0100 Subject: [PATCH 225/298] Prepare release v4.29.0 --- CHANGELOG.md | 24 +++++++++++++++++++ acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31f8ff569..d3b7909e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,30 @@ Everybody thinks that the others will donate, but in the end, nobody does. So if you think that lego is worth it, please consider [donating](https://donate.ldez.dev). +## v4.29.0 + +- Release date: 2025-11-29 +- Tag: [v4.29.0](https://github.com/go-acme/lego/releases/tag/v4.29.0) + +### Added + +- **[dnsprovider]** Add DNS provider for United-Domains +- **[dnsprovider]** Add DNS provider for Gigahost.no +- **[dnsprovider]** Add DNS provider for EdgeCenter +- **[dnsprovider]** Add DNS provider for AlibabaCloud ESA +- **[dnsprovider]** edgeone: add zones mapping +- **[dnsprovider]** namecheap: add experimental proxy support + +### Changed + +- **[dnsprovider]** gandiv5: update base API URL + +### Fixed + +- **[dnsprovider]** hetzner: use int64 for IDs +- **[dnsprovider]** baiducloud: pagination and TTL +- **[dnsprovider]** inwx: fix API breaking changes with record IDs + ## v4.28.1 - Release date: 2025-11-06 diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index bcfbebb2a..eb5a3bb5e 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.28.1" + ourUserAgent = "xenolf-acme/4.29.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index b564906c1..3b0201927 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.28.1+dev-detach" +const defaultVersion = "v4.29.0+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 1e176ab9a..bcab98bfd 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.28.1" + ourUserAgent = "goacme-lego/4.29.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From 742741fe05743d86d02cc9856f8950d7d9c42061 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sat, 29 Nov 2025 14:30:15 +0100 Subject: [PATCH 226/298] Detach v4.29.0 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index eb5a3bb5e..1c2078b38 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 3b0201927..e0fbb90e2 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.29.0+dev-release" +const defaultVersion = "v4.29.0+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index bcab98bfd..bbffcdbd4 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From cc83c025b547f7aba231316cbe0f711c669dfb70 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 1 Dec 2025 20:50:46 +0100 Subject: [PATCH 227/298] autodns: use the right response structure (#2737) --- providers/dns/autodns/autodns.go | 5 +- providers/dns/autodns/internal/client.go | 35 +++-- providers/dns/autodns/internal/client_test.go | 140 ++++++++++++++--- .../internal/fixtures/add_record-request.json | 8 +- .../autodns/internal/fixtures/add_record.json | 45 ++++-- .../dns/autodns/internal/fixtures/error.json | 21 +++ .../fixtures/remove_record-request.json | 8 +- .../internal/fixtures/remove_record.json | 45 ++++-- providers/dns/autodns/internal/types.go | 142 +++++++++++++++--- 9 files changed, 370 insertions(+), 79 deletions(-) create mode 100644 providers/dns/autodns/internal/fixtures/error.json diff --git a/providers/dns/autodns/autodns.go b/providers/dns/autodns/autodns.go index 770bac99b..fc8e793b6 100644 --- a/providers/dns/autodns/autodns.go +++ b/providers/dns/autodns/autodns.go @@ -128,7 +128,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { Value: info.Value, }} - _, err := d.client.AddTxtRecords(context.Background(), info.EffectiveFQDN, records) + _, err := d.client.AddRecords(context.Background(), info.EffectiveFQDN, records) if err != nil { return fmt.Errorf("autodns: %w", err) } @@ -147,7 +147,8 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { Value: info.Value, }} - if err := d.client.RemoveTXTRecords(context.Background(), info.EffectiveFQDN, records); err != nil { + _, err := d.client.RemoveRecords(context.Background(), info.EffectiveFQDN, records) + if err != nil { return fmt.Errorf("autodns: %w", err) } diff --git a/providers/dns/autodns/internal/client.go b/providers/dns/autodns/internal/client.go index 547596f81..d92490a60 100644 --- a/providers/dns/autodns/internal/client.go +++ b/providers/dns/autodns/internal/client.go @@ -43,24 +43,22 @@ func NewClient(username, password string, clientContext int) *Client { } } -// AddTxtRecords adds TXT records. -func (c *Client) AddTxtRecords(ctx context.Context, domain string, records []*ResourceRecord) (*Zone, error) { +// AddRecords adds records. +func (c *Client) AddRecords(ctx context.Context, domain string, records []*ResourceRecord) (*DataZoneResponse, error) { zoneStream := &ZoneStream{Adds: records} return c.updateZone(ctx, domain, zoneStream) } -// RemoveTXTRecords removes TXT records. -func (c *Client) RemoveTXTRecords(ctx context.Context, domain string, records []*ResourceRecord) error { +// RemoveRecords removes records. +func (c *Client) RemoveRecords(ctx context.Context, domain string, records []*ResourceRecord) (*DataZoneResponse, error) { zoneStream := &ZoneStream{Removes: records} - _, err := c.updateZone(ctx, domain, zoneStream) - - return err + return c.updateZone(ctx, domain, zoneStream) } // https://github.com/InterNetX/domainrobot-api/blob/bdc8fe92a2f32fcbdb29e30bf6006ab446f81223/src/domainrobot.json#L21090 -func (c *Client) updateZone(ctx context.Context, domain string, zoneStream *ZoneStream) (*Zone, error) { +func (c *Client) updateZone(ctx context.Context, domain string, zoneStream *ZoneStream) (*DataZoneResponse, error) { endpoint := c.BaseURL.JoinPath("zone", domain, "_stream") req, err := newJSONRequest(ctx, http.MethodPost, endpoint, zoneStream) @@ -68,12 +66,12 @@ func (c *Client) updateZone(ctx context.Context, domain string, zoneStream *Zone return nil, err } - var zone *Zone - if err := c.do(req, &zone); err != nil { + var resp *DataZoneResponse + if err := c.do(req, &resp); err != nil { return nil, err } - return zone, nil + return resp, nil } func (c *Client) do(req *http.Request, result any) error { @@ -88,7 +86,7 @@ func (c *Client) do(req *http.Request, result any) error { defer func() { _ = resp.Body.Close() }() if resp.StatusCode/100 != 2 { - return errutils.NewUnexpectedResponseStatusCodeError(req, resp) + return parseError(req, resp) } if result == nil { @@ -131,3 +129,16 @@ func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, paylo return req, nil } + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/autodns/internal/client_test.go b/providers/dns/autodns/internal/client_test.go index 6fc31ca34..9b0968fdc 100644 --- a/providers/dns/autodns/internal/client_test.go +++ b/providers/dns/autodns/internal/client_test.go @@ -1,6 +1,7 @@ package internal import ( + "net/http" "net/http/httptest" "net/url" "testing" @@ -24,7 +25,7 @@ func mockBuilder() *servermock.Builder[*Client] { WithJSONHeaders()) } -func TestClient_AddTxtRecords(t *testing.T) { +func TestClient_AddRecords(t *testing.T) { client := mockBuilder(). Route("POST /zone/example.com/_stream", servermock.ResponseFromFixture("add_record.json"), @@ -33,28 +34,81 @@ func TestClient_AddTxtRecords(t *testing.T) { With("X-Domainrobot-Context", "123")). Build(t) - records := []*ResourceRecord{{}} + records := []*ResourceRecord{{ + Name: "example.com", + TTL: 600, + Type: "TXT", + Value: "txtTXTtxt", + }} - zone, err := client.AddTxtRecords(t.Context(), "example.com", records) + resp, err := client.AddRecords(t.Context(), "example.com", records) require.NoError(t, err) - expected := &Zone{ - Name: "example.com", - ResourceRecords: []*ResourceRecord{{ - Name: "example.com", - TTL: 120, - Type: "TXT", - Value: "txt", - Pref: 1, - }}, - Action: "xxx", - VirtualNameServer: "yyy", + expected := &DataZoneResponse{ + STID: "20251121-appf4923-126284", + CTID: "", + Messages: []ResponseMessage{ + { + Text: "string", + Messages: []string{ + "string", + }, + Objects: []GenericObject{ + { + Type: "string", + Value: "string", + }, + }, + Code: "string", + Status: "SUCCESS", + }, + }, + Status: &ResponseStatus{ + Code: "S0301", + Text: "Zone was updated successfully on the name server.", + Type: "SUCCESS", + }, + Object: nil, + Data: []Zone{ + { + Name: "example.com", + ResourceRecords: []ResourceRecord{ + { + Name: "example.com", + TTL: 120, + Type: "TXT", + Value: "txt", + Pref: 1, + }, + }, + Action: "xxx", + VirtualNameServer: "yyy", + }, + }, } - assert.Equal(t, expected, zone) + assert.Equal(t, expected, resp) } -func TestClient_RemoveTXTRecords(t *testing.T) { +func TestClient_AddRecords_error(t *testing.T) { + client := mockBuilder(). + Route("POST /zone/example.com/_stream", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusBadRequest)). + Build(t) + + records := []*ResourceRecord{{ + Name: "example.com", + TTL: 600, + Type: "TXT", + Value: "txtTXTtxt", + }} + + _, err := client.AddRecords(t.Context(), "example.com", records) + require.EqualError(t, err, `STID: 20251121-appf4923-126284, status: code: E0202002, text: Zone konnte auf dem Nameserver nicht aktualisiert werden., type: ERROR, message: code: EF02022, text: Der Zusatzeintrag wurde doppelt eingetragen., status: ERROR, object: OURDOMAIN.TLD@nsa7.schlundtech.de/rr[17]: _acme-challenge.www.whoami.int.OURDOMAIN.TLD TXT "rK2SJb_ZcrYefbfCKU6jZEANfEAJeOtSh1Fv8hkUoVc"`) +} + +func TestClient_RemoveRecords(t *testing.T) { client := mockBuilder(). Route("POST /zone/example.com/_stream", servermock.ResponseFromFixture("remove_record.json"), @@ -63,8 +117,58 @@ func TestClient_RemoveTXTRecords(t *testing.T) { With("X-Domainrobot-Context", "123")). Build(t) - records := []*ResourceRecord{{}} + records := []*ResourceRecord{{ + Name: "example.com", + TTL: 600, + Type: "TXT", + Value: "txtTXTtxt", + }} - err := client.RemoveTXTRecords(t.Context(), "example.com", records) + resp, err := client.RemoveRecords(t.Context(), "example.com", records) require.NoError(t, err) + + expected := &DataZoneResponse{ + STID: "20251121-appf4923-126284", + CTID: "", + Messages: []ResponseMessage{ + { + Text: "string", + Messages: []string{ + "string", + }, + Objects: []GenericObject{ + { + Type: "string", + Value: "string", + }, + }, + Code: "string", + Status: "SUCCESS", + }, + }, + Status: &ResponseStatus{ + Code: "S0301", + Text: "Zone was updated successfully on the name server.", + Type: "SUCCESS", + }, + Object: nil, + Data: []Zone{ + { + Name: "example.com", + ResourceRecords: []ResourceRecord{ + { + Name: "example.com", + TTL: 120, + Type: "TXT", + Value: "txt", + Pref: 1, + }, + }, + Action: "xxx", + VirtualNameServer: "yyy", + }, + }, + } + + assert.Equal(t, expected, resp) } diff --git a/providers/dns/autodns/internal/fixtures/add_record-request.json b/providers/dns/autodns/internal/fixtures/add_record-request.json index b798b4fbd..6105c77ac 100644 --- a/providers/dns/autodns/internal/fixtures/add_record-request.json +++ b/providers/dns/autodns/internal/fixtures/add_record-request.json @@ -1,10 +1,10 @@ { "adds": [ { - "name": "", - "ttl": 0, - "type": "", - "value": "" + "name": "example.com", + "ttl": 600, + "type": "TXT", + "value": "txtTXTtxt" } ], "rems": null diff --git a/providers/dns/autodns/internal/fixtures/add_record.json b/providers/dns/autodns/internal/fixtures/add_record.json index 4a95f0784..a0ce66ba6 100644 --- a/providers/dns/autodns/internal/fixtures/add_record.json +++ b/providers/dns/autodns/internal/fixtures/add_record.json @@ -1,14 +1,41 @@ { - "origin": "example.com", - "resourceRecords": [ + "stid": "20251121-appf4923-126284", + "messages": [ { - "name": "example.com", - "ttl": 120, - "type": "TXT", - "value": "txt", - "pref": 1 + "text": "string", + "notice": "string", + "messages": [ + "string" + ], + "objects": [ + { + "type": "string", + "value": "string" + } + ], + "code": "string", + "status": "SUCCESS" } ], - "action": "xxx", - "virtualNameServer": "yyy" + "status": { + "code": "S0301", + "text": "Zone was updated successfully on the name server.", + "type": "SUCCESS" + }, + "data": [ + { + "origin": "example.com", + "resourceRecords": [ + { + "name": "example.com", + "ttl": 120, + "type": "TXT", + "value": "txt", + "pref": 1 + } + ], + "action": "xxx", + "virtualNameServer": "yyy" + } + ] } diff --git a/providers/dns/autodns/internal/fixtures/error.json b/providers/dns/autodns/internal/fixtures/error.json new file mode 100644 index 000000000..2ed635d58 --- /dev/null +++ b/providers/dns/autodns/internal/fixtures/error.json @@ -0,0 +1,21 @@ +{ + "stid": "20251121-appf4923-126284", + "messages": [ + { + "text": "Der Zusatzeintrag wurde doppelt eingetragen.", + "objects": [ + { + "type": "OURDOMAIN.TLD@nsa7.schlundtech.de/rr[17]", + "value": "_acme-challenge.www.whoami.int.OURDOMAIN.TLD TXT \"rK2SJb_ZcrYefbfCKU6jZEANfEAJeOtSh1Fv8hkUoVc\"" + } + ], + "code": "EF02022", + "status": "ERROR" + } + ], + "status": { + "code": "E0202002", + "text": "Zone konnte auf dem Nameserver nicht aktualisiert werden.", + "type": "ERROR" + } +} diff --git a/providers/dns/autodns/internal/fixtures/remove_record-request.json b/providers/dns/autodns/internal/fixtures/remove_record-request.json index 0702c7367..92361403e 100644 --- a/providers/dns/autodns/internal/fixtures/remove_record-request.json +++ b/providers/dns/autodns/internal/fixtures/remove_record-request.json @@ -2,10 +2,10 @@ "adds": null, "rems": [ { - "name": "", - "ttl": 0, - "type": "", - "value": "" + "name": "example.com", + "ttl": 600, + "type": "TXT", + "value": "txtTXTtxt" } ] } diff --git a/providers/dns/autodns/internal/fixtures/remove_record.json b/providers/dns/autodns/internal/fixtures/remove_record.json index 4a95f0784..a0ce66ba6 100644 --- a/providers/dns/autodns/internal/fixtures/remove_record.json +++ b/providers/dns/autodns/internal/fixtures/remove_record.json @@ -1,14 +1,41 @@ { - "origin": "example.com", - "resourceRecords": [ + "stid": "20251121-appf4923-126284", + "messages": [ { - "name": "example.com", - "ttl": 120, - "type": "TXT", - "value": "txt", - "pref": 1 + "text": "string", + "notice": "string", + "messages": [ + "string" + ], + "objects": [ + { + "type": "string", + "value": "string" + } + ], + "code": "string", + "status": "SUCCESS" } ], - "action": "xxx", - "virtualNameServer": "yyy" + "status": { + "code": "S0301", + "text": "Zone was updated successfully on the name server.", + "type": "SUCCESS" + }, + "data": [ + { + "origin": "example.com", + "resourceRecords": [ + { + "name": "example.com", + "ttl": 120, + "type": "TXT", + "value": "txt", + "pref": 1 + } + ], + "action": "xxx", + "virtualNameServer": "yyy" + } + ] } diff --git a/providers/dns/autodns/internal/types.go b/providers/dns/autodns/internal/types.go index 93fd678ca..8a06f4889 100644 --- a/providers/dns/autodns/internal/types.go +++ b/providers/dns/autodns/internal/types.go @@ -1,33 +1,133 @@ package internal +import ( + "fmt" + "strings" +) + +type APIResponse[T any] struct { + STID string `json:"stid"` + CTID string `json:"ctid"` + Messages []ResponseMessage `json:"messages"` + Status *ResponseStatus `json:"status"` + Object *ResponseObject `json:"object"` + Data T `json:"data"` +} + +type APIError APIResponse[any] + +func (a *APIError) Error() string { + var parts []string + + if a.STID != "" { + parts = append(parts, fmt.Sprintf("STID: %s", a.STID)) + } + + if a.CTID != "" { + parts = append(parts, fmt.Sprintf("CTID: %s", a.CTID)) + } + + if a.Status != nil { + parts = append(parts, "status: "+a.Status.String()) + } + + for _, message := range a.Messages { + parts = append(parts, "message: "+message.String()) + } + + if a.Object != nil { + parts = append(parts, "object: "+a.Object.String()) + } + + return strings.Join(parts, ", ") +} + +type DataZoneResponse APIResponse[[]Zone] + type ResponseMessage struct { - Text string `json:"text"` - Messages []string `json:"messages"` - Objects []string `json:"objects"` - Code string `json:"code"` - Status string `json:"status"` + Text string `json:"text"` + Code string `json:"code"` + Status string `json:"status"` + Messages []string `json:"messages"` + Objects []GenericObject `json:"objects"` +} + +func (r ResponseMessage) String() string { + var parts []string + + if r.Code != "" { + parts = append(parts, "code: "+r.Code) + } + + if r.Text != "" { + parts = append(parts, "text: "+r.Text) + } + + if r.Status != "" { + parts = append(parts, "status: "+r.Status) + } + + if len(r.Messages) > 0 { + parts = append(parts, "messages: "+strings.Join(r.Messages, ";")) + } + + for _, object := range r.Objects { + parts = append(parts, fmt.Sprintf("object: %s", object)) + } + + return strings.Join(parts, ", ") +} + +type GenericObject struct { + Type string `json:"type"` + Value string `json:"value"` +} + +func (g GenericObject) String() string { + return g.Type + ": " + g.Value } type ResponseStatus struct { Code string `json:"code"` Text string `json:"text"` - Type string `json:"type"` + Type string `json:"type"` // SUCCESS, ERROR, NOTIFY, NOTICE, NICCOM_NOTIFY +} + +func (r ResponseStatus) String() string { + return fmt.Sprintf("code: %s, text: %s, type: %s", r.Code, r.Text, r.Type) } type ResponseObject struct { - Type string `json:"type"` - Value string `json:"value"` - Summary int32 `json:"summary"` - Data string + Type string `json:"type"` + Value string `json:"value"` + Summary int32 `json:"summary"` + Data *ResponseObjectData `json:"data"` } -type DataZoneResponse struct { - STID string `json:"stid"` - CTID string `json:"ctid"` - Messages []*ResponseMessage `json:"messages"` - Status *ResponseStatus `json:"status"` - Object any `json:"object"` - Data []*Zone `json:"data"` +func (r ResponseObject) String() string { + var parts []string + + if r.Type != "" { + parts = append(parts, fmt.Sprintf("type: %s", r.Type)) + } + + if r.Value != "" { + parts = append(parts, fmt.Sprintf("value: %s", r.Value)) + } + + if r.Summary != 0 { + parts = append(parts, fmt.Sprintf("summary: %d", r.Summary)) + } + + if r.Data != nil { + parts = append(parts, fmt.Sprintf("data: %s", r.Data.Description)) + } + + return strings.Join(parts, ", ") +} + +type ResponseObjectData struct { + Description string `json:"description"` } // ResourceRecord holds a resource record. @@ -43,10 +143,10 @@ type ResourceRecord struct { // Zone is an autodns zone record with all for us relevant fields. // https://help.internetx.com/display/APIXMLEN/Zone+Object type Zone struct { - Name string `json:"origin"` - ResourceRecords []*ResourceRecord `json:"resourceRecords"` - Action string `json:"action"` - VirtualNameServer string `json:"virtualNameServer"` + Name string `json:"origin"` + ResourceRecords []ResourceRecord `json:"resourceRecords"` + Action string `json:"action"` + VirtualNameServer string `json:"virtualNameServer"` } // ZoneStream body of the requests. From ea97ce2f62d340f9261cbd003f742fdf5fcc4470 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 1 Dec 2025 20:51:43 +0100 Subject: [PATCH 228/298] chore: move provider "manual" into a dedicated package (#2739) --- .golangci.yml | 4 + challenge/dns01/dns_challenge_manual.go | 5 + cmd/zz_gen_cmd_dnshelp.go | 14 ++- docs/content/dns/zz_gen_manual.md | 98 +++++++++++++++++++ internal/dns/docs/generator.go | 11 +-- internal/dns/docs/templates/dns.go.tmpl | 3 - internal/dns/providers/dns_providers.go.tmpl | 3 - providers/dns/manual/manual.go | 13 +++ .../dns/manual/manual.toml | 23 ++--- .../dns/manual/manual_test.go | 12 +-- providers/dns/zz_gen_dns_providers.go | 6 +- 11 files changed, 149 insertions(+), 43 deletions(-) create mode 100644 docs/content/dns/zz_gen_manual.md create mode 100644 providers/dns/manual/manual.go rename docs/content/dns/manual.md => providers/dns/manual/manual.toml (91%) rename challenge/dns01/dns_challenge_manual_test.go => providers/dns/manual/manual_test.go (73%) diff --git a/.golangci.yml b/.golangci.yml index 2b4bcc41b..ceb7cad85 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -263,6 +263,10 @@ linters: text: cyclomatic complexity 13 of func `\(\*DNSProvider\)\.CleanUp` is high linters: - gocyclo + - path: providers/dns/manual/manual.go + text: 'SA1019: dns01.DNSProviderManual is deprecated' + linters: + - staticcheck # Those elements have been replaced by non-exposed structures. - path: providers/dns/linode/linode_test.go text: 'SA1019: linodego\.(DomainsPagedResponse|DomainRecordsPagedResponse) is deprecated' diff --git a/challenge/dns01/dns_challenge_manual.go b/challenge/dns01/dns_challenge_manual.go index c00d64041..3821fc157 100644 --- a/challenge/dns01/dns_challenge_manual.go +++ b/challenge/dns01/dns_challenge_manual.go @@ -12,9 +12,14 @@ const ( ) // DNSProviderManual is an implementation of the ChallengeProvider interface. +// TODO(ldez): move this to providers/dns/manual +// +// Deprecated: Use the manual.DNSProvider instead. type DNSProviderManual struct{} // NewDNSProviderManual returns a DNSProviderManual instance. +// +// Deprecated: Use the manual.NewDNSProvider instead. func NewDNSProviderManual() (*DNSProviderManual, error) { return &DNSProviderManual{}, nil } diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 11cf01280..022374d7a 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -12,7 +12,6 @@ import ( func allDNSCodes() string { providers := []string{ - "manual", "acme-dns", "active24", "alidns", @@ -110,6 +109,7 @@ func allDNSCodes() string { "luadns", "mailinabox", "manageengine", + "manual", "metaname", "metaregistrar", "mijnhost", @@ -2270,6 +2270,16 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/manageengine`) + case "manual": + // generated from: providers/dns/manual/manual.toml + ew.writeln(`Configuration for Manual.`) + ew.writeln(`Code: 'manual'`) + ew.writeln(`Since: 'v0.3.0'`) + ew.writeln() + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/manual`) + case "metaname": // generated from: providers/dns/metaname/metaname.toml ew.writeln(`Configuration for Metaname.`) @@ -3828,8 +3838,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/zonomi`) - case "manual": - ew.writeln(`Solving the DNS-01 challenge using CLI prompt.`) default: return fmt.Errorf("%q is not yet supported", name) } diff --git a/docs/content/dns/zz_gen_manual.md b/docs/content/dns/zz_gen_manual.md new file mode 100644 index 000000000..0300d8400 --- /dev/null +++ b/docs/content/dns/zz_gen_manual.md @@ -0,0 +1,98 @@ +--- +title: "Manual" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: manual +dnsprovider: + since: "v0.3.0" + code: "manual" + url: "" +--- + + + + + +Solving the DNS-01 challenge using CLI prompt. + + + + +- Code: `manual` +- Since: v0.3.0 + + +Here is an example bash command using the Manual provider: + +```bash +lego --email you@example.com --dns manual -d '*.example.com' -d example.com run +``` + + + + +## Example + +To start using the CLI prompt "provider", start lego with `--dns manual`: + +```console +$ lego --email "you@example.com" --domains="example.com" --dns "manual" run +``` + +What follows are a few log print-outs, interspersed with some prompts, asking for you to do perform some actions: + +```txt +No key found for account you@example.com. Generating a P256 key. +Saved key to ./.lego/accounts/acme-v02.api.letsencrypt.org/you@example.com/keys/you@example.com.key +Please review the TOS at https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf +Do you accept the TOS? Y/n +``` + +If you accept the linked Terms of Service, hit `Enter`. + +```txt +[INFO] acme: Registering account for you@example.com +!!!! HEADS UP !!!! + + Your account credentials have been saved in your Let's Encrypt + configuration directory at "./.lego/accounts". + + You should make a secure backup of this folder now. This + configuration directory will also contain certificates and + private keys obtained from Let's Encrypt so making regular + backups of this folder is ideal. +[INFO] [example.com] acme: Obtaining bundled SAN certificate +[INFO] [example.com] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz-v3/2345678901 +[INFO] [example.com] acme: Could not find solver for: tls-alpn-01 +[INFO] [example.com] acme: Could not find solver for: http-01 +[INFO] [example.com] acme: use dns-01 solver +[INFO] [example.com] acme: Preparing to solve DNS-01 +lego: Please create the following TXT record in your example.com. zone: +_acme-challenge.example.com. 120 IN TXT "hX0dPkG6Gfs9hUvBAchQclkyyoEKbShbpvJ9mY5q2JQ" +lego: Press 'Enter' when you are done +``` + +Do as instructed, and create the TXT records, and hit `Enter`. + +```txt +[INFO] [example.com] acme: Trying to solve DNS-01 +[INFO] [example.com] acme: Checking DNS record propagation using [192.168.8.1:53] +[INFO] Wait for propagation [timeout: 1m0s, interval: 2s] +[INFO] [example.com] acme: Waiting for DNS record propagation. +[INFO] [example.com] The server validated our request +[INFO] [example.com] acme: Cleaning DNS-01 challenge +lego: You can now remove this TXT record from your example.com. zone: +_acme-challenge.example.com. 120 IN TXT "hX0dPkG6Gfs9hUvBAchQclkyyoEKbShbpvJ9mY5q2JQ" +[INFO] [example.com] acme: Validations succeeded; requesting certificates +[INFO] [example.com] Server responded with a certificate. +``` + +As mentioned, you can now remove the TXT record again. + + + + + + + + diff --git a/internal/dns/docs/generator.go b/internal/dns/docs/generator.go index c7f9ef8c7..9355d0d1b 100644 --- a/internal/dns/docs/generator.go +++ b/internal/dns/docs/generator.go @@ -190,14 +190,9 @@ func generateReadMe(models *descriptors.Providers) error { } func orderProviders(models *descriptors.Providers) [][]descriptors.Provider { - providers := append(models.Providers, descriptors.Provider{ - Name: "Manual", - Code: "manual", - }) - const nbCol = 4 - slices.SortFunc(providers, func(a, b descriptors.Provider) int { + slices.SortFunc(models.Providers, func(a, b descriptors.Provider) int { return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name)) }) @@ -206,13 +201,13 @@ func orderProviders(models *descriptors.Providers) [][]descriptors.Provider { row []descriptors.Provider ) - for i, p := range providers { + for i, p := range models.Providers { switch { case len(row) == nbCol: matrix = append(matrix, row) row = []descriptors.Provider{p} - case i == len(providers)-1: + case i == len(models.Providers)-1: row = append(row, p) for j := len(row); j < nbCol; j++ { row = append(row, descriptors.Provider{}) diff --git a/internal/dns/docs/templates/dns.go.tmpl b/internal/dns/docs/templates/dns.go.tmpl index e8b336254..c1896c91a 100644 --- a/internal/dns/docs/templates/dns.go.tmpl +++ b/internal/dns/docs/templates/dns.go.tmpl @@ -12,7 +12,6 @@ import ( func allDNSCodes() string { providers := []string{ - "manual", {{- range $provider := .Providers }} "{{ $provider.Code }}", {{- end}} @@ -48,8 +47,6 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/{{ $provider.Code }}`) {{end}} - case "manual": - ew.writeln(`Solving the DNS-01 challenge using CLI prompt.`) default: return fmt.Errorf("%q is not yet supported", name) } diff --git a/internal/dns/providers/dns_providers.go.tmpl b/internal/dns/providers/dns_providers.go.tmpl index 2030a3ed0..c974ef6a9 100644 --- a/internal/dns/providers/dns_providers.go.tmpl +++ b/internal/dns/providers/dns_providers.go.tmpl @@ -6,7 +6,6 @@ import ( "fmt" "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" {{- range $provider := .Providers }} "github.com/go-acme/lego/v4/providers/dns/{{ cleanName $provider.Code }}" {{- end}} @@ -15,8 +14,6 @@ import ( // NewDNSChallengeProviderByName Factory for DNS providers. func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { switch name { - case "manual": - return dns01.NewDNSProviderManual() {{- range $provider := .Providers }} case "{{ $provider.Code }}"{{range $alias := $provider.Aliases }},"{{ $alias }}"{{end}}: return {{ cleanName $provider.Code }}.NewDNSProvider() diff --git a/providers/dns/manual/manual.go b/providers/dns/manual/manual.go new file mode 100644 index 000000000..2985bc595 --- /dev/null +++ b/providers/dns/manual/manual.go @@ -0,0 +1,13 @@ +package manual + +import ( + "github.com/go-acme/lego/v4/challenge/dns01" +) + +// DNSProvider is an implementation of the ChallengeProvider interface. +type DNSProvider = dns01.DNSProviderManual + +// NewDNSProvider returns a DNSProvider instance. +func NewDNSProvider() (*DNSProvider, error) { + return &DNSProvider{}, nil +} diff --git a/docs/content/dns/manual.md b/providers/dns/manual/manual.toml similarity index 91% rename from docs/content/dns/manual.md rename to providers/dns/manual/manual.toml index 3f9cf0a8e..88acf4750 100644 --- a/docs/content/dns/manual.md +++ b/providers/dns/manual/manual.toml @@ -1,18 +1,13 @@ ---- -title: "Manual" -date: 2019-03-03T16:39:46+01:00 -draft: false -slug: manual -dnsprovider: - since: v0.3.0 - code: manual - url: ---- +Name = "Manual" +Description = '''Solving the DNS-01 challenge using CLI prompt.''' +Code = "manual" +Since = "v0.3.0" -Solving the DNS-01 challenge using CLI prompt. - - +Example = ''' +lego --email you@example.com --dns manual -d '*.example.com' -d example.com run +''' +Additional = ''' ## Example To start using the CLI prompt "provider", start lego with `--dns manual`: @@ -70,3 +65,5 @@ _acme-challenge.example.com. 120 IN TXT "hX0dPkG6Gfs9hUvBAchQclkyyoEKbShbpvJ9mY5 ``` As mentioned, you can now remove the TXT record again. + +''' diff --git a/challenge/dns01/dns_challenge_manual_test.go b/providers/dns/manual/manual_test.go similarity index 73% rename from challenge/dns01/dns_challenge_manual_test.go rename to providers/dns/manual/manual_test.go index c183822bb..7badd4b8b 100644 --- a/challenge/dns01/dns_challenge_manual_test.go +++ b/providers/dns/manual/manual_test.go @@ -1,22 +1,14 @@ -package dns01 +package manual import ( "io" "os" "testing" - "github.com/go-acme/lego/v4/platform/tester/dnsmock" - "github.com/miekg/dns" "github.com/stretchr/testify/require" ) func TestDNSProviderManual(t *testing.T) { - useAsNameserver(t, dnsmock.NewServer(). - Query("_acme-challenge.example.com. CNAME", dnsmock.Noop). - Query("_acme-challenge.example.com. SOA", dnsmock.Error(dns.RcodeNameError)). - Query("example.com. SOA", dnsmock.SOA("")). - Build(t)) - backupStdin := os.Stdin defer func() { os.Stdin = backupStdin }() @@ -52,7 +44,7 @@ func TestDNSProviderManual(t *testing.T) { os.Stdin = file - manualProvider, err := NewDNSProviderManual() + manualProvider, err := NewDNSProvider() require.NoError(t, err) err = manualProvider.Present("example.com", "", "") diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 2add1f75f..842d00c51 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/providers/dns/acmedns" "github.com/go-acme/lego/v4/providers/dns/active24" "github.com/go-acme/lego/v4/providers/dns/alidns" @@ -104,6 +103,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/luadns" "github.com/go-acme/lego/v4/providers/dns/mailinabox" "github.com/go-acme/lego/v4/providers/dns/manageengine" + "github.com/go-acme/lego/v4/providers/dns/manual" "github.com/go-acme/lego/v4/providers/dns/metaname" "github.com/go-acme/lego/v4/providers/dns/metaregistrar" "github.com/go-acme/lego/v4/providers/dns/mijnhost" @@ -181,8 +181,6 @@ import ( // NewDNSChallengeProviderByName Factory for DNS providers. func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { switch name { - case "manual": - return dns01.NewDNSProviderManual() case "acme-dns", "acmedns": return acmedns.NewDNSProvider() case "active24": @@ -377,6 +375,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return mailinabox.NewDNSProvider() case "manageengine": return manageengine.NewDNSProvider() + case "manual": + return manual.NewDNSProvider() case "metaname": return metaname.NewDNSProvider() case "metaregistrar": From bc163db9edd23bbfc3521086c0b570f468b9a87b Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 1 Dec 2025 20:55:41 +0100 Subject: [PATCH 229/298] feat: remove email requirement (#2736) --- cmd/accounts_storage.go | 43 ++++++++++++++++--------- cmd/cmd_list.go | 2 +- cmd/cmd_renew.go | 4 ++- cmd/setup.go | 11 +------ e2e/dnschallenge/dns_challenges_test.go | 1 - 5 files changed, 33 insertions(+), 28 deletions(-) diff --git a/cmd/accounts_storage.go b/cmd/accounts_storage.go index 1dbdfb84b..01db2faf8 100644 --- a/cmd/accounts_storage.go +++ b/cmd/accounts_storage.go @@ -16,6 +16,8 @@ import ( "github.com/urfave/cli/v2" ) +const userIDPlaceholder = "noemail@example.com" + const ( baseAccountsRootFolderName = "accounts" baseKeysFolderName = "keys" @@ -32,7 +34,7 @@ const ( // // rootUserPath: // -// ./.lego/accounts/localhost_14000/hubert@hubert.com/ +// ./.lego/accounts/localhost_14000/foo@example.com/ // │ │ │ └── userID ("email" option) // │ │ └── CA server ("server" option) // │ └── root accounts directory @@ -40,7 +42,7 @@ const ( // // keysPath: // -// ./.lego/accounts/localhost_14000/hubert@hubert.com/keys/ +// ./.lego/accounts/localhost_14000/foo@example.com/keys/ // │ │ │ │ └── root keys directory // │ │ │ └── userID ("email" option) // │ │ └── CA server ("server" option) @@ -49,7 +51,7 @@ const ( // // accountFilePath: // -// ./.lego/accounts/localhost_14000/hubert@hubert.com/account.json +// ./.lego/accounts/localhost_14000/foo@example.com/account.json // │ │ │ │ └── account file // │ │ │ └── userID ("email" option) // │ │ └── CA server ("server" option) @@ -57,6 +59,7 @@ const ( // └── "path" option type AccountsStorage struct { userID string + email string rootPath string rootUserPath string keysPath string @@ -66,8 +69,13 @@ type AccountsStorage struct { // NewAccountsStorage Creates a new AccountsStorage. func NewAccountsStorage(ctx *cli.Context) *AccountsStorage { - // TODO: move to account struct? Currently MUST pass email. - email := getEmail(ctx) + // TODO: move to account struct? + email := ctx.String(flgEmail) + + userID := email + if userID == "" { + userID = userIDPlaceholder + } serverURL, err := url.Parse(ctx.String(flgServer)) if err != nil { @@ -77,10 +85,11 @@ func NewAccountsStorage(ctx *cli.Context) *AccountsStorage { rootPath := filepath.Join(ctx.String(flgPath), baseAccountsRootFolderName) serverPath := strings.NewReplacer(":", "_", "/", string(os.PathSeparator)).Replace(serverURL.Host) accountsPath := filepath.Join(rootPath, serverPath) - rootUserPath := filepath.Join(accountsPath, email) + rootUserPath := filepath.Join(accountsPath, userID) return &AccountsStorage{ - userID: email, + userID: userID, + email: email, rootPath: rootPath, rootUserPath: rootUserPath, keysPath: filepath.Join(rootUserPath, baseKeysFolderName), @@ -112,6 +121,10 @@ func (s *AccountsStorage) GetUserID() string { return s.userID } +func (s *AccountsStorage) GetEmail() string { + return s.email +} + func (s *AccountsStorage) Save(account *Account) error { jsonBytes, err := json.MarshalIndent(account, "", "\t") if err != nil { @@ -124,14 +137,14 @@ func (s *AccountsStorage) Save(account *Account) error { func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account { fileBytes, err := os.ReadFile(s.accountFilePath) if err != nil { - log.Fatalf("Could not load file for account %s: %v", s.userID, err) + log.Fatalf("Could not load file for account %s: %v", s.GetUserID(), err) } var account Account err = json.Unmarshal(fileBytes, &account) if err != nil { - log.Fatalf("Could not parse file for account %s: %v", s.userID, err) + log.Fatalf("Could not parse file for account %s: %v", s.GetUserID(), err) } account.key = privateKey @@ -139,14 +152,14 @@ func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account { if account.Registration == nil || account.Registration.Body.Status == "" { reg, err := tryRecoverRegistration(s.ctx, privateKey) if err != nil { - log.Fatalf("Could not load account for %s. Registration is nil: %#v", s.userID, err) + log.Fatalf("Could not load account for %s. Registration is nil: %#v", s.GetUserID(), err) } account.Registration = reg err = s.Save(&account) if err != nil { - log.Fatalf("Could not save account for %s. Registration is nil: %#v", s.userID, err) + log.Fatalf("Could not save account for %s. Registration is nil: %#v", s.GetUserID(), err) } } @@ -154,15 +167,15 @@ func (s *AccountsStorage) LoadAccount(privateKey crypto.PrivateKey) *Account { } func (s *AccountsStorage) GetPrivateKey(keyType certcrypto.KeyType) crypto.PrivateKey { - accKeyPath := filepath.Join(s.keysPath, s.userID+".key") + accKeyPath := filepath.Join(s.keysPath, s.GetUserID()+".key") if _, err := os.Stat(accKeyPath); os.IsNotExist(err) { - log.Printf("No key found for account %s. Generating a %s key.", s.userID, keyType) + log.Printf("No key found for account %s. Generating a %s key.", s.GetUserID(), keyType) s.createKeysFolder() privateKey, err := generatePrivateKey(accKeyPath, keyType) if err != nil { - log.Fatalf("Could not generate RSA private account key for account %s: %v", s.userID, err) + log.Fatalf("Could not generate RSA private account key for account %s: %v", s.GetUserID(), err) } log.Printf("Saved key to %s", accKeyPath) @@ -180,7 +193,7 @@ func (s *AccountsStorage) GetPrivateKey(keyType certcrypto.KeyType) crypto.Priva func (s *AccountsStorage) createKeysFolder() { if err := createNonExistingFolder(s.keysPath); err != nil { - log.Fatalf("Could not check/create directory for account %s: %v", s.userID, err) + log.Fatalf("Could not check/create directory for account %s: %v", s.GetUserID(), err) } } diff --git a/cmd/cmd_list.go b/cmd/cmd_list.go index 864b85977..483592d47 100644 --- a/cmd/cmd_list.go +++ b/cmd/cmd_list.go @@ -36,7 +36,7 @@ func createList() *cli.Command { // fake email, needed by NewAccountsStorage &cli.StringFlag{ Name: flgEmail, - Value: "unknown", + Value: "", Hidden: true, }, }, diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index 99bc5ebbd..4b41ebc78 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -144,7 +144,9 @@ func renew(ctx *cli.Context) error { bundle := !ctx.Bool(flgNoBundle) - meta := map[string]string{hookEnvAccountEmail: account.Email} + meta := map[string]string{ + hookEnvAccountEmail: account.Email, + } // CSR if ctx.IsSet(flgCSR) { diff --git a/cmd/setup.go b/cmd/setup.go index 319b7680e..6d15adad3 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -40,7 +40,7 @@ func setupAccount(ctx *cli.Context, accountsStorage *AccountsStorage) (*Account, if accountsStorage.ExistsAccountFilePath() { account = accountsStorage.LoadAccount(privateKey) } else { - account = &Account{Email: accountsStorage.GetUserID(), key: privateKey} + account = &Account{Email: accountsStorage.GetEmail(), key: privateKey} } return account, keyType @@ -118,15 +118,6 @@ func getKeyType(ctx *cli.Context) certcrypto.KeyType { return "" } -func getEmail(ctx *cli.Context) string { - email := ctx.String(flgEmail) - if email == "" { - log.Fatalf("You have to pass an account (email address) to the program using --%s or -m", flgEmail) - } - - return email -} - func getUserAgent(ctx *cli.Context) string { return strings.TrimSpace(fmt.Sprintf("%s lego-cli/%s", ctx.String(flgUserAgent), ctx.App.Version)) } diff --git a/e2e/dnschallenge/dns_challenges_test.go b/e2e/dnschallenge/dns_challenges_test.go index 509b57bb1..9dd9ab0d6 100644 --- a/e2e/dnschallenge/dns_challenges_test.go +++ b/e2e/dnschallenge/dns_challenges_test.go @@ -58,7 +58,6 @@ func TestChallengeDNS_Run(t *testing.T) { loader.CleanLegoFiles() err := load.RunLego( - "-m", "hubert@hubert.com", "--accept-tos", "--dns", "exec", "--dns.resolvers", ":8053", From dea97e4dfaf3024edcbc959c3efa3556cdfa267c Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 1 Dec 2025 21:37:20 +0100 Subject: [PATCH 230/298] Add DNS provider for Gravity (#2738) --- README.md | 54 ++-- cmd/zz_gen_cmd_dnshelp.go | 23 ++ docs/content/dns/zz_gen_gravity.md | 71 +++++ docs/data/zz_cli_help.toml | 2 +- go.mod | 2 +- providers/dns/gravity/gravity.go | 205 ++++++++++++++ providers/dns/gravity/gravity.toml | 26 ++ providers/dns/gravity/gravity_test.go | 254 ++++++++++++++++++ providers/dns/gravity/internal/client.go | 234 ++++++++++++++++ providers/dns/gravity/internal/client_test.go | 160 +++++++++++ .../fixtures/create_record-request.json | 6 + .../dns/gravity/internal/fixtures/error.json | 8 + .../internal/fixtures/login-request.json | 4 + .../dns/gravity/internal/fixtures/login.json | 3 + .../dns/gravity/internal/fixtures/me.json | 16 ++ .../internal/fixtures/me_unauthenticated.json | 5 + .../dns/gravity/internal/fixtures/zones.json | 19 ++ .../internal/fixtures/zones_empty.json | 3 + providers/dns/gravity/internal/types.go | 82 ++++++ providers/dns/zz_gen_dns_providers.go | 3 + 20 files changed, 1151 insertions(+), 29 deletions(-) create mode 100644 docs/content/dns/zz_gen_gravity.md create mode 100644 providers/dns/gravity/gravity.go create mode 100644 providers/dns/gravity/gravity.toml create mode 100644 providers/dns/gravity/gravity_test.go create mode 100644 providers/dns/gravity/internal/client.go create mode 100644 providers/dns/gravity/internal/client_test.go create mode 100644 providers/dns/gravity/internal/fixtures/create_record-request.json create mode 100644 providers/dns/gravity/internal/fixtures/error.json create mode 100644 providers/dns/gravity/internal/fixtures/login-request.json create mode 100644 providers/dns/gravity/internal/fixtures/login.json create mode 100644 providers/dns/gravity/internal/fixtures/me.json create mode 100644 providers/dns/gravity/internal/fixtures/me_unauthenticated.json create mode 100644 providers/dns/gravity/internal/fixtures/zones.json create mode 100644 providers/dns/gravity/internal/fixtures/zones_empty.json create mode 100644 providers/dns/gravity/internal/types.go diff --git a/README.md b/README.md index 42bf26c73..dddcec72d 100644 --- a/README.md +++ b/README.md @@ -142,137 +142,137 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Go Daddy Google Cloud Google Domains - Hetzner + Gravity + Hetzner Hosting.de Hostinger Hosttech - HTTP request + HTTP request http.net Huawei Cloud Hurricane Electric DNS - HyperOne + HyperOne IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox - Infomaniak + Infomaniak Internet Initiative Japan Internet.bs INWX - Ionos + Ionos IPv64 iwantmyname (Deprecated) Joker - Joohoi's ACME-DNS + Joohoi's ACME-DNS KeyHelp Liara Lima-City - Linode (v4) + Linode (v4) Liquid Web Loopia LuaDNS - Mail-in-a-Box + Mail-in-a-Box ManageEngine CloudDNS Manual Metaname - Metaregistrar + Metaregistrar mijn.host Mittwald myaddr.{tools,dev,io} - MyDNS.jp + MyDNS.jp MythicBeasts Name.com Namecheap - Namesilo + Namesilo NearlyFreeSpeech.NET Netcup Netlify - Nicmanager + Nicmanager NIFCloud Njalla Nodion - NS1 + NS1 Octenium Open Telekom Cloud Oracle Cloud - OVH + OVH plesk.com Porkbun PowerDNS - Rackspace + Rackspace Rain Yun/雨云 RcodeZero reg.ru - Regfish + Regfish RFC2136 RimuHosting RU CENTER - Sakura Cloud + Sakura Cloud Scaleway Selectel Selectel v2 - SelfHost.(de|eu) + SelfHost.(de|eu) Servercow Shellrent Simply.com - Sonic + Sonic Spaceship Stackpath Technitium - Tencent Cloud DNS + Tencent Cloud DNS Tencent EdgeOne Timeweb Cloud TransIP - UKFast SafeDNS + UKFast SafeDNS Ultradns United-Domains Variomedia - VegaDNS + VegaDNS Vercel Versio.[nl|eu|uk] VinylDNS - VK Cloud + VK Cloud Volcano Engine/火山引擎 Vscale Vultr - webnames.ca + webnames.ca webnames.ru Websupport WEDOS - West.cn/西部数码 + West.cn/西部数码 Yandex 360 Yandex Cloud Yandex PDD - Zone.ee + Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 022374d7a..2695f0dba 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -79,6 +79,7 @@ func allDNSCodes() string { "glesys", "godaddy", "googledomains", + "gravity", "hetzner", "hostingde", "hostinger", @@ -1636,6 +1637,28 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/googledomains`) + case "gravity": + // generated from: providers/dns/gravity/gravity.toml + ew.writeln(`Configuration for Gravity.`) + ew.writeln(`Code: 'gravity'`) + ew.writeln(`Since: 'v4.30.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "GRAVITY_PASSWORD": Password`) + ew.writeln(` - "GRAVITY_SERVER_URL": URL of the server`) + ew.writeln(` - "GRAVITY_USERNAME": Username`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "GRAVITY_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "GRAVITY_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "GRAVITY_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "GRAVITY_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 1)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/gravity`) + case "hetzner": // generated from: providers/dns/hetzner/hetzner.toml ew.writeln(`Configuration for Hetzner.`) diff --git a/docs/content/dns/zz_gen_gravity.md b/docs/content/dns/zz_gen_gravity.md new file mode 100644 index 000000000..42d5e6128 --- /dev/null +++ b/docs/content/dns/zz_gen_gravity.md @@ -0,0 +1,71 @@ +--- +title: "Gravity" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: gravity +dnsprovider: + since: "v4.30.0" + code: "gravity" + url: "https://gravity.beryju.io/" +--- + + + + + + +Configuration for [Gravity](https://gravity.beryju.io/). + + + + +- Code: `gravity` +- Since: v4.30.0 + + +Here is an example bash command using the Gravity provider: + +```bash +GRAVITY_SERVER_URL="https://example.org:1234" \ +GRAVITY_USERNAME="xxxxxxxxxxxxxxxxxxxxx" \ +GRAVITY_PASSWORD="yyyyyyyyyyyyyyyyyyyyy" \ +lego --email you@example.com --dns gravity -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `GRAVITY_PASSWORD` | Password | +| `GRAVITY_SERVER_URL` | URL of the server | +| `GRAVITY_USERNAME` | Username | + +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 | +|--------------------------------|-------------| +| `GRAVITY_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `GRAVITY_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `GRAVITY_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `GRAVITY_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 1) | + +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://gravity.beryju.io/docs/api/reference/) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index f9401b0e3..51c6d39f3 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/go.mod b/go.mod index f68c2c9eb..cd019fac3 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.4.0 github.com/google/go-cmp v0.7.0 github.com/google/go-querystring v1.1.0 + github.com/google/uuid v1.6.0 github.com/gophercloud/gophercloud v1.14.1 github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/hashicorp/go-retryablehttp v0.7.8 @@ -157,7 +158,6 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect diff --git a/providers/dns/gravity/gravity.go b/providers/dns/gravity/gravity.go new file mode 100644 index 000000000..c8594441a --- /dev/null +++ b/providers/dns/gravity/gravity.go @@ -0,0 +1,205 @@ +// Package gravity implements a DNS provider for solving the DNS-01 challenge using Gravity. +package gravity + +import ( + "context" + "errors" + "fmt" + "net/http" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/gravity/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/google/uuid" +) + +// Environment variables names. +const ( + envNamespace = "GRAVITY_" + + EnvUsername = envNamespace + "USERNAME" + EnvPassword = envNamespace + "PASSWORD" + EnvServerURL = envNamespace + "SERVER_URL" + + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" + EnvSequenceInterval = envNamespace + "SEQUENCE_INTERVAL" +) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + Username string + Password string + ServerURL string + + PropagationTimeout time.Duration + PollingInterval time.Duration + SequenceInterval time.Duration + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + SequenceInterval: env.GetOrDefaultSecond(EnvSequenceInterval, 1*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client + + records map[string]internal.Record + recordsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for Gravity. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvUsername, EnvPassword, EnvServerURL) + if err != nil { + return nil, fmt.Errorf("gravity: %w", err) + } + + config := NewDefaultConfig() + config.Username = values[EnvUsername] + config.Password = values[EnvPassword] + config.ServerURL = values[EnvServerURL] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Gravity. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("gravity: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.ServerURL, config.Username, config.Password) + if err != nil { + return nil, fmt.Errorf("gravity: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + records: make(map[string]internal.Record), + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + _, err := d.client.Login(ctx) + if err != nil { + return fmt.Errorf("gravity: login: %w", err) + } + + zone, err := d.findZone(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("gravity: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone) + if err != nil { + return fmt.Errorf("gravity: %w", err) + } + + id := uuid.New() + + record := internal.Record{ + Data: info.Value, + Hostname: subDomain, + Type: "TXT", + UID: id.String(), + } + + err = d.client.CreateDNSRecord(ctx, zone, record) + if err != nil { + return fmt.Errorf("gravity: create DNS record: %w", err) + } + + d.recordsMu.Lock() + + record.Fqdn = zone + d.records[token] = record + d.recordsMu.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) + + d.recordsMu.Lock() + record, ok := d.records[token] + d.recordsMu.Unlock() + + if !ok { + return fmt.Errorf("gravity: unknown record for '%s' '%s'", info.EffectiveFQDN, token) + } + + err := d.client.DeleteDNSRecord(context.Background(), record.Fqdn, record) + if err != nil { + return fmt.Errorf("gravity: delete record: %w", err) + } + + 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 +} + +// Sequential implements the [dns01.sequential] interface. +// It changes the behavior of the provider to resolve DNS challenges sequentially. +// Returns the interval between each iteration. +// +// Gravity supports adding multiple records for the same domain, but the DNS server doesn't work as expected: +// if you call the DNS server, it will answer only the latest record instead of all of them. +func (d *DNSProvider) Sequential() time.Duration { + return d.config.SequenceInterval +} + +func (d *DNSProvider) findZone(ctx context.Context, effectiveFQDN string) (string, error) { + var zone string + + for fqdn := range dns01.DomainsSeq(effectiveFQDN) { + zones, err := d.client.GetDNSZones(ctx, fqdn) + if err != nil { + return "", fmt.Errorf("get DNS zones: %w", err) + } + + if len(zones) != 0 { + zone = zones[0].Name + break + } + } + + if zone == "" { + return "", fmt.Errorf("could not find zone for %q", effectiveFQDN) + } + + return zone, nil +} diff --git a/providers/dns/gravity/gravity.toml b/providers/dns/gravity/gravity.toml new file mode 100644 index 000000000..6010e26e1 --- /dev/null +++ b/providers/dns/gravity/gravity.toml @@ -0,0 +1,26 @@ +Name = "Gravity" +Description = '''''' +URL = "https://gravity.beryju.io/" +Code = "gravity" +Since = "v4.30.0" + +Example = ''' +GRAVITY_SERVER_URL="https://example.org:1234" \ +GRAVITY_USERNAME="xxxxxxxxxxxxxxxxxxxxx" \ +GRAVITY_PASSWORD="yyyyyyyyyyyyyyyyyyyyy" \ +lego --email you@example.com --dns gravity -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + GRAVITY_SERVER_URL = "URL of the server" + GRAVITY_USERNAME = "Username" + GRAVITY_PASSWORD = "Password" + [Configuration.Additional] + GRAVITY_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + GRAVITY_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + GRAVITY_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 1)" + GRAVITY_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://gravity.beryju.io/docs/api/reference/" diff --git a/providers/dns/gravity/gravity_test.go b/providers/dns/gravity/gravity_test.go new file mode 100644 index 000000000..b59b856fe --- /dev/null +++ b/providers/dns/gravity/gravity_test.go @@ -0,0 +1,254 @@ +package gravity + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/go-acme/lego/v4/providers/dns/gravity/internal" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest( + EnvUsername, + EnvPassword, + EnvServerURL, +).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvUsername: "user", + EnvPassword: "secret", + EnvServerURL: "https://example.org:1234", + }, + }, + { + desc: "missing EnvUsername", + envVars: map[string]string{ + EnvUsername: "", + EnvPassword: "secret", + EnvServerURL: "https://example.org:1234", + }, + expected: "gravity: some credentials information are missing: GRAVITY_USERNAME", + }, + { + desc: "missing EnvPassword", + envVars: map[string]string{ + EnvUsername: "user", + EnvPassword: "", + EnvServerURL: "https://example.org:1234", + }, + expected: "gravity: some credentials information are missing: GRAVITY_PASSWORD", + }, + { + desc: "missing EnvServerURL", + envVars: map[string]string{ + EnvUsername: "user", + EnvPassword: "secret", + EnvServerURL: "", + }, + expected: "gravity: some credentials information are missing: GRAVITY_SERVER_URL", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "gravity: some credentials information are missing: GRAVITY_USERNAME,GRAVITY_PASSWORD,GRAVITY_SERVER_URL", + }, + } + + 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 + username string + password string + serverURL string + expected string + }{ + { + desc: "success", + username: "user", + password: "secret", + serverURL: "https://example.org:1234", + }, + { + desc: "missing username", + username: "", + password: "secret", + serverURL: "https://example.org:1234", + expected: "gravity: credentials missing", + }, + { + desc: "missing password", + username: "user", + password: "", + serverURL: "https://example.org:1234", + expected: "gravity: credentials missing", + }, + { + desc: "missing server URL", + username: "user", + password: "secret", + serverURL: "", + expected: "gravity: server URL missing", + }, + { + desc: "missing credentials", + expected: "gravity: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Username = test.username + config.Password = test.password + config.ServerURL = test.serverURL + + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + + config.Username = "user" + config.Password = "secret" + config.ServerURL = server.URL + + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + return p, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("POST /api/v1/auth/login", + servermock.ResponseFromInternal("login.json"), + servermock.CheckRequestJSONBodyFromInternal("login-request.json")). + Route("GET /api/v1/dns/", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if req.URL.Query().Get("name") != "example.com." { + servermock.ResponseFromInternal("zones.json").ServeHTTP(rw, req) + return + } + + servermock.ResponseFromInternal("zones_empty.json").ServeHTTP(rw, req) + }), + ). + Route("POST /api/v1/dns/zones/records", + servermock.Noop(). + WithStatusCode(http.StatusNoContent), + servermock.CheckQueryParameter().Strict(). + With("zone", "example.com."). + WithRegexp("uid", `\w{8}-\w{4}-\w{4}-\w{4}-\w{12}`). + With("hostname", "_acme-challenge")). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("DELETE /api/v1/dns/zones/records", + servermock.Noop(). + WithStatusCode(http.StatusNoContent), + servermock.CheckQueryParameter().Strict(). + With("zone", "example.com."). + With("uid", "123"). + With("type", "TXT"). + With("hostname", "_acme-challenge")). + Build(t) + + provider.records["abc"] = internal.Record{ + Fqdn: "example.com.", + Hostname: "_acme-challenge", + Type: "TXT", + UID: "123", + } + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/gravity/internal/client.go b/providers/dns/gravity/internal/client.go new file mode 100644 index 000000000..41c6294c3 --- /dev/null +++ b/providers/dns/gravity/internal/client.go @@ -0,0 +1,234 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/cookiejar" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" + "golang.org/x/net/publicsuffix" +) + +// Client the Gravity API client. +type Client struct { + username string + password string + + baseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(serverURL, username, password string) (*Client, error) { + if username == "" || password == "" { + return nil, errors.New("credentials missing") + } + + if serverURL == "" { + return nil, errors.New("server URL missing") + } + + baseURL, err := url.Parse(serverURL) + if err != nil { + return nil, err + } + + return &Client{ + username: username, + password: password, + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) Login(ctx context.Context) (*Auth, error) { + jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) + if err != nil { + return nil, err + } + + c.HTTPClient.Jar = jar + + login := Login{ + Username: c.username, + Password: c.password, + } + + endpoint := c.baseURL.JoinPath("api", "v1", "auth", "login") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, login) + if err != nil { + return nil, err + } + + result := &Auth{} + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result, nil +} + +func (c *Client) Me(ctx context.Context) (*UserInfo, error) { + endpoint := c.baseURL.JoinPath("api", "v1", "auth", "me") + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + result := &UserInfo{} + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result, err +} + +func (c *Client) GetDNSZones(ctx context.Context, name string) ([]Zone, error) { + endpoint := c.baseURL.JoinPath("api", "v1", "dns", "zones") + + if name != "" { + query := endpoint.Query() + query.Set("name", name) + endpoint.RawQuery = query.Encode() + } + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + result := Zones{} + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result.Zones, nil +} + +func (c *Client) CreateDNSRecord(ctx context.Context, zone string, record Record) error { + endpoint := c.baseURL.JoinPath("api", "v1", "dns", "zones", "records") + + query := endpoint.Query() + + query.Set("zone", zone) + query.Set("hostname", record.Hostname) + + // When the UID is the same as an existing one, the record is updated, else a new record is created. + // An explicit UID is not required to create a record. + if record.UID != "" { + query.Set("uid", record.UID) + } + + endpoint.RawQuery = query.Encode() + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) DeleteDNSRecord(ctx context.Context, zone string, record Record) error { + endpoint := c.baseURL.JoinPath("api", "v1", "dns", "zones", "records") + + query := endpoint.Query() + + query.Set("zone", zone) + query.Set("hostname", record.Hostname) + query.Set("uid", record.UID) + query.Set("type", record.Type) + + endpoint.RawQuery = query.Encode() + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) do(req *http.Request, result any) error { + useragent.SetHeader(req.Header) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/gravity/internal/client_test.go b/providers/dns/gravity/internal/client_test.go new file mode 100644 index 000000000..98b17c59e --- /dev/null +++ b/providers/dns/gravity/internal/client_test.go @@ -0,0 +1,160 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(server.URL, "user", "secret") + if err != nil { + return nil, err + } + + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(), + ) +} + +func TestClient_Login(t *testing.T) { + client := mockBuilder(). + Route("POST /api/v1/auth/login", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + http.SetCookie(rw, &http.Cookie{ + Name: "gravity_session", + Value: "session_id", + Path: "/", + }) + + servermock.ResponseFromFixture("login.json").ServeHTTP(rw, req) + }), + servermock.CheckRequestJSONBodyFromFixture("login-request.json")). + Build(t) + + auth, err := client.Login(t.Context()) + require.NoError(t, err) + + cookies := client.HTTPClient.Jar.Cookies(client.baseURL) + + require.Len(t, cookies, 1) + + assert.Equal(t, "gravity_session", cookies[0].Name) + assert.Equal(t, "session_id", cookies[0].Value) + + expected := &Auth{Successful: true} + + assert.Equal(t, expected, auth) +} + +func TestClient_Login_error(t *testing.T) { + client := mockBuilder(). + Route("POST /api/v1/auth/login", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + _, err := client.Login(t.Context()) + require.EqualError(t, err, "status: UNAUTHENTICATED, error: unauthenticated, additionalProp1: string") +} + +func TestClient_Me(t *testing.T) { + client := mockBuilder(). + Route("GET /api/v1/auth/me", + servermock.ResponseFromFixture("me.json")). + Build(t) + + info, err := client.Me(t.Context()) + require.NoError(t, err) + + expected := &UserInfo{ + Username: "admin", + Authenticated: true, + Permissions: []Permission{{ + Methods: []string{"GET", "POST", "PUT", "HEAD", "DELETE"}, + Path: "/*", + }}, + } + + assert.Equal(t, expected, info) +} + +func TestClient_GetDNSZones(t *testing.T) { + client := mockBuilder(). + Route("GET /api/v1/dns/", + servermock.ResponseFromFixture("zones.json"), + servermock.CheckQueryParameter().Strict(). + With("name", "example.com.")). + Build(t) + + zones, err := client.GetDNSZones(t.Context(), "example.com.") + require.NoError(t, err) + + expected := []Zone{{ + Name: "example.com.", + HandlerConfigs: []HandlerConfig{ + {Type: "memory"}, + {Type: "etcd"}, + }, + DefaultTTL: 86400, + RecordCount: 1, + }} + + assert.Equal(t, expected, zones) +} + +func TestClient_CreateDNSRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /api/v1/dns/zones/records", + servermock.Noop(). + WithStatusCode(http.StatusNoContent), + servermock.CheckRequestJSONBodyFromFixture("create_record-request.json"), + servermock.CheckQueryParameter().Strict(). + With("zone", "example.com."). + With("uid", "123"). + With("hostname", "_acme-challenge")). + Build(t) + + record := Record{ + Data: "txtTXTtxt", + Hostname: "_acme-challenge", + Type: "TXT", + UID: "123", + } + + err := client.CreateDNSRecord(t.Context(), "example.com.", record) + require.NoError(t, err) +} + +func TestClient_DeleteDNSRecord(t *testing.T) { + client := mockBuilder(). + Route("DELETE /api/v1/dns/zones/records", + servermock.Noop(). + WithStatusCode(http.StatusNoContent), + servermock.CheckQueryParameter().Strict(). + With("zone", "example.com."). + With("uid", "123"). + With("type", "TXT"). + With("hostname", "_acme-challenge")). + Build(t) + + record := Record{ + Data: "txtTXTtxt", + Hostname: "_acme-challenge", + Type: "TXT", + UID: "123", + } + + err := client.DeleteDNSRecord(t.Context(), "example.com.", record) + require.NoError(t, err) +} diff --git a/providers/dns/gravity/internal/fixtures/create_record-request.json b/providers/dns/gravity/internal/fixtures/create_record-request.json new file mode 100644 index 000000000..d671d1342 --- /dev/null +++ b/providers/dns/gravity/internal/fixtures/create_record-request.json @@ -0,0 +1,6 @@ +{ + "data": "txtTXTtxt", + "hostname": "_acme-challenge", + "type": "TXT", + "uid": "123" +} diff --git a/providers/dns/gravity/internal/fixtures/error.json b/providers/dns/gravity/internal/fixtures/error.json new file mode 100644 index 000000000..38b78fcca --- /dev/null +++ b/providers/dns/gravity/internal/fixtures/error.json @@ -0,0 +1,8 @@ +{ + "code": 0, + "context": { + "additionalProp1": "string" + }, + "error": "unauthenticated", + "status": "UNAUTHENTICATED" +} diff --git a/providers/dns/gravity/internal/fixtures/login-request.json b/providers/dns/gravity/internal/fixtures/login-request.json new file mode 100644 index 000000000..c641cd3e5 --- /dev/null +++ b/providers/dns/gravity/internal/fixtures/login-request.json @@ -0,0 +1,4 @@ +{ + "username": "user", + "password": "secret" +} diff --git a/providers/dns/gravity/internal/fixtures/login.json b/providers/dns/gravity/internal/fixtures/login.json new file mode 100644 index 000000000..b9ae7145f --- /dev/null +++ b/providers/dns/gravity/internal/fixtures/login.json @@ -0,0 +1,3 @@ +{ + "successful": true +} diff --git a/providers/dns/gravity/internal/fixtures/me.json b/providers/dns/gravity/internal/fixtures/me.json new file mode 100644 index 000000000..881a2ca5f --- /dev/null +++ b/providers/dns/gravity/internal/fixtures/me.json @@ -0,0 +1,16 @@ +{ + "username": "admin", + "authenticated": true, + "permissions": [ + { + "path": "/*", + "methods": [ + "GET", + "POST", + "PUT", + "HEAD", + "DELETE" + ] + } + ] +} diff --git a/providers/dns/gravity/internal/fixtures/me_unauthenticated.json b/providers/dns/gravity/internal/fixtures/me_unauthenticated.json new file mode 100644 index 000000000..67698b8e2 --- /dev/null +++ b/providers/dns/gravity/internal/fixtures/me_unauthenticated.json @@ -0,0 +1,5 @@ +{ + "username": "", + "authenticated": false, + "permissions": null +} diff --git a/providers/dns/gravity/internal/fixtures/zones.json b/providers/dns/gravity/internal/fixtures/zones.json new file mode 100644 index 000000000..53a8df6c1 --- /dev/null +++ b/providers/dns/gravity/internal/fixtures/zones.json @@ -0,0 +1,19 @@ +{ + "zones": [ + { + "name": "example.com.", + "handlerConfigs": [ + { + "type": "memory" + }, + { + "type": "etcd" + } + ], + "defaultTTL": 86400, + "authoritative": false, + "hook": "", + "recordCount": 1 + } + ] +} diff --git a/providers/dns/gravity/internal/fixtures/zones_empty.json b/providers/dns/gravity/internal/fixtures/zones_empty.json new file mode 100644 index 000000000..d8b70b45e --- /dev/null +++ b/providers/dns/gravity/internal/fixtures/zones_empty.json @@ -0,0 +1,3 @@ +{ + "zones": null +} diff --git a/providers/dns/gravity/internal/types.go b/providers/dns/gravity/internal/types.go new file mode 100644 index 000000000..cb6e99083 --- /dev/null +++ b/providers/dns/gravity/internal/types.go @@ -0,0 +1,82 @@ +package internal + +import ( + "fmt" + "strings" +) + +type APIError struct { + Status string `json:"status"` + ErrorMsg string `json:"error"` + Code int `json:"code"` + Context map[string]string `json:"context"` +} + +func (a *APIError) Error() string { + var msg strings.Builder + + msg.WriteString(fmt.Sprintf("status: %s, error: %s", a.Status, a.ErrorMsg)) + + if a.Code != 0 { + msg.WriteString(fmt.Sprintf(", code: %d", a.Code)) + } + + if len(a.Context) != 0 { + for k, v := range a.Context { + msg.WriteString(fmt.Sprintf(", %s: %s", k, v)) + } + } + + return msg.String() +} + +type Login struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type Auth struct { + Successful bool `json:"successful"` +} + +type UserInfo struct { + Username string `json:"username"` + Authenticated bool `json:"authenticated"` + Permissions []Permission `json:"permissions"` +} + +type Permission struct { + Methods []string `json:"methods"` + Path string `json:"path"` +} + +type Zones struct { + Zones []Zone `json:"zones"` +} + +type Zone struct { + Name string `json:"name"` + HandlerConfigs []HandlerConfig `json:"handlerConfigs"` + DefaultTTL int `json:"defaultTTL"` + Authoritative bool `json:"authoritative"` + Hook string `json:"hook"` + RecordCount int `json:"recordCount"` +} + +type HandlerConfig struct { + Type string `json:"type"` + CacheTTL int `json:"cache_ttl,omitempty"` + To []string `json:"to,omitempty"` +} + +type Record struct { + Data string `json:"data,omitempty"` + Fqdn string `json:"fqdn,omitempty"` + Hostname string `json:"hostname,omitempty"` + MxPreference int `json:"mxPreference,omitempty"` + SrvPort int `json:"srvPort,omitempty"` + SrvPriority int `json:"srvPriority,omitempty"` + SrvWeight int `json:"srvWeight,omitempty"` + Type string `json:"type,omitempty"` + UID string `json:"uid,omitempty"` +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 842d00c51..95937c986 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -73,6 +73,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/glesys" "github.com/go-acme/lego/v4/providers/dns/godaddy" "github.com/go-acme/lego/v4/providers/dns/googledomains" + "github.com/go-acme/lego/v4/providers/dns/gravity" "github.com/go-acme/lego/v4/providers/dns/hetzner" "github.com/go-acme/lego/v4/providers/dns/hostingde" "github.com/go-acme/lego/v4/providers/dns/hostinger" @@ -315,6 +316,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return godaddy.NewDNSProvider() case "googledomains": return googledomains.NewDNSProvider() + case "gravity": + return gravity.NewDNSProvider() case "hetzner": return hetzner.NewDNSProvider() case "hostingde": From 36552da309c4faa028b8309252302f2a30de823d Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 5 Dec 2025 16:45:51 +0100 Subject: [PATCH 231/298] chore: update workflows (#2741) --- .github/workflows/documentation.yml | 8 ++------ .github/workflows/go-cross.yml | 9 ++------- .github/workflows/pr.yml | 27 ++++++++++----------------- .github/workflows/release.yml | 8 +++----- .golangci.yml | 3 +++ 5 files changed, 20 insertions(+), 35 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index c0bbbfbdc..4f9d444fc 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -17,15 +17,11 @@ jobs: steps: - # https://github.com/marketplace/actions/checkout - - name: Check out code - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - # https://github.com/marketplace/actions/setup-go-environment - - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/go-cross.yml b/.github/workflows/go-cross.yml index 30ec652a2..9dee85035 100644 --- a/.github/workflows/go-cross.yml +++ b/.github/workflows/go-cross.yml @@ -20,13 +20,8 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] steps: - # https://github.com/marketplace/actions/checkout - - name: Checkout code - uses: actions/checkout@v4 - - # https://github.com/marketplace/actions/setup-go-environment - - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 91977bc28..626d9f6e9 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: stable - GOLANGCI_LINT_VERSION: v2.6.0 + GOLANGCI_LINT_VERSION: v2.7.1 HUGO_VERSION: 0.148.2 CGO_ENABLED: 0 LEGO_E2E_TESTS: CI @@ -21,40 +21,33 @@ jobs: steps: - # https://github.com/marketplace/actions/checkout - - name: Check out code - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - # https://github.com/marketplace/actions/setup-go-environment - - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version: ${{ env.GO_VERSION }} - name: Check and get dependencies run: | - go mod tidy - git diff --exit-code go.mod - git diff --exit-code go.sum + go mod tidy --diff - name: Generate and Check generated elements run: | make generate-dns git diff --exit-code - # https://golangci-lint.run/usage/install#other-ci - - name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }} - run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION} - golangci-lint --version + - uses: golangci/golangci-lint-action@v9 + with: + version: ${{ env.GOLANGCI_LINT_VERSION }} + install-only: true - name: Install Pebble - run: go install github.com/letsencrypt/pebble/v2/cmd/pebble@v2.7.0 + run: go install github.com/letsencrypt/pebble/v2/cmd/pebble@v2.8.0 - name: Install challtestsrv - run: go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@v2.7.0 + run: go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@v2.8.0 - name: Set up a Memcached server uses: niden/actions-memcached@v7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca2e1867e..3ba055979 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,13 +42,11 @@ jobs: docker-images: true swap-storage: false - - name: Check out code - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - - name: Set up Go ${{ env.GO_VERSION }} - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version: ${{ env.GO_VERSION }} @@ -71,7 +69,7 @@ jobs: - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6 with: - version: v2.12.3 + version: v2.13.0 args: release -p 1 --clean --timeout=90m env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN_REPO }} diff --git a/.golangci.yml b/.golangci.yml index ceb7cad85..b851169ff 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -180,6 +180,9 @@ linters: text: Error return value of `fmt.Fprintln` is not checked linters: - errcheck + - text: "var-naming: avoid meaningless package names" + linters: + - revive - path: certcrypto/crypto.go text: (tlsFeatureExtensionOID|ocspMustStapleFeature) is a global variable linters: From 02dd7152f03ed630889e98a0ca2225495c4a13e7 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 8 Dec 2025 20:25:08 +0100 Subject: [PATCH 232/298] Add DNS provider for Syse.no (#2742) --- README.md | 16 +- cmd/zz_gen_cmd_dnshelp.go | 21 ++ docs/content/dns/zz_gen_syse.md | 70 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/syse/internal/client.go | 131 +++++++++++ providers/dns/syse/internal/client_test.go | 102 ++++++++ .../fixtures/create_record-request.json | 7 + .../syse/internal/fixtures/create_record.json | 8 + providers/dns/syse/internal/types.go | 11 + providers/dns/syse/syse.go | 182 +++++++++++++++ providers/dns/syse/syse.toml | 25 ++ providers/dns/syse/syse_test.go | 220 ++++++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 13 files changed, 789 insertions(+), 9 deletions(-) create mode 100644 docs/content/dns/zz_gen_syse.md create mode 100644 providers/dns/syse/internal/client.go create mode 100644 providers/dns/syse/internal/client_test.go create mode 100644 providers/dns/syse/internal/fixtures/create_record-request.json create mode 100644 providers/dns/syse/internal/fixtures/create_record.json create mode 100644 providers/dns/syse/internal/types.go create mode 100644 providers/dns/syse/syse.go create mode 100644 providers/dns/syse/syse.toml create mode 100644 providers/dns/syse/syse_test.go diff --git a/README.md b/README.md index dddcec72d..015d45ea6 100644 --- a/README.md +++ b/README.md @@ -237,42 +237,42 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Sonic Spaceship Stackpath - Technitium + Syse + Technitium Tencent Cloud DNS Tencent EdgeOne Timeweb Cloud - TransIP + TransIP UKFast SafeDNS Ultradns United-Domains - Variomedia + Variomedia VegaDNS Vercel Versio.[nl|eu|uk] - VinylDNS + VinylDNS VK Cloud Volcano Engine/火山引擎 Vscale - Vultr + Vultr webnames.ca webnames.ru Websupport - WEDOS + WEDOS West.cn/西部数码 Yandex 360 Yandex Cloud - Yandex PDD + Yandex PDD Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 2695f0dba..dda63b2a3 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -157,6 +157,7 @@ func allDNSCodes() string { "sonic", "spaceship", "stackpath", + "syse", "technitium", "tencentcloud", "timewebcloud", @@ -3312,6 +3313,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/stackpath`) + case "syse": + // generated from: providers/dns/syse/syse.toml + ew.writeln(`Configuration for Syse.`) + ew.writeln(`Code: 'syse'`) + ew.writeln(`Since: 'v4.30.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "SYSE_CREDENTIALS": Comma-separated list of 'zone:password' credential pairs`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "SYSE_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "SYSE_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "SYSE_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 1200)`) + ew.writeln(` - "SYSE_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/syse`) + case "technitium": // generated from: providers/dns/technitium/technitium.toml ew.writeln(`Configuration for Technitium.`) diff --git a/docs/content/dns/zz_gen_syse.md b/docs/content/dns/zz_gen_syse.md new file mode 100644 index 000000000..1d9d957d5 --- /dev/null +++ b/docs/content/dns/zz_gen_syse.md @@ -0,0 +1,70 @@ +--- +title: "Syse" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: syse +dnsprovider: + since: "v4.30.0" + code: "syse" + url: "https://www.syse.no/" +--- + + + + + + +Configuration for [Syse](https://www.syse.no/). + + + + +- Code: `syse` +- Since: v4.30.0 + + +Here is an example bash command using the Syse provider: + +```bash +SYSE_CREDENTIALS=example.com:password \ +lego --email you@example.com --dns syse -d '*.example.com' -d example.com run + +SYSE_CREDENTIALS=example.org:password1,example.com:password2 \ +lego --email you@example.com --dns syse -d '*.example.org' -d example.org -d '*.example.com' -d example.com +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `SYSE_CREDENTIALS` | Comma-separated list of `zone:password` credential pairs | + +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 | +|--------------------------------|-------------| +| `SYSE_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `SYSE_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `SYSE_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 1200) | +| `SYSE_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://www.syse.no/api/dns) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 51c6d39f3..37d8700e8 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/syse/internal/client.go b/providers/dns/syse/internal/client.go new file mode 100644 index 000000000..8cb801469 --- /dev/null +++ b/providers/dns/syse/internal/client.go @@ -0,0 +1,131 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" +) + +const defaultBaseURL = "https://www.syse.no/api" + +// Client the Syse API client. +type Client struct { + credentials map[string]string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(credentials map[string]string) (*Client, error) { + if len(credentials) == 0 { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + credentials: credentials, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) CreateRecord(ctx context.Context, zone string, record Record) (*Record, error) { + endpoint := c.BaseURL.JoinPath("dns", zone) + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) + if err != nil { + return nil, err + } + + req.SetBasicAuth(zone, c.credentials[zone]) + + result := new(Record) + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result, nil +} + +func (c *Client) DeleteRecord(ctx context.Context, zone, recordID string) error { + endpoint := c.BaseURL.JoinPath("dns", zone, recordID) + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + req.SetBasicAuth(zone, c.credentials[zone]) + + return c.do(req, nil) +} + +func (c *Client) do(req *http.Request, result any) error { + useragent.SetHeader(req.Header) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + raw, _ := io.ReadAll(resp.Body) + + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} diff --git a/providers/dns/syse/internal/client_test.go b/providers/dns/syse/internal/client_test.go new file mode 100644 index 000000000..88416aa88 --- /dev/null +++ b/providers/dns/syse/internal/client_test.go @@ -0,0 +1,102 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(map[string]string{ + "example.com": "secret", + }) + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(), + ) +} + +func TestClient_CreateRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /dns/example.com", + servermock.ResponseFromFixture("create_record.json"), + servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). + Build(t) + + record := Record{ + Type: "TXT", + Prefix: "_acme-challenge", + Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + Active: true, + TTL: 120, + } + + result, err := client.CreateRecord(t.Context(), "example.com", record) + require.NoError(t, err) + + expected := &Record{ + ID: "1234", + Type: "TXT", + Prefix: "_acme-challenge", + Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + Active: true, + TTL: 120, + } + + assert.Equal(t, expected, result) +} + +func TestClient_CreateRecord_error(t *testing.T) { + client := mockBuilder(). + Route("POST /dns/example.com", + servermock.RawStringResponse(http.StatusText(http.StatusUnauthorized)). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + record := Record{ + Type: "TXT", + Prefix: "_acme-challenge", + Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + Active: true, + TTL: 120, + } + + _, err := client.CreateRecord(t.Context(), "example.com", record) + require.EqualError(t, err, "unexpected status code: [status code: 401] body: Unauthorized") +} + +func TestClient_DeleteRecord(t *testing.T) { + client := mockBuilder(). + Route("DELETE /dns/example.com/1234", + servermock.Noop()). + Build(t) + + err := client.DeleteRecord(t.Context(), "example.com", "1234") + require.NoError(t, err) +} + +func TestClient_DeleteRecord_error(t *testing.T) { + client := mockBuilder(). + Route("DELETE /dns/example.com/1234", + servermock.RawStringResponse(http.StatusText(http.StatusUnauthorized)). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + err := client.DeleteRecord(t.Context(), "example.com", "1234") + require.EqualError(t, err, "unexpected status code: [status code: 401] body: Unauthorized") +} diff --git a/providers/dns/syse/internal/fixtures/create_record-request.json b/providers/dns/syse/internal/fixtures/create_record-request.json new file mode 100644 index 000000000..549a0f60f --- /dev/null +++ b/providers/dns/syse/internal/fixtures/create_record-request.json @@ -0,0 +1,7 @@ +{ + "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "active": true, + "ttl": 120, + "prefix": "_acme-challenge", + "type": "TXT" +} diff --git a/providers/dns/syse/internal/fixtures/create_record.json b/providers/dns/syse/internal/fixtures/create_record.json new file mode 100644 index 000000000..b598779c6 --- /dev/null +++ b/providers/dns/syse/internal/fixtures/create_record.json @@ -0,0 +1,8 @@ +{ + "id": "1234", + "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "active": true, + "ttl": 120, + "prefix": "_acme-challenge", + "type": "TXT" +} diff --git a/providers/dns/syse/internal/types.go b/providers/dns/syse/internal/types.go new file mode 100644 index 000000000..4b90205e1 --- /dev/null +++ b/providers/dns/syse/internal/types.go @@ -0,0 +1,11 @@ +package internal + +type Record struct { + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Prefix string `json:"prefix,omitempty"` + Content string `json:"content,omitempty"` + Priority int `json:"prio,omitempty"` + TTL int `json:"ttl,omitempty"` + Active bool `json:"active,omitempty"` +} diff --git a/providers/dns/syse/syse.go b/providers/dns/syse/syse.go new file mode 100644 index 000000000..aab07131f --- /dev/null +++ b/providers/dns/syse/syse.go @@ -0,0 +1,182 @@ +// Package syse implements a DNS provider for solving the DNS-01 challenge using Syse. +package syse + +import ( + "context" + "errors" + "fmt" + "net/http" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/syse/internal" +) + +// Environment variables names. +const ( + envNamespace = "SYSE_" + + EnvCredentials = envNamespace + "CREDENTIALS" + + 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 { + Credentials map[string]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, 1200*time.Second), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client + + recordIDs map[string]string + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for Syse. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvCredentials) + if err != nil { + return nil, fmt.Errorf("syse: %w", err) + } + + config := NewDefaultConfig() + + credentials, err := env.ParsePairs(values[EnvCredentials]) + if err != nil { + return nil, fmt.Errorf("syse: credentials: %w", err) + } + + config.Credentials = credentials + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Syse. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("syse: the configuration of the DNS provider is nil") + } + + if len(config.Credentials) == 0 { + return nil, errors.New("syse: missing credentials") + } + + for domain, password := range config.Credentials { + if domain == "" { + return nil, fmt.Errorf(`syse: missing domain: "%s:%s"`, domain, password) + } + + if password == "" { + return nil, fmt.Errorf(`syse: missing password: "%s:%s"`, domain, password) + } + } + + client, err := internal.NewClient(config.Credentials) + if err != nil { + return nil, fmt.Errorf("syse: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + recordIDs: make(map[string]string), + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("syse: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("syse: %w", err) + } + + record := internal.Record{ + Type: "TXT", + Prefix: subDomain, + Content: info.Value, + TTL: d.config.TTL, + Active: true, + } + + newRecord, err := d.client.CreateRecord(context.Background(), dns01.UnFqdn(authZone), record) + if err != nil { + return fmt.Errorf("syse: 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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("syse: could not find zone for domain %q: %w", domain, err) + } + + // gets 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("syse: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) + } + + err = d.client.DeleteRecord(context.Background(), dns01.UnFqdn(authZone), recordID) + if err != nil { + return fmt.Errorf("syse: delete record: %w", err) + } + + 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/syse/syse.toml b/providers/dns/syse/syse.toml new file mode 100644 index 000000000..0ae585854 --- /dev/null +++ b/providers/dns/syse/syse.toml @@ -0,0 +1,25 @@ +Name = "Syse" +Description = '''''' +URL = "https://www.syse.no/" +Code = "syse" +Since = "v4.30.0" + +Example = ''' +SYSE_CREDENTIALS=example.com:password \ +lego --email you@example.com --dns syse -d '*.example.com' -d example.com run + +SYSE_CREDENTIALS=example.org:password1,example.com:password2 \ +lego --email you@example.com --dns syse -d '*.example.org' -d example.org -d '*.example.com' -d example.com +''' + +[Configuration] + [Configuration.Credentials] + SYSE_CREDENTIALS = "Comma-separated list of `zone:password` credential pairs" + [Configuration.Additional] + SYSE_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + SYSE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 1200)" + SYSE_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + SYSE_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://www.syse.no/api/dns" diff --git a/providers/dns/syse/syse_test.go b/providers/dns/syse/syse_test.go new file mode 100644 index 000000000..a4472aa7c --- /dev/null +++ b/providers/dns/syse/syse_test.go @@ -0,0 +1,220 @@ +package syse + +import ( + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvCredentials).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvCredentials: "example.org:123", + }, + }, + { + desc: "success multiple domains", + envVars: map[string]string{ + EnvCredentials: "example.org:123,example.com:456,example.net:789", + }, + }, + { + desc: "invalid credentials", + envVars: map[string]string{ + EnvCredentials: ",", + }, + expected: `syse: credentials: incorrect pair: `, + }, + { + desc: "missing password", + envVars: map[string]string{ + EnvCredentials: "example.org:", + }, + expected: `syse: missing password: "example.org:"`, + }, + { + desc: "missing domain", + envVars: map[string]string{ + EnvCredentials: ":123", + }, + expected: `syse: missing domain: ":123"`, + }, + { + desc: "invalid credentials, partial", + envVars: map[string]string{ + EnvCredentials: "example.org:123,example.net", + }, + expected: "syse: credentials: incorrect pair: example.net", + }, + { + desc: "missing credentials", + envVars: map[string]string{ + EnvCredentials: "", + }, + expected: "syse: some credentials information are missing: SYSE_CREDENTIALS", + }, + } + + 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 + creds map[string]string + expected string + }{ + { + desc: "success", + creds: map[string]string{"example.org": "123"}, + }, + { + desc: "success multiple domains", + creds: map[string]string{ + "example.org": "123", + "example.com": "456", + "example.net": "789", + }, + }, + { + desc: "missing credentials", + expected: "syse: missing credentials", + }, + { + desc: "missing domain", + creds: map[string]string{"": "123"}, + expected: `syse: missing domain: ":123"`, + }, + { + desc: "missing password", + creds: map[string]string{"example.org": ""}, + expected: `syse: missing password: "example.org:"`, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Credentials = test.creds + + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.Credentials = map[string]string{ + "example.org": "secret", + } + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("/", servermock.DumpRequest()). + Route("POST /dns/example.com", + servermock.ResponseFromInternal("create_record.json"), + servermock.CheckRequestJSONBodyFromInternal("create_record-request.json")). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("DELETE /dns/example.com/1234", + servermock.Noop()). + Build(t) + + provider.recordIDs["abc"] = "1234" + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 95937c986..9448e163f 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -151,6 +151,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/sonic" "github.com/go-acme/lego/v4/providers/dns/spaceship" "github.com/go-acme/lego/v4/providers/dns/stackpath" + "github.com/go-acme/lego/v4/providers/dns/syse" "github.com/go-acme/lego/v4/providers/dns/technitium" "github.com/go-acme/lego/v4/providers/dns/tencentcloud" "github.com/go-acme/lego/v4/providers/dns/timewebcloud" @@ -472,6 +473,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return spaceship.NewDNSProvider() case "stackpath": return stackpath.NewDNSProvider() + case "syse": + return syse.NewDNSProvider() case "technitium": return technitium.NewDNSProvider() case "tencentcloud": From 961fd586d925a43c4e279a6a1bca815da3369a64 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 9 Dec 2025 16:25:30 +0100 Subject: [PATCH 233/298] docs: add notes --- docs/content/_index.md | 10 ++++++++++ docs/content/dns/_index.md | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/docs/content/_index.md b/docs/content/_index.md index ba90ddc97..d3787cf19 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -7,6 +7,16 @@ chapter: false Let's Encrypt client and ACME library written in Go. +{{% notice important %}} +lego is an independent, free, and open-source project, if you value it, consider [supporting it](https://donate.ldez.dev)! ❤️ + +This project is not owned by a company. I'm not an employee of a company. + +I don't have gifted domains/accounts from DNS companies. + +I've been maintaining it for about 10 years. +{{% /notice %}} + ## Features - ACME v2 [RFC 8555](https://www.rfc-editor.org/rfc/rfc8555.html) diff --git a/docs/content/dns/_index.md b/docs/content/dns/_index.md index 7ccfeb53d..2b6f0489c 100644 --- a/docs/content/dns/_index.md +++ b/docs/content/dns/_index.md @@ -5,6 +5,16 @@ draft: false weight: 3 --- +{{% notice important %}} +lego is an independent, free, and open-source project, if you value it, consider [supporting it](https://donate.ldez.dev)! ❤️ + +This project is not owned by a company. I'm not an employee of a company. + +I don't have gifted domains/accounts from DNS companies. + +I've been maintaining it for about 10 years. +{{% /notice %}} + ## Configuration and Credentials Credentials and DNS configuration for DNS providers must be passed through environment variables. From 1e57e29a9dd7617472fab3feac52b802b0f0f296 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 9 Dec 2025 16:32:14 +0100 Subject: [PATCH 234/298] chore: skip jekyll --- docs/static/.nojekyll | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/static/.nojekyll diff --git a/docs/static/.nojekyll b/docs/static/.nojekyll new file mode 100644 index 000000000..e69de29bb From 9e2dffe8d222ceaeb8aa9b681263fd87189982b2 Mon Sep 17 00:00:00 2001 From: Adrian <161029+aalmenar@users.noreply.github.com> Date: Wed, 10 Dec 2025 21:52:23 +0100 Subject: [PATCH 235/298] Add DNS Provider for Neodigit (#2747) Co-authored-by: Fernandez Ludovic --- README.md | 35 ++-- cmd/zz_gen_cmd_dnshelp.go | 21 ++ docs/content/dns/zz_gen_neodigit.md | 67 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/neodigit/internal/client.go | 182 ++++++++++++++++ .../dns/neodigit/internal/client_test.go | 174 ++++++++++++++++ .../fixtures/create_record-request.json | 8 + .../internal/fixtures/create_record.json | 10 + .../internal/fixtures/get_records.json | 26 +++ .../neodigit/internal/fixtures/get_zones.json | 16 ++ providers/dns/neodigit/internal/types.go | 23 +++ providers/dns/neodigit/neodigit.go | 195 ++++++++++++++++++ providers/dns/neodigit/neodigit.toml | 22 ++ providers/dns/neodigit/neodigit_test.go | 174 ++++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 15 files changed, 942 insertions(+), 16 deletions(-) create mode 100644 docs/content/dns/zz_gen_neodigit.md create mode 100644 providers/dns/neodigit/internal/client.go create mode 100644 providers/dns/neodigit/internal/client_test.go create mode 100644 providers/dns/neodigit/internal/fixtures/create_record-request.json create mode 100644 providers/dns/neodigit/internal/fixtures/create_record.json create mode 100644 providers/dns/neodigit/internal/fixtures/get_records.json create mode 100644 providers/dns/neodigit/internal/fixtures/get_zones.json create mode 100644 providers/dns/neodigit/internal/types.go create mode 100644 providers/dns/neodigit/neodigit.go create mode 100644 providers/dns/neodigit/neodigit.toml create mode 100644 providers/dns/neodigit/neodigit_test.go diff --git a/README.md b/README.md index 015d45ea6..8ef958264 100644 --- a/README.md +++ b/README.md @@ -196,83 +196,88 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Namesilo NearlyFreeSpeech.NET + Neodigit Netcup - Netlify + Netlify Nicmanager NIFCloud Njalla - Nodion + Nodion NS1 Octenium Open Telekom Cloud - Oracle Cloud + Oracle Cloud OVH plesk.com Porkbun - PowerDNS + PowerDNS Rackspace Rain Yun/雨云 RcodeZero - reg.ru + reg.ru Regfish RFC2136 RimuHosting - RU CENTER + RU CENTER Sakura Cloud Scaleway Selectel - Selectel v2 + Selectel v2 SelfHost.(de|eu) Servercow Shellrent - Simply.com + Simply.com Sonic Spaceship Stackpath - Syse + Syse Technitium Tencent Cloud DNS Tencent EdgeOne - Timeweb Cloud + Timeweb Cloud TransIP UKFast SafeDNS Ultradns - United-Domains + United-Domains Variomedia VegaDNS Vercel - Versio.[nl|eu|uk] + Versio.[nl|eu|uk] VinylDNS VK Cloud Volcano Engine/火山引擎 - Vscale + Vscale Vultr webnames.ca webnames.ru - Websupport + Websupport WEDOS West.cn/西部数码 Yandex 360 - Yandex Cloud + Yandex Cloud Yandex PDD Zone.ee ZoneEdit + Zonomi + + + diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index dda63b2a3..09667f572 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -122,6 +122,7 @@ func allDNSCodes() string { "namedotcom", "namesilo", "nearlyfreespeech", + "neodigit", "netcup", "netlify", "nicmanager", @@ -2534,6 +2535,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/nearlyfreespeech`) + case "neodigit": + // generated from: providers/dns/neodigit/neodigit.toml + ew.writeln(`Configuration for Neodigit.`) + ew.writeln(`Code: 'neodigit'`) + ew.writeln(`Since: 'v4.30.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "NEODIGIT_TOKEN": API token`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "NEODIGIT_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "NEODIGIT_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "NEODIGIT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) + ew.writeln(` - "NEODIGIT_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/neodigit`) + case "netcup": // generated from: providers/dns/netcup/netcup.toml ew.writeln(`Configuration for Netcup.`) diff --git a/docs/content/dns/zz_gen_neodigit.md b/docs/content/dns/zz_gen_neodigit.md new file mode 100644 index 000000000..70dfb6343 --- /dev/null +++ b/docs/content/dns/zz_gen_neodigit.md @@ -0,0 +1,67 @@ +--- +title: "Neodigit" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: neodigit +dnsprovider: + since: "v4.30.0" + code: "neodigit" + url: "https://www.neodigit.net" +--- + + + + + + +Configuration for [Neodigit](https://www.neodigit.net). + + + + +- Code: `neodigit` +- Since: v4.30.0 + + +Here is an example bash command using the Neodigit provider: + +```bash +NEODIGIT_TOKEN=xxxxxx \ +lego --email you@example.com --dns neodigit -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `NEODIGIT_TOKEN` | API token | + +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 | +|--------------------------------|-------------| +| `NEODIGIT_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `NEODIGIT_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `NEODIGIT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | +| `NEODIGIT_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://developers.neodigit.net/#dns) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 37d8700e8..b172beedb 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/neodigit/internal/client.go b/providers/dns/neodigit/internal/client.go new file mode 100644 index 000000000..1e883c6d2 --- /dev/null +++ b/providers/dns/neodigit/internal/client.go @@ -0,0 +1,182 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" +) + +// DefaultBaseURL is the default API endpoint. +const DefaultBaseURL = "https://api.neodigit.net/v1" + +// Client is a Neodigit API client. +type Client struct { + token string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(token string) (*Client, error) { + if token == "" { + return nil, errors.New("credentials missing: token") + } + + baseURL, err := url.Parse(DefaultBaseURL) + if err != nil { + return nil, err + } + + return &Client{ + token: token, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 30 * time.Second}, + }, nil +} + +// GetZones lists all DNS zones. +func (c *Client) GetZones(ctx context.Context) ([]Zone, error) { + endpoint := c.BaseURL.JoinPath("dns", "zones") + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var zones []Zone + + err = c.do(req, &zones) + if err != nil { + return nil, err + } + + return zones, nil +} + +// GetRecords lists all records in a zone. +func (c *Client) GetRecords(ctx context.Context, zoneID int, recordType string) ([]Record, error) { + endpoint := c.BaseURL.JoinPath("dns", "zones", strconv.Itoa(zoneID), "records") + + if recordType != "" { + query := endpoint.Query() + query.Set("type", recordType) + endpoint.RawQuery = query.Encode() + } + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var records []Record + + err = c.do(req, &records) + if err != nil { + return nil, err + } + + return records, nil +} + +// CreateRecord creates a new DNS record. +func (c *Client) CreateRecord(ctx context.Context, zoneID int, record Record) (*Record, error) { + endpoint := c.BaseURL.JoinPath("dns", "zones", strconv.Itoa(zoneID), "records") + + payload := RecordRequest{Record: record} + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, payload) + if err != nil { + return nil, err + } + + var result Record + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return &result, nil +} + +// DeleteRecord deletes a DNS record. +func (c *Client) DeleteRecord(ctx context.Context, zoneID, recordID int) error { + endpoint := c.BaseURL.JoinPath("dns", "zones", strconv.Itoa(zoneID), "records", strconv.Itoa(recordID)) + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) do(req *http.Request, result any) error { + useragent.SetHeader(req.Header) + + req.Header.Set("X-TCpanel-Token", c.token) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + raw, _ := io.ReadAll(resp.Body) + + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} diff --git a/providers/dns/neodigit/internal/client_test.go b/providers/dns/neodigit/internal/client_test.go new file mode 100644 index 000000000..4e9cf3e85 --- /dev/null +++ b/providers/dns/neodigit/internal/client_test.go @@ -0,0 +1,174 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("secret") + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + With("X-TCpanel-Token", "secret")) +} + +func TestClient_GetZones(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/zones", + servermock.ResponseFromFixture("get_zones.json")). + Build(t) + + zones, err := client.GetZones(t.Context()) + require.NoError(t, err) + + expected := []Zone{ + { + ID: 6, + Name: "example.com", + HumanName: "example.com", + }, + { + ID: 7, + Name: "example.org", + HumanName: "example.org", + }, + } + + assert.Equal(t, expected, zones) +} + +func TestClient_GetZones_error(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/zones", + servermock.RawStringResponse(`{"error": "unauthorized"}`). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + zones, err := client.GetZones(t.Context()) + require.Error(t, err) + + assert.Nil(t, zones) +} + +func TestClient_GetRecords(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/zones/6/records", + servermock.ResponseFromFixture("get_records.json")). + Build(t) + + records, err := client.GetRecords(t.Context(), 6, "") + require.NoError(t, err) + + expected := []Record{ + { + ID: 98, + Name: "", + Type: "SOA", + Content: "ns1.example.org dns.example.org 2015092102 7200 7200 1209600 1800", + TTL: 7200, + }, + { + ID: 99, + Name: "", + Type: "NS", + Content: "ns1.example.org", + TTL: 7200, + }, + { + ID: 100, + Name: "_acme-challenge", + Type: "TXT", + Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 120, + }, + } + + assert.Equal(t, expected, records) +} + +func TestClient_CreateRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /dns/zones/6/records", + servermock.ResponseFromFixture("create_record.json"). + WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). + Build(t) + + record := Record{ + Name: "_acme-challenge", + Type: "TXT", + Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 120, + } + + result, err := client.CreateRecord(t.Context(), 6, record) + require.NoError(t, err) + + expected := &Record{ + ID: 101, + Name: "_acme-challenge", + Type: "TXT", + Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 120, + } + + assert.Equal(t, expected, result) +} + +func TestClient_CreateRecord_error(t *testing.T) { + client := mockBuilder(). + Route("POST /dns/zones/6/records", + servermock.RawStringResponse(`{"error": "bad request"}`). + WithStatusCode(http.StatusBadRequest)). + Build(t) + + record := Record{ + Name: "_acme-challenge", + Type: "TXT", + Content: "test-value", + TTL: 120, + } + + result, err := client.CreateRecord(t.Context(), 6, record) + require.Error(t, err) + + assert.Nil(t, result) +} + +func TestClient_DeleteRecord(t *testing.T) { + client := mockBuilder(). + Route("DELETE /dns/zones/6/records/101", + servermock.Noop(). + WithStatusCode(http.StatusNoContent)). + Build(t) + + err := client.DeleteRecord(t.Context(), 6, 101) + require.NoError(t, err) +} + +func TestClient_DeleteRecord_error(t *testing.T) { + client := mockBuilder(). + Route("DELETE /dns/zones/6/records/999", + servermock.RawStringResponse(`{"error": "not found"}`). + WithStatusCode(http.StatusNotFound)). + Build(t) + + err := client.DeleteRecord(t.Context(), 6, 999) + require.Error(t, err) +} diff --git a/providers/dns/neodigit/internal/fixtures/create_record-request.json b/providers/dns/neodigit/internal/fixtures/create_record-request.json new file mode 100644 index 000000000..4cd339c98 --- /dev/null +++ b/providers/dns/neodigit/internal/fixtures/create_record-request.json @@ -0,0 +1,8 @@ +{ + "record": { + "name": "_acme-challenge", + "type": "TXT", + "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "ttl": 120 + } +} diff --git a/providers/dns/neodigit/internal/fixtures/create_record.json b/providers/dns/neodigit/internal/fixtures/create_record.json new file mode 100644 index 000000000..6f30010ac --- /dev/null +++ b/providers/dns/neodigit/internal/fixtures/create_record.json @@ -0,0 +1,10 @@ +{ + "id": 101, + "name": "_acme-challenge", + "type": "TXT", + "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "ttl": 120, + "prio": null, + "created_at": "2015-09-21T14:40:27.127+02:00", + "updated_at": "2015-09-21T14:40:27.127+02:00" +} diff --git a/providers/dns/neodigit/internal/fixtures/get_records.json b/providers/dns/neodigit/internal/fixtures/get_records.json new file mode 100644 index 000000000..00e09c37f --- /dev/null +++ b/providers/dns/neodigit/internal/fixtures/get_records.json @@ -0,0 +1,26 @@ +[ + { + "id": 98, + "name": "", + "type": "SOA", + "content": "ns1.example.org dns.example.org 2015092102 7200 7200 1209600 1800", + "ttl": 7200, + "prio": null + }, + { + "id": 99, + "name": "", + "type": "NS", + "content": "ns1.example.org", + "ttl": 7200, + "prio": null + }, + { + "id": 100, + "name": "_acme-challenge", + "type": "TXT", + "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "ttl": 120, + "prio": null + } +] diff --git a/providers/dns/neodigit/internal/fixtures/get_zones.json b/providers/dns/neodigit/internal/fixtures/get_zones.json new file mode 100644 index 000000000..01a08dced --- /dev/null +++ b/providers/dns/neodigit/internal/fixtures/get_zones.json @@ -0,0 +1,16 @@ +[ + { + "id": 6, + "name": "example.com", + "created_at": "2015-09-21T12:19:04.000+02:00", + "updated_at": "2015-09-21T12:19:04.000+02:00", + "human_name": "example.com" + }, + { + "id": 7, + "name": "example.org", + "created_at": "2015-09-22T10:00:00.000+02:00", + "updated_at": "2015-09-22T10:00:00.000+02:00", + "human_name": "example.org" + } +] diff --git a/providers/dns/neodigit/internal/types.go b/providers/dns/neodigit/internal/types.go new file mode 100644 index 000000000..505bfbced --- /dev/null +++ b/providers/dns/neodigit/internal/types.go @@ -0,0 +1,23 @@ +package internal + +// Zone represents a DNS zone. +type Zone struct { + ID int `json:"id"` + Name string `json:"name"` + HumanName string `json:"human_name"` +} + +// Record represents a DNS record. +type Record struct { + ID int `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Content string `json:"content,omitempty"` + TTL int `json:"ttl,omitempty"` + Priority int `json:"prio,omitempty"` +} + +// RecordRequest is the request body for creating/updating a record. +type RecordRequest struct { + Record Record `json:"record"` +} diff --git a/providers/dns/neodigit/neodigit.go b/providers/dns/neodigit/neodigit.go new file mode 100644 index 000000000..08ff64e04 --- /dev/null +++ b/providers/dns/neodigit/neodigit.go @@ -0,0 +1,195 @@ +// Package neodigit implements a DNS provider for solving the DNS-01 challenge using Neodigit DNS. +package neodigit + +import ( + "context" + "errors" + "fmt" + "net/http" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/neodigit/internal" +) + +// Environment variables names. +const ( + envNamespace = "NEODIGIT_" + + EnvToken = envNamespace + "TOKEN" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + Token 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, 5*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client + + zoneIDs map[string]int + recordIDs map[string]int + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for Neodigit. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvToken) + if err != nil { + return nil, fmt.Errorf("neodigit: %w", err) + } + + config := NewDefaultConfig() + config.Token = values[EnvToken] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Neodigit. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("neodigit: the configuration of the DNS provider is nil") + } + + if config.Token == "" { + return nil, errors.New("neodigit: missing credentials") + } + + client, err := internal.NewClient(config.Token) + if err != nil { + return nil, fmt.Errorf("neodigit: create client: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + zoneIDs: make(map[string]int), + recordIDs: make(map[string]int), + }, nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("neodigit: could not find zone for domain %q: %w", domain, err) + } + + authZone = dns01.UnFqdn(authZone) + + zone, err := d.findZone(ctx, authZone) + if err != nil { + return fmt.Errorf("neodigit: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("neodigit: %w", err) + } + + record := internal.Record{ + Name: subDomain, + Type: "TXT", + Content: info.Value, + TTL: d.config.TTL, + } + + newRecord, err := d.client.CreateRecord(ctx, zone.ID, record) + if err != nil { + return fmt.Errorf("neodigit: create record: %w", err) + } + + d.recordIDsMu.Lock() + d.zoneIDs[token] = zone.ID + 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) + + d.recordIDsMu.Lock() + zoneID, zoneOK := d.zoneIDs[token] + recordID, recordOK := d.recordIDs[token] + d.recordIDsMu.Unlock() + + if !zoneOK || !recordOK { + return fmt.Errorf("neodigit: unknown record ID or zone ID for '%s' '%s'", info.EffectiveFQDN, token) + } + + err := d.client.DeleteRecord(context.Background(), zoneID, recordID) + if err != nil { + return fmt.Errorf("neodigit: delete record: fqdn=%s, zoneID=%d, recordID=%d: %w", + info.EffectiveFQDN, zoneID, recordID, err) + } + + d.recordIDsMu.Lock() + delete(d.zoneIDs, token) + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + + return nil +} + +func (d *DNSProvider) findZone(ctx context.Context, zoneName string) (*internal.Zone, error) { + zones, err := d.client.GetZones(ctx) + if err != nil { + return nil, fmt.Errorf("get zones: %w", err) + } + + for _, zone := range zones { + if zone.Name == zoneName || zone.HumanName == zoneName { + return &zone, nil + } + } + + return nil, fmt.Errorf("zone not found: %s", zoneName) +} diff --git a/providers/dns/neodigit/neodigit.toml b/providers/dns/neodigit/neodigit.toml new file mode 100644 index 000000000..b391a6512 --- /dev/null +++ b/providers/dns/neodigit/neodigit.toml @@ -0,0 +1,22 @@ +Name = "Neodigit" +Description = '''''' +URL = "https://www.neodigit.net" +Code = "neodigit" +Since = "v4.30.0" + +Example = ''' +NEODIGIT_TOKEN=xxxxxx \ +lego --email you@example.com --dns neodigit -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + NEODIGIT_TOKEN = "API token" + [Configuration.Additional] + NEODIGIT_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + NEODIGIT_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" + NEODIGIT_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + NEODIGIT_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://developers.neodigit.net/#dns" diff --git a/providers/dns/neodigit/neodigit_test.go b/providers/dns/neodigit/neodigit_test.go new file mode 100644 index 000000000..1f32d31a3 --- /dev/null +++ b/providers/dns/neodigit/neodigit_test.go @@ -0,0 +1,174 @@ +package neodigit + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvToken).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvToken: "secret", + }, + }, + { + desc: "missing credentials: token", + envVars: map[string]string{ + EnvToken: "", + }, + expected: "neodigit: some credentials information are missing: NEODIGIT_TOKEN", + }, + } + + 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 + token string + expected string + }{ + { + desc: "success", + token: "secret", + }, + { + desc: "missing token", + expected: "neodigit: missing credentials", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Token = test.token + + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + + config.Token = "secret" + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + With("X-TCpanel-Token", "secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("GET /dns/zones", + servermock.ResponseFromInternal("get_zones.json")). + Route("POST /dns/zones/6/records", + servermock.ResponseFromInternal("create_record.json"). + WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBodyFromInternal("create_record-request.json")). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("DELETE /dns/zones/456/records/123", + servermock.Noop(). + WithStatusCode(http.StatusNoContent)). + Build(t) + + token := "abc" + + provider.recordIDs[token] = 123 + provider.zoneIDs[token] = 456 + + err := provider.CleanUp("example.com", token, "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 9448e163f..5a068c3a5 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -116,6 +116,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/namedotcom" "github.com/go-acme/lego/v4/providers/dns/namesilo" "github.com/go-acme/lego/v4/providers/dns/nearlyfreespeech" + "github.com/go-acme/lego/v4/providers/dns/neodigit" "github.com/go-acme/lego/v4/providers/dns/netcup" "github.com/go-acme/lego/v4/providers/dns/netlify" "github.com/go-acme/lego/v4/providers/dns/nicmanager" @@ -403,6 +404,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return namesilo.NewDNSProvider() case "nearlyfreespeech": return nearlyfreespeech.NewDNSProvider() + case "neodigit": + return neodigit.NewDNSProvider() case "netcup": return netcup.NewDNSProvider() case "netlify": From e54598536ba25fb754bb2c0d68d77e61ea6ce6f5 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 11 Dec 2025 12:58:21 +0100 Subject: [PATCH 236/298] Add DNS provider for Virtualname (#2748) --- README.md | 10 +- cmd/zz_gen_cmd_dnshelp.go | 21 +++ docs/content/dns/zz_gen_virtualname.md | 67 +++++++ docs/data/zz_cli_help.toml | 2 +- .../tecnocratica}/internal/client.go | 8 +- .../tecnocratica}/internal/client_test.go | 0 .../fixtures/create_record-request.json | 0 .../internal/fixtures/create_record.json | 0 .../internal/fixtures/get_records.json | 0 .../internal/fixtures/get_zones.json | 0 .../tecnocratica}/internal/types.go | 0 .../dns/internal/tecnocratica/provider.go | 165 ++++++++++++++++++ .../internal/tecnocratica/provider_test.go | 99 +++++++++++ providers/dns/neodigit/neodigit.go | 120 ++----------- providers/dns/neodigit/neodigit_test.go | 62 +------ providers/dns/virtualname/virtualname.go | 101 +++++++++++ providers/dns/virtualname/virtualname.toml | 22 +++ providers/dns/virtualname/virtualname_test.go | 116 ++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 19 files changed, 619 insertions(+), 177 deletions(-) create mode 100644 docs/content/dns/zz_gen_virtualname.md rename providers/dns/{neodigit => internal/tecnocratica}/internal/client.go (95%) rename providers/dns/{neodigit => internal/tecnocratica}/internal/client_test.go (100%) rename providers/dns/{neodigit => internal/tecnocratica}/internal/fixtures/create_record-request.json (100%) rename providers/dns/{neodigit => internal/tecnocratica}/internal/fixtures/create_record.json (100%) rename providers/dns/{neodigit => internal/tecnocratica}/internal/fixtures/get_records.json (100%) rename providers/dns/{neodigit => internal/tecnocratica}/internal/fixtures/get_zones.json (100%) rename providers/dns/{neodigit => internal/tecnocratica}/internal/types.go (100%) create mode 100644 providers/dns/internal/tecnocratica/provider.go create mode 100644 providers/dns/internal/tecnocratica/provider_test.go create mode 100644 providers/dns/virtualname/virtualname.go create mode 100644 providers/dns/virtualname/virtualname.toml create mode 100644 providers/dns/virtualname/virtualname_test.go diff --git a/README.md b/README.md index 8ef958264..570a340e5 100644 --- a/README.md +++ b/README.md @@ -256,28 +256,28 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Versio.[nl|eu|uk] VinylDNS + Virtualname VK Cloud - Volcano Engine/火山引擎 + Volcano Engine/火山引擎 Vscale Vultr webnames.ca - webnames.ru + webnames.ru Websupport WEDOS West.cn/西部数码 - Yandex 360 + Yandex 360 Yandex Cloud Yandex PDD Zone.ee - ZoneEdit + ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 09667f572..ebd4cc72b 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -170,6 +170,7 @@ func allDNSCodes() string { "vercel", "versio", "vinyldns", + "virtualname", "vkcloud", "volcengine", "vscale", @@ -3588,6 +3589,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/vinyldns`) + case "virtualname": + // generated from: providers/dns/virtualname/virtualname.toml + ew.writeln(`Configuration for Virtualname.`) + ew.writeln(`Code: 'virtualname'`) + ew.writeln(`Since: 'v4.30.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "VIRTUALNAME_TOKEN": API token`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "VIRTUALNAME_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "VIRTUALNAME_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "VIRTUALNAME_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) + ew.writeln(` - "VIRTUALNAME_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/virtualname`) + case "vkcloud": // generated from: providers/dns/vkcloud/vkcloud.toml ew.writeln(`Configuration for VK Cloud.`) diff --git a/docs/content/dns/zz_gen_virtualname.md b/docs/content/dns/zz_gen_virtualname.md new file mode 100644 index 000000000..afba24ad0 --- /dev/null +++ b/docs/content/dns/zz_gen_virtualname.md @@ -0,0 +1,67 @@ +--- +title: "Virtualname" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: virtualname +dnsprovider: + since: "v4.30.0" + code: "virtualname" + url: "https://www.virtualname.es/" +--- + + + + + + +Configuration for [Virtualname](https://www.virtualname.es/). + + + + +- Code: `virtualname` +- Since: v4.30.0 + + +Here is an example bash command using the Virtualname provider: + +```bash +VIRTUALNAME_TOKEN=xxxxxx \ +lego --email you@example.com --dns virtualname -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `VIRTUALNAME_TOKEN` | API token | + +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 | +|--------------------------------|-------------| +| `VIRTUALNAME_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `VIRTUALNAME_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `VIRTUALNAME_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | +| `VIRTUALNAME_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://developers.virtualname.net/#dns) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index b172beedb..506c7e47d 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/neodigit/internal/client.go b/providers/dns/internal/tecnocratica/internal/client.go similarity index 95% rename from providers/dns/neodigit/internal/client.go rename to providers/dns/internal/tecnocratica/internal/client.go index 1e883c6d2..5a529fa2f 100644 --- a/providers/dns/neodigit/internal/client.go +++ b/providers/dns/internal/tecnocratica/internal/client.go @@ -16,10 +16,10 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internal/useragent" ) -// DefaultBaseURL is the default API endpoint. -const DefaultBaseURL = "https://api.neodigit.net/v1" +// defaultBaseURL is the default API endpoint. +const defaultBaseURL = "https://api.neodigit.net/v1" -// Client is a Neodigit API client. +// Client is a Tecnocrática API client. type Client struct { token string @@ -33,7 +33,7 @@ func NewClient(token string) (*Client, error) { return nil, errors.New("credentials missing: token") } - baseURL, err := url.Parse(DefaultBaseURL) + baseURL, err := url.Parse(defaultBaseURL) if err != nil { return nil, err } diff --git a/providers/dns/neodigit/internal/client_test.go b/providers/dns/internal/tecnocratica/internal/client_test.go similarity index 100% rename from providers/dns/neodigit/internal/client_test.go rename to providers/dns/internal/tecnocratica/internal/client_test.go diff --git a/providers/dns/neodigit/internal/fixtures/create_record-request.json b/providers/dns/internal/tecnocratica/internal/fixtures/create_record-request.json similarity index 100% rename from providers/dns/neodigit/internal/fixtures/create_record-request.json rename to providers/dns/internal/tecnocratica/internal/fixtures/create_record-request.json diff --git a/providers/dns/neodigit/internal/fixtures/create_record.json b/providers/dns/internal/tecnocratica/internal/fixtures/create_record.json similarity index 100% rename from providers/dns/neodigit/internal/fixtures/create_record.json rename to providers/dns/internal/tecnocratica/internal/fixtures/create_record.json diff --git a/providers/dns/neodigit/internal/fixtures/get_records.json b/providers/dns/internal/tecnocratica/internal/fixtures/get_records.json similarity index 100% rename from providers/dns/neodigit/internal/fixtures/get_records.json rename to providers/dns/internal/tecnocratica/internal/fixtures/get_records.json diff --git a/providers/dns/neodigit/internal/fixtures/get_zones.json b/providers/dns/internal/tecnocratica/internal/fixtures/get_zones.json similarity index 100% rename from providers/dns/neodigit/internal/fixtures/get_zones.json rename to providers/dns/internal/tecnocratica/internal/fixtures/get_zones.json diff --git a/providers/dns/neodigit/internal/types.go b/providers/dns/internal/tecnocratica/internal/types.go similarity index 100% rename from providers/dns/neodigit/internal/types.go rename to providers/dns/internal/tecnocratica/internal/types.go diff --git a/providers/dns/internal/tecnocratica/provider.go b/providers/dns/internal/tecnocratica/provider.go new file mode 100644 index 000000000..17cfb8379 --- /dev/null +++ b/providers/dns/internal/tecnocratica/provider.go @@ -0,0 +1,165 @@ +// Package tecnocratica implements a DNS provider for solving the DNS-01 challenge using Tecnocrática. +package tecnocratica + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/internal/tecnocratica/internal" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + Token string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client + + zoneIDs map[string]int + recordIDs map[string]int + recordIDsMu sync.Mutex +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Tecnocrática. +func NewDNSProviderConfig(config *Config, baseURL string) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("the configuration of the DNS provider is nil") + } + + if config.Token == "" { + return nil, errors.New("missing credentials") + } + + client, err := internal.NewClient(config.Token) + if err != nil { + return nil, fmt.Errorf("create client: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + if baseURL != "" { + client.BaseURL, err = url.Parse(baseURL) + if err != nil { + return nil, err + } + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + zoneIDs: make(map[string]int), + recordIDs: make(map[string]int), + }, nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("could not find zone for domain %q: %w", domain, err) + } + + authZone = dns01.UnFqdn(authZone) + + zone, err := d.findZone(ctx, authZone) + if err != nil { + return fmt.Errorf("%w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("%w", err) + } + + record := internal.Record{ + Name: subDomain, + Type: "TXT", + Content: info.Value, + TTL: d.config.TTL, + } + + newRecord, err := d.client.CreateRecord(ctx, zone.ID, record) + if err != nil { + return fmt.Errorf("create record: %w", err) + } + + d.recordIDsMu.Lock() + d.zoneIDs[token] = zone.ID + 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) + + d.recordIDsMu.Lock() + zoneID, zoneOK := d.zoneIDs[token] + recordID, recordOK := d.recordIDs[token] + d.recordIDsMu.Unlock() + + if !zoneOK || !recordOK { + return fmt.Errorf("unknown record ID or zone ID for '%s' '%s'", info.EffectiveFQDN, token) + } + + err := d.client.DeleteRecord(context.Background(), zoneID, recordID) + if err != nil { + return fmt.Errorf("delete record: fqdn=%s, zoneID=%d, recordID=%d: %w", + info.EffectiveFQDN, zoneID, recordID, err) + } + + d.recordIDsMu.Lock() + delete(d.zoneIDs, token) + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + + return nil +} + +func (d *DNSProvider) findZone(ctx context.Context, zoneName string) (*internal.Zone, error) { + zones, err := d.client.GetZones(ctx) + if err != nil { + return nil, fmt.Errorf("get zones: %w", err) + } + + for _, zone := range zones { + if zone.Name == zoneName || zone.HumanName == zoneName { + return &zone, nil + } + } + + return nil, fmt.Errorf("zone not found: %s", zoneName) +} diff --git a/providers/dns/internal/tecnocratica/provider_test.go b/providers/dns/internal/tecnocratica/provider_test.go new file mode 100644 index 000000000..33e5f7c67 --- /dev/null +++ b/providers/dns/internal/tecnocratica/provider_test.go @@ -0,0 +1,99 @@ +package tecnocratica + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + token string + expected string + }{ + { + desc: "success", + token: "secret", + }, + { + desc: "missing token", + expected: "missing credentials", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := &Config{} + config.Token = test.token + + 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 mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := &Config{ + Token: "secret", + PropagationTimeout: 10 * time.Second, + PollingInterval: 1 * time.Second, + TTL: 120, + HTTPClient: server.Client(), + } + + p, err := NewDNSProviderConfig(config, server.URL) + if err != nil { + return nil, err + } + + return p, nil + }, + servermock.CheckHeader().WithJSONHeaders(). + With("X-TCpanel-Token", "secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("GET /dns/zones", + servermock.ResponseFromInternal("get_zones.json")). + Route("POST /dns/zones/6/records", + servermock.ResponseFromInternal("create_record.json"). + WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBodyFromInternal("create_record-request.json")). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("DELETE /dns/zones/456/records/123", + servermock.Noop(). + WithStatusCode(http.StatusNoContent)). + Build(t) + + token := "abc" + + provider.recordIDs[token] = 123 + provider.zoneIDs[token] = 456 + + err := provider.CleanUp("example.com", token, "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/neodigit/neodigit.go b/providers/dns/neodigit/neodigit.go index 08ff64e04..eb4530479 100644 --- a/providers/dns/neodigit/neodigit.go +++ b/providers/dns/neodigit/neodigit.go @@ -2,18 +2,15 @@ package neodigit import ( - "context" "errors" "fmt" "net/http" - "sync" "time" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/neodigit/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/tecnocratica" ) // Environment variables names. @@ -31,14 +28,7 @@ const ( var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. -type Config struct { - Token string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} +type Config = tecnocratica.Config // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { @@ -54,12 +44,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *internal.Client - - zoneIDs map[string]int - recordIDs map[string]int - recordIDsMu sync.Mutex + prv challenge.ProviderTimeout } // NewDNSProvider returns a DNSProvider instance configured for Neodigit. @@ -81,115 +66,36 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("neodigit: the configuration of the DNS provider is nil") } - if config.Token == "" { - return nil, errors.New("neodigit: missing credentials") - } - - client, err := internal.NewClient(config.Token) + provider, err := tecnocratica.NewDNSProviderConfig(config, "") if err != nil { - return nil, fmt.Errorf("neodigit: create client: %w", err) + return nil, fmt.Errorf("neodigit: %w", err) } - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - zoneIDs: make(map[string]int), - recordIDs: make(map[string]int), - }, nil -} - -// Timeout returns the timeout and interval to use when checking for DNS propagation. -func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { - return d.config.PropagationTimeout, d.config.PollingInterval + return &DNSProvider{prv: provider}, nil } // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - ctx := context.Background() - - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("neodigit: could not find zone for domain %q: %w", domain, err) - } - - authZone = dns01.UnFqdn(authZone) - - zone, err := d.findZone(ctx, authZone) + err := d.prv.Present(domain, token, keyAuth) if err != nil { return fmt.Errorf("neodigit: %w", err) } - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("neodigit: %w", err) - } - - record := internal.Record{ - Name: subDomain, - Type: "TXT", - Content: info.Value, - TTL: d.config.TTL, - } - - newRecord, err := d.client.CreateRecord(ctx, zone.ID, record) - if err != nil { - return fmt.Errorf("neodigit: create record: %w", err) - } - - d.recordIDsMu.Lock() - d.zoneIDs[token] = zone.ID - 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) - - d.recordIDsMu.Lock() - zoneID, zoneOK := d.zoneIDs[token] - recordID, recordOK := d.recordIDs[token] - d.recordIDsMu.Unlock() - - if !zoneOK || !recordOK { - return fmt.Errorf("neodigit: unknown record ID or zone ID for '%s' '%s'", info.EffectiveFQDN, token) - } - - err := d.client.DeleteRecord(context.Background(), zoneID, recordID) + err := d.prv.CleanUp(domain, token, keyAuth) if err != nil { - return fmt.Errorf("neodigit: delete record: fqdn=%s, zoneID=%d, recordID=%d: %w", - info.EffectiveFQDN, zoneID, recordID, err) + return fmt.Errorf("neodigit: %w", err) } - d.recordIDsMu.Lock() - delete(d.zoneIDs, token) - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - return nil } -func (d *DNSProvider) findZone(ctx context.Context, zoneName string) (*internal.Zone, error) { - zones, err := d.client.GetZones(ctx) - if err != nil { - return nil, fmt.Errorf("get zones: %w", err) - } - - for _, zone := range zones { - if zone.Name == zoneName || zone.HumanName == zoneName { - return &zone, nil - } - } - - return nil, fmt.Errorf("zone not found: %s", zoneName) +// 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.prv.Timeout() } diff --git a/providers/dns/neodigit/neodigit_test.go b/providers/dns/neodigit/neodigit_test.go index 1f32d31a3..39f67c59c 100644 --- a/providers/dns/neodigit/neodigit_test.go +++ b/providers/dns/neodigit/neodigit_test.go @@ -1,13 +1,9 @@ package neodigit import ( - "net/http" - "net/http/httptest" - "net/url" "testing" "github.com/go-acme/lego/v4/platform/tester" - "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/require" ) @@ -49,8 +45,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } @@ -84,8 +79,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } @@ -120,55 +114,3 @@ func TestLiveCleanUp(t *testing.T) { err = provider.CleanUp(envTest.GetDomain(), "", "123d==") require.NoError(t, err) } - -func mockBuilder() *servermock.Builder[*DNSProvider] { - return servermock.NewBuilder( - func(server *httptest.Server) (*DNSProvider, error) { - config := NewDefaultConfig() - - config.Token = "secret" - config.HTTPClient = server.Client() - - p, err := NewDNSProviderConfig(config) - if err != nil { - return nil, err - } - - p.client.BaseURL, _ = url.Parse(server.URL) - - return p, nil - }, - servermock.CheckHeader().WithJSONHeaders(). - With("X-TCpanel-Token", "secret"), - ) -} - -func TestDNSProvider_Present(t *testing.T) { - provider := mockBuilder(). - Route("GET /dns/zones", - servermock.ResponseFromInternal("get_zones.json")). - Route("POST /dns/zones/6/records", - servermock.ResponseFromInternal("create_record.json"). - WithStatusCode(http.StatusCreated), - servermock.CheckRequestJSONBodyFromInternal("create_record-request.json")). - Build(t) - - err := provider.Present("example.com", "abc", "123d==") - require.NoError(t, err) -} - -func TestDNSProvider_CleanUp(t *testing.T) { - provider := mockBuilder(). - Route("DELETE /dns/zones/456/records/123", - servermock.Noop(). - WithStatusCode(http.StatusNoContent)). - Build(t) - - token := "abc" - - provider.recordIDs[token] = 123 - provider.zoneIDs[token] = 456 - - err := provider.CleanUp("example.com", token, "123d==") - require.NoError(t, err) -} diff --git a/providers/dns/virtualname/virtualname.go b/providers/dns/virtualname/virtualname.go new file mode 100644 index 000000000..6b04e8169 --- /dev/null +++ b/providers/dns/virtualname/virtualname.go @@ -0,0 +1,101 @@ +// Package virtualname implements a DNS provider for solving the DNS-01 challenge using Virtualname DNS. +package virtualname + +import ( + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/tecnocratica" +) + +// Environment variables names. +const ( + envNamespace = "VIRTUALNAME_" + + EnvToken = envNamespace + "TOKEN" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config = tecnocratica.Config + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + prv challenge.ProviderTimeout +} + +// NewDNSProvider returns a DNSProvider instance configured for Virtualname. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvToken) + if err != nil { + return nil, fmt.Errorf("virtualname: %w", err) + } + + config := NewDefaultConfig() + config.Token = values[EnvToken] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Virtualname. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("virtualname: the configuration of the DNS provider is nil") + } + + provider, err := tecnocratica.NewDNSProviderConfig(config, "https://api.virtualname.net/v1") + if err != nil { + return nil, fmt.Errorf("virtualname: %w", err) + } + + return &DNSProvider{prv: provider}, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + err := d.prv.Present(domain, token, keyAuth) + if err != nil { + return fmt.Errorf("virtualname: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + err := d.prv.CleanUp(domain, token, keyAuth) + if err != nil { + return fmt.Errorf("virtualname: %w", err) + } + + 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.prv.Timeout() +} diff --git a/providers/dns/virtualname/virtualname.toml b/providers/dns/virtualname/virtualname.toml new file mode 100644 index 000000000..7cc4c5344 --- /dev/null +++ b/providers/dns/virtualname/virtualname.toml @@ -0,0 +1,22 @@ +Name = "Virtualname" +Description = '''''' +URL = "https://www.virtualname.es/" +Code = "virtualname" +Since = "v4.30.0" + +Example = ''' +VIRTUALNAME_TOKEN=xxxxxx \ +lego --email you@example.com --dns virtualname -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + VIRTUALNAME_TOKEN = "API token" + [Configuration.Additional] + VIRTUALNAME_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + VIRTUALNAME_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" + VIRTUALNAME_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + VIRTUALNAME_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://developers.virtualname.net/#dns" diff --git a/providers/dns/virtualname/virtualname_test.go b/providers/dns/virtualname/virtualname_test.go new file mode 100644 index 000000000..da5867e86 --- /dev/null +++ b/providers/dns/virtualname/virtualname_test.go @@ -0,0 +1,116 @@ +package virtualname + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvToken).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvToken: "secret", + }, + }, + { + desc: "missing credentials: token", + envVars: map[string]string{ + EnvToken: "", + }, + expected: "virtualname: some credentials information are missing: VIRTUALNAME_TOKEN", + }, + } + + 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.prv) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + token string + expected string + }{ + { + desc: "success", + token: "secret", + }, + { + desc: "missing token", + expected: "virtualname: missing credentials", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Token = test.token + + p, err := NewDNSProviderConfig(config) + + if test.expected == "" { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.prv) + } 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 5a068c3a5..2b1b14b24 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -164,6 +164,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/vercel" "github.com/go-acme/lego/v4/providers/dns/versio" "github.com/go-acme/lego/v4/providers/dns/vinyldns" + "github.com/go-acme/lego/v4/providers/dns/virtualname" "github.com/go-acme/lego/v4/providers/dns/vkcloud" "github.com/go-acme/lego/v4/providers/dns/volcengine" "github.com/go-acme/lego/v4/providers/dns/vscale" @@ -500,6 +501,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return versio.NewDNSProvider() case "vinyldns": return vinyldns.NewDNSProvider() + case "virtualname": + return virtualname.NewDNSProvider() case "vkcloud": return vkcloud.NewDNSProvider() case "volcengine": From c59d163e797f4f96cb0830648b1181ef8d8e25de Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 11 Dec 2025 14:16:34 +0100 Subject: [PATCH 237/298] chore: improves github templates --- .github/ISSUE_TEMPLATE/new_dns_provider.yml | 11 +++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 3 +++ 2 files changed, 14 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/new_dns_provider.yml b/.github/ISSUE_TEMPLATE/new_dns_provider.yml index 902951ed8..cfd6e5c8c 100644 --- a/.github/ISSUE_TEMPLATE/new_dns_provider.yml +++ b/.github/ISSUE_TEMPLATE/new_dns_provider.yml @@ -40,6 +40,17 @@ body: validations: required: true + - type: dropdown + id: profile + attributes: + label: Who are you? + options: + - A customer of this DNS provider + - An employee of this DNS provider + - Other (please explain) + validations: + required: true + - type: input id: provider-link attributes: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8b1690de5..ac9fd4975 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,4 +6,7 @@ IMPORTANT: 2. Don't open a work-in-progress pull request. If you open a PR, the PR must be ready to be reviewed. 3. If a pull request doesn't follow the previous elements, it will close. +Also, pull requests from a fork inside a GitHub organization are not allowed because of access limitation on them. +Only pull requests from personal forks are allowed. + --> From 465d7918a80d1887d84f6eeb8aaee2e71622114c Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 15 Dec 2025 20:16:31 +0100 Subject: [PATCH 238/298] Add DNS provider for hosting.nl (#1967) --- README.md | 54 +++--- cmd/zz_gen_cmd_dnshelp.go | 21 +++ docs/content/dns/zz_gen_hostingnl.md | 67 +++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/hostingnl/hostingnl.go | 168 ++++++++++++++++++ providers/dns/hostingnl/hostingnl.toml | 22 +++ providers/dns/hostingnl/hostingnl_test.go | 167 +++++++++++++++++ providers/dns/hostingnl/internal/client.go | 144 +++++++++++++++ .../dns/hostingnl/internal/client_test.go | 92 ++++++++++ .../internal/fixtures/add_record-request.json | 8 + .../internal/fixtures/add_record.json | 13 ++ .../fixtures/delete_record-request.json | 5 + .../internal/fixtures/delete_record.json | 8 + .../hostingnl/internal/fixtures/error.json | 5 + .../internal/fixtures/error_other.json | 3 + providers/dns/hostingnl/internal/types.go | 36 ++++ providers/dns/zz_gen_dns_providers.go | 3 + 17 files changed, 790 insertions(+), 28 deletions(-) create mode 100644 docs/content/dns/zz_gen_hostingnl.md create mode 100644 providers/dns/hostingnl/hostingnl.go create mode 100644 providers/dns/hostingnl/hostingnl.toml create mode 100644 providers/dns/hostingnl/hostingnl_test.go create mode 100644 providers/dns/hostingnl/internal/client.go create mode 100644 providers/dns/hostingnl/internal/client_test.go create mode 100644 providers/dns/hostingnl/internal/fixtures/add_record-request.json create mode 100644 providers/dns/hostingnl/internal/fixtures/add_record.json create mode 100644 providers/dns/hostingnl/internal/fixtures/delete_record-request.json create mode 100644 providers/dns/hostingnl/internal/fixtures/delete_record.json create mode 100644 providers/dns/hostingnl/internal/fixtures/error.json create mode 100644 providers/dns/hostingnl/internal/fixtures/error_other.json create mode 100644 providers/dns/hostingnl/internal/types.go diff --git a/README.md b/README.md index 570a340e5..3b395aad9 100644 --- a/README.md +++ b/README.md @@ -146,138 +146,138 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Hetzner Hosting.de + Hosting.nl Hostinger - Hosttech + Hosttech HTTP request http.net Huawei Cloud - Hurricane Electric DNS + Hurricane Electric DNS HyperOne IBM Cloud (SoftLayer) IIJ DNS Platform Service - Infoblox + Infoblox Infomaniak Internet Initiative Japan Internet.bs - INWX + INWX Ionos IPv64 iwantmyname (Deprecated) - Joker + Joker Joohoi's ACME-DNS KeyHelp Liara - Lima-City + Lima-City Linode (v4) Liquid Web Loopia - LuaDNS + LuaDNS Mail-in-a-Box ManageEngine CloudDNS Manual - Metaname + Metaname Metaregistrar mijn.host Mittwald - myaddr.{tools,dev,io} + myaddr.{tools,dev,io} MyDNS.jp MythicBeasts Name.com - Namecheap + Namecheap Namesilo NearlyFreeSpeech.NET Neodigit - Netcup + Netcup Netlify Nicmanager NIFCloud - Njalla + Njalla Nodion NS1 Octenium - Open Telekom Cloud + Open Telekom Cloud Oracle Cloud OVH plesk.com - Porkbun + Porkbun PowerDNS Rackspace Rain Yun/雨云 - RcodeZero + RcodeZero reg.ru Regfish RFC2136 - RimuHosting + RimuHosting RU CENTER Sakura Cloud Scaleway - Selectel + Selectel Selectel v2 SelfHost.(de|eu) Servercow - Shellrent + Shellrent Simply.com Sonic Spaceship - Stackpath + Stackpath Syse Technitium Tencent Cloud DNS - Tencent EdgeOne + Tencent EdgeOne Timeweb Cloud TransIP UKFast SafeDNS - Ultradns + Ultradns United-Domains Variomedia VegaDNS - Vercel + Vercel Versio.[nl|eu|uk] VinylDNS Virtualname - VK Cloud + VK Cloud Volcano Engine/火山引擎 Vscale Vultr - webnames.ca + webnames.ca webnames.ru Websupport WEDOS - West.cn/西部数码 + West.cn/西部数码 Yandex 360 Yandex Cloud Yandex PDD - Zone.ee + Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index ebd4cc72b..89b3ce7e1 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -83,6 +83,7 @@ func allDNSCodes() string { "hetzner", "hostingde", "hostinger", + "hostingnl", "hosttech", "httpnet", "httpreq", @@ -1723,6 +1724,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/hostinger`) + case "hostingnl": + // generated from: providers/dns/hostingnl/hostingnl.toml + ew.writeln(`Configuration for Hosting.nl.`) + ew.writeln(`Code: 'hostingnl'`) + ew.writeln(`Since: 'v4.30.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "HOSTINGNL_API_KEY": The API key`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "HOSTINGNL_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "HOSTINGNL_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "HOSTINGNL_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "HOSTINGNL_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/hostingnl`) + case "hosttech": // generated from: providers/dns/hosttech/hosttech.toml ew.writeln(`Configuration for Hosttech.`) diff --git a/docs/content/dns/zz_gen_hostingnl.md b/docs/content/dns/zz_gen_hostingnl.md new file mode 100644 index 000000000..0577affd4 --- /dev/null +++ b/docs/content/dns/zz_gen_hostingnl.md @@ -0,0 +1,67 @@ +--- +title: "Hosting.nl" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: hostingnl +dnsprovider: + since: "v4.30.0" + code: "hostingnl" + url: "https://hosting.nl" +--- + + + + + + +Configuration for [Hosting.nl](https://hosting.nl). + + + + +- Code: `hostingnl` +- Since: v4.30.0 + + +Here is an example bash command using the Hosting.nl provider: + +```bash +HOSTINGNL_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns hostingnl -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `HOSTINGNL_API_KEY` | The 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 | +|--------------------------------|-------------| +| `HOSTINGNL_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `HOSTINGNL_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `HOSTINGNL_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `HOSTINGNL_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://api.hosting.nl/api/documentation) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 506c7e47d..7962f0d73 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/hostingnl/hostingnl.go b/providers/dns/hostingnl/hostingnl.go new file mode 100644 index 000000000..a49941817 --- /dev/null +++ b/providers/dns/hostingnl/hostingnl.go @@ -0,0 +1,168 @@ +// Package hostingnl implements a DNS provider for solving the DNS-01 challenge using hosting.nl. +package hostingnl + +import ( + "context" + "errors" + "fmt" + "net/http" + "strconv" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/hostingnl/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" +) + +// Environment variables names. +const ( + envNamespace = "HOSTINGNL_" + + EnvAPIKey = envNamespace + "API_KEY" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + APIKey string + HTTPClient *http.Client + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 10*time.Second), + }, + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client + + recordIDs map[string]string + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for hosting.nl. +// Credentials must be passed in the environment variables: +// HOSTINGNL_APIKEY. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIKey) + if err != nil { + return nil, fmt.Errorf("hostingnl: %w", err) + } + + config := NewDefaultConfig() + config.APIKey = values[EnvAPIKey] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for hosting.nl. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("hostingnl: the configuration of the DNS provider is nil") + } + + if config.APIKey == "" { + return nil, errors.New("hostingnl: APIKey is missing") + } + + client := internal.NewClient(config.APIKey) + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + recordIDs: make(map[string]string), + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("hostingnl: could not find zone for domain %q: %w", domain, err) + } + + record := internal.Record{ + Name: dns01.UnFqdn(info.EffectiveFQDN), + Type: "TXT", + Content: strconv.Quote(info.Value), + TTL: d.config.TTL, + Priority: 0, + } + + newRecord, err := d.client.AddRecord(context.Background(), dns01.UnFqdn(authZone), record) + if err != nil { + return fmt.Errorf("hostingnl: failed to create TXT record, fqdn=%s: %w", info.EffectiveFQDN, err) + } + + d.recordIDsMu.Lock() + d.recordIDs[token] = newRecord.ID + d.recordIDsMu.Unlock() + + return nil +} + +// CleanUp removes the TXT records matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("hostingnl: could not find zone for domain %q: %w", domain, err) + } + + // gets the record's unique ID + d.recordIDsMu.Lock() + recordID, ok := d.recordIDs[token] + d.recordIDsMu.Unlock() + + if !ok { + return fmt.Errorf("hostingnl: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) + } + + err = d.client.DeleteRecord(context.Background(), dns01.UnFqdn(authZone), recordID) + if err != nil { + return fmt.Errorf("hostingnl: failed to delete TXT record, id=%s: %w", recordID, err) + } + + // deletes 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/hostingnl/hostingnl.toml b/providers/dns/hostingnl/hostingnl.toml new file mode 100644 index 000000000..a26c07ab2 --- /dev/null +++ b/providers/dns/hostingnl/hostingnl.toml @@ -0,0 +1,22 @@ +Name = "Hosting.nl" +Description = '''''' +URL = "https://hosting.nl" +Code = "hostingnl" +Since = "v4.30.0" + +Example = ''' +HOSTINGNL_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns hostingnl -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + HOSTINGNL_API_KEY = "The API key" + [Configuration.Additional] + HOSTINGNL_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + HOSTINGNL_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + HOSTINGNL_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + HOSTINGNL_HTTP_TIMEOUT = "API request timeout in seconds (Default: 10)" + +[Links] + API = "https://api.hosting.nl/api/documentation" diff --git a/providers/dns/hostingnl/hostingnl_test.go b/providers/dns/hostingnl/hostingnl_test.go new file mode 100644 index 000000000..cef754c7c --- /dev/null +++ b/providers/dns/hostingnl/hostingnl_test.go @@ -0,0 +1,167 @@ +package hostingnl + +import ( + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "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: "key", + }, + }, + { + desc: "missing API key", + envVars: map[string]string{}, + expected: "hostingnl: some credentials information are missing: HOSTINGNL_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: "key", + }, + { + desc: "missing API key", + expected: "hostingnl: APIKey is 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.APIKey = "secret" + config.HTTPClient = server.Client() + + provider, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + provider.client.BaseURL, _ = url.Parse(server.URL) + + return provider, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With("API-TOKEN", "secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("POST /domains/example.com/dns", + servermock.ResponseFromInternal("add_record.json"), + servermock.CheckQueryParameter().Strict(), + servermock.CheckRequestJSONBodyFromInternal("add_record-request.json")). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("DELETE /domains/example.com/dns", + servermock.ResponseFromInternal("delete_record.json"), + servermock.CheckQueryParameter().Strict(), + servermock.CheckRequestJSONBodyFromInternal("delete_record-request.json")). + Build(t) + + provider.recordIDs["abc"] = "12345" + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/hostingnl/internal/client.go b/providers/dns/hostingnl/internal/client.go new file mode 100644 index 000000000..f2d7b5346 --- /dev/null +++ b/providers/dns/hostingnl/internal/client.go @@ -0,0 +1,144 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" +) + +const defaultBaseURL = "https://api.hosting.nl" + +type Client struct { + apiKey string + + BaseURL *url.URL + HTTPClient *http.Client +} + +func NewClient(apiKey string) *Client { + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + apiKey: apiKey, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 5 * time.Second}, + } +} + +func (c Client) AddRecord(ctx context.Context, domain string, record Record) (*Record, error) { + endpoint := c.BaseURL.JoinPath("domains", domain, "dns") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, []Record{record}) + if err != nil { + return nil, err + } + + var result APIResponse[Record] + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + if len(result.Data) != 1 { + return nil, fmt.Errorf("unexpected response data: %v", result.Data) + } + + return &result.Data[0], nil +} + +func (c Client) DeleteRecord(ctx context.Context, domain, recordID string) error { + endpoint := c.BaseURL.JoinPath("domains", domain, "dns") + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, []Record{{ID: recordID}}) + if err != nil { + return err + } + + var result APIResponse[Record] + + err = c.do(req, &result) + if err != nil { + return err + } + + return nil +} + +func (c Client) do(req *http.Request, result any) error { + useragent.SetHeader(req.Header) + + req.Header.Set("API-TOKEN", c.apiKey) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var apiErr APIError + + err := json.Unmarshal(raw, &apiErr) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return fmt.Errorf("[status code: %d] %w", resp.StatusCode, apiErr) +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} diff --git a/providers/dns/hostingnl/internal/client_test.go b/providers/dns/hostingnl/internal/client_test.go new file mode 100644 index 000000000..efdb98980 --- /dev/null +++ b/providers/dns/hostingnl/internal/client_test.go @@ -0,0 +1,92 @@ +package internal + +import ( + "context" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder( + func(server *httptest.Server) (*Client, error) { + client := NewClient("secret") + client.HTTPClient = server.Client() + client.BaseURL, _ = url.Parse(server.URL) + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With("API-TOKEN", "secret"), + ) +} + +func TestClient_AddRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /domains/example.com/dns", + servermock.ResponseFromFixture("add_record.json"), + servermock.CheckQueryParameter().Strict(), + servermock.CheckRequestJSONBodyFromFixture("add_record-request.json")). + Build(t) + + record := Record{ + Name: "_acme-challenge.example.com", + Type: "TXT", + Content: strconv.Quote("ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"), + TTL: 120, + } + + newRecord, err := client.AddRecord(context.Background(), "example.com", record) + require.NoError(t, err) + + expected := &Record{ + ID: "12345", + Name: "_acme-challenge.example.com", + Type: "TXT", + Content: strconv.Quote("ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"), + TTL: 120, + } + + assert.Equal(t, expected, newRecord) +} + +func TestClient_DeleteRecord(t *testing.T) { + client := mockBuilder(). + Route("DELETE /domains/example.com/dns", + servermock.ResponseFromFixture("delete_record.json"), + servermock.CheckQueryParameter().Strict(), + servermock.CheckRequestJSONBodyFromFixture("delete_record-request.json")). + Build(t) + + err := client.DeleteRecord(context.Background(), "example.com", "12345") + require.NoError(t, err) +} + +func TestClient_DeleteRecord_error(t *testing.T) { + client := mockBuilder(). + Route("DELETE /domains/example.com/dns", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + err := client.DeleteRecord(context.Background(), "example.com", "12345") + require.EqualError(t, err, "[status code: 401] Something went wrong") +} + +func TestClient_DeleteRecord_error_other(t *testing.T) { + client := mockBuilder(). + Route("DELETE /domains/example.com/dns", + servermock.ResponseFromFixture("error_other.json"). + WithStatusCode(http.StatusNotFound)). + Build(t) + + err := client.DeleteRecord(context.Background(), "example.com", "12345") + require.EqualError(t, err, "[status code: 404] Resource not found") +} diff --git a/providers/dns/hostingnl/internal/fixtures/add_record-request.json b/providers/dns/hostingnl/internal/fixtures/add_record-request.json new file mode 100644 index 000000000..6b68ec3c6 --- /dev/null +++ b/providers/dns/hostingnl/internal/fixtures/add_record-request.json @@ -0,0 +1,8 @@ +[ + { + "name": "_acme-challenge.example.com", + "type": "TXT", + "content": "\"ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY\"", + "ttl": 120 + } +] diff --git a/providers/dns/hostingnl/internal/fixtures/add_record.json b/providers/dns/hostingnl/internal/fixtures/add_record.json new file mode 100644 index 000000000..a822a4f8d --- /dev/null +++ b/providers/dns/hostingnl/internal/fixtures/add_record.json @@ -0,0 +1,13 @@ +{ + "success": true, + "data": [ + { + "id": "12345", + "type": "TXT", + "content": "\"ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY\"", + "name": "_acme-challenge.example.com", + "prio": 0, + "ttl": 120 + } + ] +} diff --git a/providers/dns/hostingnl/internal/fixtures/delete_record-request.json b/providers/dns/hostingnl/internal/fixtures/delete_record-request.json new file mode 100644 index 000000000..cfc26d2b9 --- /dev/null +++ b/providers/dns/hostingnl/internal/fixtures/delete_record-request.json @@ -0,0 +1,5 @@ +[ + { + "id": "12345" + } +] diff --git a/providers/dns/hostingnl/internal/fixtures/delete_record.json b/providers/dns/hostingnl/internal/fixtures/delete_record.json new file mode 100644 index 000000000..c041c1f6d --- /dev/null +++ b/providers/dns/hostingnl/internal/fixtures/delete_record.json @@ -0,0 +1,8 @@ +{ + "success": true, + "data": [ + { + "id": "12345" + } + ] +} diff --git a/providers/dns/hostingnl/internal/fixtures/error.json b/providers/dns/hostingnl/internal/fixtures/error.json new file mode 100644 index 000000000..170587246 --- /dev/null +++ b/providers/dns/hostingnl/internal/fixtures/error.json @@ -0,0 +1,5 @@ +{ + "errors": { + "message": "Something went wrong" + } +} diff --git a/providers/dns/hostingnl/internal/fixtures/error_other.json b/providers/dns/hostingnl/internal/fixtures/error_other.json new file mode 100644 index 000000000..ca7ecab28 --- /dev/null +++ b/providers/dns/hostingnl/internal/fixtures/error_other.json @@ -0,0 +1,3 @@ +{ + "error": "Resource not found" +} diff --git a/providers/dns/hostingnl/internal/types.go b/providers/dns/hostingnl/internal/types.go new file mode 100644 index 000000000..f324665fe --- /dev/null +++ b/providers/dns/hostingnl/internal/types.go @@ -0,0 +1,36 @@ +package internal + +type Record struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Content string `json:"content,omitempty"` + TTL int `json:"ttl,omitempty"` + Priority int `json:"prio,omitempty"` +} + +type APIResponse[T any] struct { + Success bool `json:"success"` + Data []T `json:"data"` +} + +type APIError struct { + ErrorMsg string `json:"error"` + Errors Error `json:"errors"` +} + +func (e APIError) Error() string { + if e.ErrorMsg != "" { + return e.ErrorMsg + } + + return e.Errors.Error() +} + +type Error struct { + Message string `json:"message"` +} + +func (e Error) Error() string { + return e.Message +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 2b1b14b24..3b7b8c5fc 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -77,6 +77,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/hetzner" "github.com/go-acme/lego/v4/providers/dns/hostingde" "github.com/go-acme/lego/v4/providers/dns/hostinger" + "github.com/go-acme/lego/v4/providers/dns/hostingnl" "github.com/go-acme/lego/v4/providers/dns/hosttech" "github.com/go-acme/lego/v4/providers/dns/httpnet" "github.com/go-acme/lego/v4/providers/dns/httpreq" @@ -327,6 +328,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return hostingde.NewDNSProvider() case "hostinger": return hostinger.NewDNSProvider() + case "hostingnl": + return hostingnl.NewDNSProvider() case "hosttech": return hosttech.NewDNSProvider() case "httpnet": From a6e6b92d35eaf8f2f9dc49311d24331a036ce65c Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 10 Dec 2025 19:07:25 +0100 Subject: [PATCH 239/298] chore: clean maps --- providers/dns/aliesa/aliesa.go | 4 ++++ providers/dns/allinkl/allinkl.go | 4 ++++ providers/dns/auroradns/auroradns.go | 5 +++-- providers/dns/azion/azion.go | 22 ++------------------ providers/dns/binarylane/binarylane.go | 4 ++++ providers/dns/checkdomain/internal/client.go | 6 +++--- providers/dns/cloudru/cloudru.go | 5 +++-- providers/dns/designate/designate.go | 5 +++-- providers/dns/easydns/easydns.go | 10 ++++----- providers/dns/edgeone/edgeone.go | 11 ++++++---- providers/dns/gravity/gravity.go | 4 ++++ providers/dns/hosttech/hosttech.go | 4 ++++ providers/dns/huaweicloud/huaweicloud.go | 4 ++++ providers/dns/internal/hostingde/provider.go | 10 ++++----- providers/dns/limacity/limacity.go | 4 ++++ providers/dns/liquidweb/liquidweb.go | 5 +++-- providers/dns/luadns/luadns.go | 7 +++---- providers/dns/metaname/metaname.go | 4 ++++ providers/dns/mittwald/mittwald.go | 4 ++++ providers/dns/nodion/nodion.go | 4 ++++ providers/dns/octenium/octenium.go | 4 ++++ providers/dns/ovh/ovh.go | 5 +++-- providers/dns/plesk/plesk.go | 4 ++++ providers/dns/porkbun/porkbun.go | 4 ++++ providers/dns/selfhostde/selfhostde.go | 4 ++++ providers/dns/shellrent/shellrent.go | 4 ++++ providers/dns/syse/syse.go | 4 ++++ providers/dns/variomedia/variomedia.go | 4 ++++ providers/dns/volcengine/volcengine.go | 4 ++++ 29 files changed, 111 insertions(+), 52 deletions(-) diff --git a/providers/dns/aliesa/aliesa.go b/providers/dns/aliesa/aliesa.go index deb8162da..2a38389be 100644 --- a/providers/dns/aliesa/aliesa.go +++ b/providers/dns/aliesa/aliesa.go @@ -212,6 +212,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("aliesa: delete record: %w", err) } + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + return nil } diff --git a/providers/dns/allinkl/allinkl.go b/providers/dns/allinkl/allinkl.go index a5b27ff59..7e8f5ab4e 100644 --- a/providers/dns/allinkl/allinkl.go +++ b/providers/dns/allinkl/allinkl.go @@ -186,5 +186,9 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("allinkl: %w", err) } + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + return nil } diff --git a/providers/dns/auroradns/auroradns.go b/providers/dns/auroradns/auroradns.go index 95d6ab759..50d2fbc25 100644 --- a/providers/dns/auroradns/auroradns.go +++ b/providers/dns/auroradns/auroradns.go @@ -53,10 +53,11 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { + config *Config + client *auroradns.Client + recordIDs map[string]string recordIDsMu sync.Mutex - config *Config - client *auroradns.Client } // NewDNSProvider returns a DNSProvider instance configured for AuroraDNS. diff --git a/providers/dns/azion/azion.go b/providers/dns/azion/azion.go index 8150d90d5..5584ece0b 100644 --- a/providers/dns/azion/azion.go +++ b/providers/dns/azion/azion.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "net/http" - "sync" "time" "github.com/aziontech/azionapi-go-sdk/idns" @@ -56,9 +55,6 @@ func NewDefaultConfig() *Config { type DNSProvider struct { config *Config client *idns.APIClient - - recordIDs map[string]int32 - recordIDsMu sync.Mutex } // NewDNSProvider returns a DNSProvider instance configured for Azion. @@ -98,9 +94,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client := idns.NewAPIClient(clientConfig) return &DNSProvider{ - config: config, - client: client, - recordIDs: make(map[string]int32), + config: config, + client: client, }, nil } @@ -161,12 +156,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return errors.New("azion: create zone record error") } - results := resp.GetResults() - - d.recordIDsMu.Lock() - d.recordIDs[token] = results.GetId() - d.recordIDsMu.Unlock() - return nil } @@ -186,13 +175,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("azion: %w", err) } - defer func() { - // Cleans the record ID. - d.recordIDsMu.Lock() - delete(d.recordIDs, token) - d.recordIDsMu.Unlock() - }() - existingRecord, err := d.findExistingTXTRecord(ctxAuth, zone.GetId(), subDomain) if err != nil { return fmt.Errorf("azion: find existing record: %w", err) diff --git a/providers/dns/binarylane/binarylane.go b/providers/dns/binarylane/binarylane.go index 9ff80d698..5bbb7a16a 100644 --- a/providers/dns/binarylane/binarylane.go +++ b/providers/dns/binarylane/binarylane.go @@ -151,6 +151,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("binarylane: delete record: %w", err) } + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + return nil } diff --git a/providers/dns/checkdomain/internal/client.go b/providers/dns/checkdomain/internal/client.go index d626275ab..68d090755 100644 --- a/providers/dns/checkdomain/internal/client.go +++ b/providers/dns/checkdomain/internal/client.go @@ -36,11 +36,11 @@ const maxInt = int((^uint(0)) >> 1) // Client the Autodns API client. type Client struct { - domainIDMapping map[string]int - domainIDMu sync.Mutex - BaseURL *url.URL httpClient *http.Client + + domainIDMapping map[string]int + domainIDMu sync.Mutex } // NewClient creates a new Client. diff --git a/providers/dns/cloudru/cloudru.go b/providers/dns/cloudru/cloudru.go index 287c12045..dd597952a 100644 --- a/providers/dns/cloudru/cloudru.go +++ b/providers/dns/cloudru/cloudru.go @@ -61,8 +61,9 @@ func NewDefaultConfig() *Config { } type DNSProvider struct { - config *Config - client *internal.Client + config *Config + client *internal.Client + records map[string]*internal.Record recordsMu sync.Mutex } diff --git a/providers/dns/designate/designate.go b/providers/dns/designate/designate.go index 47c8ad8f1..41bf251f6 100644 --- a/providers/dns/designate/designate.go +++ b/providers/dns/designate/designate.go @@ -68,8 +68,9 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *gophercloud.ServiceClient + config *Config + client *gophercloud.ServiceClient + dnsEntriesMu sync.Mutex } diff --git a/providers/dns/easydns/easydns.go b/providers/dns/easydns/easydns.go index ae0a0c3b8..205063e7b 100644 --- a/providers/dns/easydns/easydns.go +++ b/providers/dns/easydns/easydns.go @@ -190,16 +190,14 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } err = d.client.DeleteRecord(ctx, dns01.UnFqdn(authZone), recordID) - - d.recordIDsMu.Lock() - defer delete(d.recordIDs, key) - - d.recordIDsMu.Unlock() - if err != nil { return fmt.Errorf("easydns: %w", err) } + d.recordIDsMu.Lock() + delete(d.recordIDs, key) + d.recordIDsMu.Unlock() + return nil } diff --git a/providers/dns/edgeone/edgeone.go b/providers/dns/edgeone/edgeone.go index 509a75c77..6931c6715 100644 --- a/providers/dns/edgeone/edgeone.go +++ b/providers/dns/edgeone/edgeone.go @@ -119,10 +119,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { } return &DNSProvider{ - config: config, - client: client, - recordIDs: map[string]*string{}, - recordIDsMu: sync.Mutex{}, + config: config, + client: client, + recordIDs: map[string]*string{}, }, nil } @@ -190,6 +189,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("edgeone: delete record failed: %w", err) } + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + return nil } diff --git a/providers/dns/gravity/gravity.go b/providers/dns/gravity/gravity.go index c8594441a..b0bbb2fcb 100644 --- a/providers/dns/gravity/gravity.go +++ b/providers/dns/gravity/gravity.go @@ -163,6 +163,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("gravity: delete record: %w", err) } + d.recordsMu.Lock() + delete(d.records, token) + d.recordsMu.Unlock() + return nil } diff --git a/providers/dns/hosttech/hosttech.go b/providers/dns/hosttech/hosttech.go index fac64f054..73346f6cb 100644 --- a/providers/dns/hosttech/hosttech.go +++ b/providers/dns/hosttech/hosttech.go @@ -174,5 +174,9 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("hosttech: %w", err) } + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + return nil } diff --git a/providers/dns/huaweicloud/huaweicloud.go b/providers/dns/huaweicloud/huaweicloud.go index 5a2773ab2..e47f3e2b5 100644 --- a/providers/dns/huaweicloud/huaweicloud.go +++ b/providers/dns/huaweicloud/huaweicloud.go @@ -209,6 +209,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("huaweicloud: delete record: %w", err) } + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + return nil } diff --git a/providers/dns/internal/hostingde/provider.go b/providers/dns/internal/hostingde/provider.go index 644dc8aaf..b5277f042 100644 --- a/providers/dns/internal/hostingde/provider.go +++ b/providers/dns/internal/hostingde/provider.go @@ -165,16 +165,16 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { RecordsToDelete: rec, } - // Delete record ID from map - d.recordIDsMu.Lock() - delete(d.recordIDs, info.EffectiveFQDN) - d.recordIDsMu.Unlock() - _, err = d.client.UpdateZone(ctx, req) if err != nil { return err } + // Delete record ID from map + d.recordIDsMu.Lock() + delete(d.recordIDs, info.EffectiveFQDN) + d.recordIDsMu.Unlock() + return nil } diff --git a/providers/dns/limacity/limacity.go b/providers/dns/limacity/limacity.go index 9e1f58f1a..3291faf66 100644 --- a/providers/dns/limacity/limacity.go +++ b/providers/dns/limacity/limacity.go @@ -193,6 +193,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("limacity: delete record (domain ID=%d, record ID=%d): %w", domainID, recordID, err) } + d.domainIDsMu.Lock() + delete(d.domainIDs, info.EffectiveFQDN) + d.domainIDsMu.Unlock() + return nil } diff --git a/providers/dns/liquidweb/liquidweb.go b/providers/dns/liquidweb/liquidweb.go index b56968fe3..6e93e2a12 100644 --- a/providers/dns/liquidweb/liquidweb.go +++ b/providers/dns/liquidweb/liquidweb.go @@ -62,8 +62,9 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *lw.API + config *Config + client *lw.API + recordIDs map[string]int recordIDsMu sync.Mutex } diff --git a/providers/dns/luadns/luadns.go b/providers/dns/luadns/luadns.go index 02108ce62..68b9c66b8 100644 --- a/providers/dns/luadns/luadns.go +++ b/providers/dns/luadns/luadns.go @@ -104,10 +104,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { client.HTTPClient = clientdebug.Wrap(client.HTTPClient) return &DNSProvider{ - config: config, - client: client, - recordsMu: sync.Mutex{}, - records: make(map[string]*internal.DNSRecord), + config: config, + client: client, + records: make(map[string]*internal.DNSRecord), }, nil } diff --git a/providers/dns/metaname/metaname.go b/providers/dns/metaname/metaname.go index d5d87dc4d..d6e962024 100644 --- a/providers/dns/metaname/metaname.go +++ b/providers/dns/metaname/metaname.go @@ -153,6 +153,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("metaname: delete record: %w", err) } + d.recordsMu.Lock() + delete(d.records, token) + d.recordsMu.Unlock() + return nil } diff --git a/providers/dns/mittwald/mittwald.go b/providers/dns/mittwald/mittwald.go index 6292dd787..dcd882482 100644 --- a/providers/dns/mittwald/mittwald.go +++ b/providers/dns/mittwald/mittwald.go @@ -170,6 +170,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("mittwald: update/delete TXT record: %w", err) } + d.zoneIDsMu.Lock() + delete(d.zoneIDs, token) + d.zoneIDsMu.Unlock() + return nil } diff --git a/providers/dns/nodion/nodion.go b/providers/dns/nodion/nodion.go index e34d7db28..4bc887568 100644 --- a/providers/dns/nodion/nodion.go +++ b/providers/dns/nodion/nodion.go @@ -208,5 +208,9 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("regru: failed to remove TXT records [domain: %s]: %w", dns01.UnFqdn(authZone), err) } + d.zoneIDsMu.Lock() + delete(d.zoneIDs, token) + d.zoneIDsMu.Unlock() + return nil } diff --git a/providers/dns/octenium/octenium.go b/providers/dns/octenium/octenium.go index af469f5ed..6032dcce1 100644 --- a/providers/dns/octenium/octenium.go +++ b/providers/dns/octenium/octenium.go @@ -169,6 +169,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { break } + d.domainIDsMu.Lock() + delete(d.domainIDs, token) + d.domainIDsMu.Unlock() + return nil } diff --git a/providers/dns/ovh/ovh.go b/providers/dns/ovh/ovh.go index b7e522540..a8d12d819 100644 --- a/providers/dns/ovh/ovh.go +++ b/providers/dns/ovh/ovh.go @@ -102,8 +102,9 @@ func (c *Config) hasAppKeyAuth() bool { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *ovh.Client + config *Config + client *ovh.Client + recordIDs map[string]int64 recordIDsMu sync.Mutex } diff --git a/providers/dns/plesk/plesk.go b/providers/dns/plesk/plesk.go index b764dff33..5f07dcb50 100644 --- a/providers/dns/plesk/plesk.go +++ b/providers/dns/plesk/plesk.go @@ -173,5 +173,9 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("plesk: failed to delete record (%d): %w", recordID, err) } + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + return nil } diff --git a/providers/dns/porkbun/porkbun.go b/providers/dns/porkbun/porkbun.go index dc9efb013..2f999ebcc 100644 --- a/providers/dns/porkbun/porkbun.go +++ b/providers/dns/porkbun/porkbun.go @@ -171,6 +171,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("porkbun: failed to delete record: %w", err) } + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + return nil } diff --git a/providers/dns/selfhostde/selfhostde.go b/providers/dns/selfhostde/selfhostde.go index bb475deea..035cd5363 100644 --- a/providers/dns/selfhostde/selfhostde.go +++ b/providers/dns/selfhostde/selfhostde.go @@ -186,5 +186,9 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("selfhostde: emptied DNS TXT record (id=%s): %w", recordID, err) } + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + return nil } diff --git a/providers/dns/shellrent/shellrent.go b/providers/dns/shellrent/shellrent.go index 5a3a1f6de..0cd33e19a 100644 --- a/providers/dns/shellrent/shellrent.go +++ b/providers/dns/shellrent/shellrent.go @@ -172,6 +172,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("shellrent: delete record: %w", err) } + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + return nil } diff --git a/providers/dns/syse/syse.go b/providers/dns/syse/syse.go index aab07131f..29633280c 100644 --- a/providers/dns/syse/syse.go +++ b/providers/dns/syse/syse.go @@ -172,6 +172,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("syse: delete record: %w", err) } + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + return nil } diff --git a/providers/dns/variomedia/variomedia.go b/providers/dns/variomedia/variomedia.go index 90ac70a05..2d12fd975 100644 --- a/providers/dns/variomedia/variomedia.go +++ b/providers/dns/variomedia/variomedia.go @@ -180,6 +180,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("variomedia: %w", err) } + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + return nil } diff --git a/providers/dns/volcengine/volcengine.go b/providers/dns/volcengine/volcengine.go index 9a5886e6d..765d38adb 100644 --- a/providers/dns/volcengine/volcengine.go +++ b/providers/dns/volcengine/volcengine.go @@ -171,6 +171,10 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return fmt.Errorf("volcengine: delete record: %w", err) } + d.recordIDsMu.Lock() + delete(d.recordIDs, token) + d.recordIDsMu.Unlock() + return nil } From bb5e70a4e5f682e2ef140dbe71665b539a62764e Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 15 Dec 2025 23:34:25 +0100 Subject: [PATCH 240/298] docs: improve contributing guide --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- CONTRIBUTING.md | 23 ++++++++--------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ac9fd4975..795320a8d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,7 +4,7 @@ IMPORTANT: 1. Create an issue and wait for a maintainer to approve it BEFORE opening a pull request. 2. Don't open a work-in-progress pull request. If you open a PR, the PR must be ready to be reviewed. -3. If a pull request doesn't follow the previous elements, it will close. +3. If a pull request doesn't follow one of the previous elements, it will be closed. Also, pull requests from a fork inside a GitHub organization are not allowed because of access limitation on them. Only pull requests from personal forks are allowed. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a0005cff8..05e4fa994 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ To ensure a great and easy experience for everyone, please review the few guidel - If both of the above do not apply, create a new issue and include as much information as possible. Bug reports should include all information a person could need to reproduce your problem without the need to -follow up for more information. If possible, provide detailed steps for us to reproduce it, the expected behaviour and the actual behaviour. +follow up for more information. If possible, provide detailed steps for us to reproduce it, the expected behavior and the actual behavior. ## Feature proposals and requests @@ -20,31 +20,26 @@ It is up to you to make a strong point about your proposal and convince us of th ## Pull requests +Create an issue and wait for a maintainer to approve it BEFORE opening a pull request. + Patches, new features and improvements are a great way to help the project. Please keep them focused on one thing and do not include unrelated commits. -All pull requests which alter the behaviour of the program, add new behaviour or somehow alter code in a non-trivial way should **always** include tests. +All pull requests that alter the behavior of the program, +add new behavior or somehow alter code in a non-trivial way should **always** include tests. -If you want to contribute a significant pull request (with a non-trivial workload for you) please **ask first**. We do not want you to spend -a lot of time on something the project's developers might not want to merge into the project. - -**IMPORTANT**: By submitting a patch, you agree to allow the project -owners to license your work under the terms of the [MIT License](LICENSE). +**IMPORTANT**: By submitting a patch, you agree to allow the project owners to license your work under the terms of the [MIT License](LICENSE). ### How to create a pull request Requirements: -- `go` v1.15+ +- `go` v1.24+ - environment variable: `GO111MODULE=on` First, you have to install [GoLang](https://golang.org/doc/install) and [golangci-lint](https://github.com/golangci/golangci-lint#install). ```bash -# Create the root folder -mkdir -p $GOPATH/src/github.com/go-acme -cd $GOPATH/src/github.com/go-acme - # clone your fork git clone git@github.com:YOUR_USERNAME/lego.git cd lego @@ -56,14 +51,12 @@ git fetch upstream ```bash # Create your branch -git checkout -b my-feature +git switch -c my-feature ## Create your code ## ``` ```bash -# Format -make fmt # Linters make checks # Tests From e21ba75da8da148f9b0cff3b298fdd63d151e51f Mon Sep 17 00:00:00 2001 From: Karl Fritsche Date: Mon, 15 Dec 2025 23:45:24 +0100 Subject: [PATCH 241/298] Add DNS provider for Ionos Cloud (#2752) Co-authored-by: Fernandez Ludovic --- README.md | 46 ++--- cmd/zz_gen_cmd_dnshelp.go | 21 ++ docs/content/dns/zz_gen_ionoscloud.md | 67 +++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/ionoscloud/internal/client.go | 172 ++++++++++++++++ .../dns/ionoscloud/internal/client_test.go | 134 +++++++++++++ .../fixtures/create_record-request.json | 8 + .../internal/fixtures/create_record.json | 25 +++ .../ionoscloud/internal/fixtures/error.json | 9 + .../ionoscloud/internal/fixtures/zones.json | 40 ++++ providers/dns/ionoscloud/internal/types.go | 97 +++++++++ providers/dns/ionoscloud/ionoscloud.go | 184 ++++++++++++++++++ providers/dns/ionoscloud/ionoscloud.toml | 22 +++ providers/dns/ionoscloud/ionoscloud_test.go | 173 ++++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 15 files changed, 979 insertions(+), 24 deletions(-) create mode 100644 docs/content/dns/zz_gen_ionoscloud.md create mode 100644 providers/dns/ionoscloud/internal/client.go create mode 100644 providers/dns/ionoscloud/internal/client_test.go create mode 100644 providers/dns/ionoscloud/internal/fixtures/create_record-request.json create mode 100644 providers/dns/ionoscloud/internal/fixtures/create_record.json create mode 100644 providers/dns/ionoscloud/internal/fixtures/error.json create mode 100644 providers/dns/ionoscloud/internal/fixtures/zones.json create mode 100644 providers/dns/ionoscloud/internal/types.go create mode 100644 providers/dns/ionoscloud/ionoscloud.go create mode 100644 providers/dns/ionoscloud/ionoscloud.toml create mode 100644 providers/dns/ionoscloud/ionoscloud_test.go diff --git a/README.md b/README.md index 3b395aad9..ff9473e58 100644 --- a/README.md +++ b/README.md @@ -166,118 +166,118 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). INWX Ionos + Ionos Cloud IPv64 - iwantmyname (Deprecated) + iwantmyname (Deprecated) Joker Joohoi's ACME-DNS KeyHelp - Liara + Liara Lima-City Linode (v4) Liquid Web - Loopia + Loopia LuaDNS Mail-in-a-Box ManageEngine CloudDNS - Manual + Manual Metaname Metaregistrar mijn.host - Mittwald + Mittwald myaddr.{tools,dev,io} MyDNS.jp MythicBeasts - Name.com + Name.com Namecheap Namesilo NearlyFreeSpeech.NET - Neodigit + Neodigit Netcup Netlify Nicmanager - NIFCloud + NIFCloud Njalla Nodion NS1 - Octenium + Octenium Open Telekom Cloud Oracle Cloud OVH - plesk.com + plesk.com Porkbun PowerDNS Rackspace - Rain Yun/雨云 + Rain Yun/雨云 RcodeZero reg.ru Regfish - RFC2136 + RFC2136 RimuHosting RU CENTER Sakura Cloud - Scaleway + Scaleway Selectel Selectel v2 SelfHost.(de|eu) - Servercow + Servercow Shellrent Simply.com Sonic - Spaceship + Spaceship Stackpath Syse Technitium - Tencent Cloud DNS + Tencent Cloud DNS Tencent EdgeOne Timeweb Cloud TransIP - UKFast SafeDNS + UKFast SafeDNS Ultradns United-Domains Variomedia - VegaDNS + VegaDNS Vercel Versio.[nl|eu|uk] VinylDNS - Virtualname + Virtualname VK Cloud Volcano Engine/火山引擎 Vscale - Vultr + Vultr webnames.ca webnames.ru Websupport - WEDOS + WEDOS West.cn/西部数码 Yandex 360 Yandex Cloud - Yandex PDD + Yandex PDD Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 89b3ce7e1..e62c337ff 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -98,6 +98,7 @@ func allDNSCodes() string { "internetbs", "inwx", "ionos", + "ionoscloud", "ipv64", "iwantmyname", "joker", @@ -2043,6 +2044,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/ionos`) + case "ionoscloud": + // generated from: providers/dns/ionoscloud/ionoscloud.toml + ew.writeln(`Configuration for Ionos Cloud.`) + ew.writeln(`Code: 'ionoscloud'`) + ew.writeln(`Since: 'v4.30.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "IONOSCLOUD_API_TOKEN": API token`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "IONOSCLOUD_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "IONOSCLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "IONOSCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "IONOSCLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/ionoscloud`) + case "ipv64": // generated from: providers/dns/ipv64/ipv64.toml ew.writeln(`Configuration for IPv64.`) diff --git a/docs/content/dns/zz_gen_ionoscloud.md b/docs/content/dns/zz_gen_ionoscloud.md new file mode 100644 index 000000000..9d33a95e5 --- /dev/null +++ b/docs/content/dns/zz_gen_ionoscloud.md @@ -0,0 +1,67 @@ +--- +title: "Ionos Cloud" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: ionoscloud +dnsprovider: + since: "v4.30.0" + code: "ionoscloud" + url: "https://cloud.ionos.de/network/cloud-dns" +--- + + + + + + +Configuration for [Ionos Cloud](https://cloud.ionos.de/network/cloud-dns). + + + + +- Code: `ionoscloud` +- Since: v4.30.0 + + +Here is an example bash command using the Ionos Cloud provider: + +```bash +IONOSCLOUD_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns ionoscloud -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `IONOSCLOUD_API_TOKEN` | API token | + +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 | +|--------------------------------|-------------| +| `IONOSCLOUD_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `IONOSCLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `IONOSCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `IONOSCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://api.ionos.com/docs/dns/v1/) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 7962f0d73..fdb13f57a 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/ionoscloud/internal/client.go b/providers/dns/ionoscloud/internal/client.go new file mode 100644 index 000000000..5b7d3a0fc --- /dev/null +++ b/providers/dns/ionoscloud/internal/client.go @@ -0,0 +1,172 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" +) + +const defaultBaseURL = "https://dns.de-fra.ionos.com" + +const authorizationHeader = "Authorization" + +// Client the Ionos Cloud API client. +type Client struct { + apiKey string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(apiKey string) (*Client, error) { + if apiKey == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + apiKey: apiKey, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +// RetrieveZones returns a list of the DNS zones. +// https://api.ionos.com/docs/dns/v1/#tag/Zones/operation/zonesGet +func (c *Client) RetrieveZones(ctx context.Context, zoneName string) ([]Zone, error) { + endpoint := c.BaseURL.JoinPath("zones") + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + query := req.URL.Query() + query.Add("filter.zoneName", zoneName) + req.URL.RawQuery = query.Encode() + + result := ZonesResponse{} + + if err := c.do(req, &result); err != nil { + return nil, err + } + + return result.Items, nil +} + +// CreateRecord creates a new record for the DNS zone. +// https://api.ionos.com/docs/dns/v1/#tag/Records/operation/zonesRecordsPost +func (c *Client) CreateRecord(ctx context.Context, zoneID string, record RecordProperties) (*RecordResponse, error) { + endpoint := c.BaseURL.JoinPath("zones", zoneID, "records") + + payload := map[string]RecordProperties{ + "properties": record, + } + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, payload) + if err != nil { + return nil, err + } + + result := &RecordResponse{} + + if err := c.do(req, result); err != nil { + return nil, err + } + + return result, nil +} + +// DeleteRecord deletes a specified record from the DNS zone. +// https://api.ionos.com/docs/dns/v1/#tag/Records/operation/zonesRecordsDelete +func (c *Client) DeleteRecord(ctx context.Context, zoneID, recordID string) error { + endpoint := c.BaseURL.JoinPath("zones", zoneID, "records", recordID) + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) do(req *http.Request, result any) error { + useragent.SetHeader(req.Header) + + req.Header.Set(authorizationHeader, "Bearer "+c.apiKey) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/ionoscloud/internal/client_test.go b/providers/dns/ionoscloud/internal/client_test.go new file mode 100644 index 000000000..dc478cc64 --- /dev/null +++ b/providers/dns/ionoscloud/internal/client_test.go @@ -0,0 +1,134 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + "time" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("secret") + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + WithAuthorization("Bearer secret"), + ) +} + +func TestClient_RetrieveZones(t *testing.T) { + client := mockBuilder(). + Route("GET /zones", + servermock.ResponseFromFixture("zones.json"), + servermock.CheckQueryParameter().Strict(). + With("filter.zoneName", "example.com")). + Build(t) + + zones, err := client.RetrieveZones(t.Context(), "example.com") + require.NoError(t, err) + + expected := []Zone{{ + ID: "e74d0d15-f567-4b7b-9069-26ee1f93bae3", + Type: "zone", + Metadata: ZoneMetadata{ + CreatedDate: time.Date(2022, time.August, 21, 15, 52, 53, 0, time.UTC), + CreatedBy: "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", + CreatedByUserID: "87f9a82e-b28d-49ed-9d04-fba2c0459cd3", + LastModifiedDate: time.Date(2022, time.August, 21, 15, 52, 53, 0, time.UTC), + LastModifiedBy: "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", + LastModifiedByUserID: "63cef532-26fe-4a64-a4e0-de7c8a506c90", + ResourceURN: "ionos::::", + State: "PROVISIONING", + Nameservers: []string{"ns-ic.ui-dns.com", "ns-ic.ui-dns.de", "ns-ic.ui-dns.org", "ns-ic.ui-dns.biz"}, + }, + Properties: ZoneProperties{ + ZoneName: "example.com", + Description: "The hosted zone is used for example.com", + Enabled: true, + }, + }} + + assert.Equal(t, expected, zones) +} + +func TestClient_RetrieveZones_error(t *testing.T) { + client := mockBuilder(). + Route("GET /zones", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized)). + Build(t) + + _, err := client.RetrieveZones(t.Context(), "example.com") + require.EqualError(t, err, "401: paas-auth-1: Unauthorized, wrong or no api key provided to process this request") +} + +func TestClient_CreateRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /zones/abc/records", + servermock.ResponseFromFixture("create_record.json"), + servermock.CheckRequestJSONBodyFromFixture("create_record-request.json")). + Build(t) + + record := RecordProperties{ + Name: "_acme-challenge", + Type: "TXT", + Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 120, + } + + result, err := client.CreateRecord(t.Context(), "abc", record) + require.NoError(t, err) + + expected := &RecordResponse{ + ID: "90d81ac0-3a30-44d4-95a5-12959effa6ee", + Type: "record", + Metadata: RecordMetadata{ + CreatedDate: time.Date(2022, time.August, 21, 15, 52, 53, 0, time.UTC), + CreatedBy: "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", + CreatedByUserID: "87f9a82e-b28d-49ed-9d04-fba2c0459cd3", + LastModifiedDate: time.Date(2022, time.August, 21, 15, 52, 53, 0, time.UTC), + LastModifiedBy: "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", + LastModifiedByUserID: "63cef532-26fe-4a64-a4e0-de7c8a506c90", + ResourceURN: "ionos::::", + State: "PROVISIONING", + Fqdn: "app.example.com", + ZoneID: "a363f30c-4c0c-4552-9a07-298d87f219bf", + }, + Properties: RecordProperties{ + Name: "app", + Type: "A", + Content: "1.2.3.4", + TTL: 3600, + Priority: 3600, + Enabled: true, + }, + } + + assert.Equal(t, expected, result) +} + +func TestClient_DeleteRecord(t *testing.T) { + client := mockBuilder(). + Route("DELETE /zones/abc/records/def", + servermock.Noop(). + WithStatusCode(http.StatusAccepted)). + Build(t) + + err := client.DeleteRecord(t.Context(), "abc", "def") + require.NoError(t, err) +} diff --git a/providers/dns/ionoscloud/internal/fixtures/create_record-request.json b/providers/dns/ionoscloud/internal/fixtures/create_record-request.json new file mode 100644 index 000000000..d4f52bba8 --- /dev/null +++ b/providers/dns/ionoscloud/internal/fixtures/create_record-request.json @@ -0,0 +1,8 @@ +{ + "properties": { + "name": "_acme-challenge", + "type": "TXT", + "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "ttl": 120 + } +} diff --git a/providers/dns/ionoscloud/internal/fixtures/create_record.json b/providers/dns/ionoscloud/internal/fixtures/create_record.json new file mode 100644 index 000000000..d3094c3b2 --- /dev/null +++ b/providers/dns/ionoscloud/internal/fixtures/create_record.json @@ -0,0 +1,25 @@ +{ + "id": "90d81ac0-3a30-44d4-95a5-12959effa6ee", + "type": "record", + "href": "", + "metadata": { + "createdDate": "2022-08-21T15:52:53Z", + "createdBy": "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", + "createdByUserId": "87f9a82e-b28d-49ed-9d04-fba2c0459cd3", + "lastModifiedDate": "2022-08-21T15:52:53Z", + "lastModifiedBy": "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", + "lastModifiedByUserId": "63cef532-26fe-4a64-a4e0-de7c8a506c90", + "resourceURN": "ionos::::", + "state": "PROVISIONING", + "fqdn": "app.example.com", + "zoneId": "a363f30c-4c0c-4552-9a07-298d87f219bf" + }, + "properties": { + "name": "app", + "type": "A", + "content": "1.2.3.4", + "ttl": 3600, + "priority": 3600, + "enabled": true + } +} diff --git a/providers/dns/ionoscloud/internal/fixtures/error.json b/providers/dns/ionoscloud/internal/fixtures/error.json new file mode 100644 index 000000000..bed0e5efb --- /dev/null +++ b/providers/dns/ionoscloud/internal/fixtures/error.json @@ -0,0 +1,9 @@ +{ + "httpStatus": 401, + "messages": [ + { + "errorCode": "paas-auth-1", + "message": "Unauthorized, wrong or no api key provided to process this request" + } + ] +} diff --git a/providers/dns/ionoscloud/internal/fixtures/zones.json b/providers/dns/ionoscloud/internal/fixtures/zones.json new file mode 100644 index 000000000..c9c2c62f9 --- /dev/null +++ b/providers/dns/ionoscloud/internal/fixtures/zones.json @@ -0,0 +1,40 @@ +{ + "id": "e74d0d15-f567-4b7b-9069-26ee1f93bae3", + "type": "collection", + "href": "", + "offset": 0, + "limit": 1000, + "_links": { + "prev": "http://PREVIOUS-PAGE-URI", + "self": "http://THIS-PAGE-URI", + "next": "http://NEXT-PAGE-URI" + }, + "items": [ + { + "id": "e74d0d15-f567-4b7b-9069-26ee1f93bae3", + "type": "zone", + "href": "", + "metadata": { + "createdDate": "2022-08-21T15:52:53Z", + "createdBy": "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", + "createdByUserId": "87f9a82e-b28d-49ed-9d04-fba2c0459cd3", + "lastModifiedDate": "2022-08-21T15:52:53Z", + "lastModifiedBy": "ionos:iam:cloud:31960002:users/87f9a82e-b28d-49ed-9d04-fba2c0459cd3", + "lastModifiedByUserId": "63cef532-26fe-4a64-a4e0-de7c8a506c90", + "resourceURN": "ionos::::", + "state": "PROVISIONING", + "nameservers": [ + "ns-ic.ui-dns.com", + "ns-ic.ui-dns.de", + "ns-ic.ui-dns.org", + "ns-ic.ui-dns.biz" + ] + }, + "properties": { + "zoneName": "example.com", + "description": "The hosted zone is used for example.com", + "enabled": true + } + } + ] +} diff --git a/providers/dns/ionoscloud/internal/types.go b/providers/dns/ionoscloud/internal/types.go new file mode 100644 index 000000000..49348f4d1 --- /dev/null +++ b/providers/dns/ionoscloud/internal/types.go @@ -0,0 +1,97 @@ +package internal + +import ( + "fmt" + "strconv" + "strings" + "time" +) + +type APIError struct { + HTTPStatus int `json:"httpStatus"` + Messages []ErrorMessage `json:"messages"` +} + +func (a *APIError) Error() string { + var msg strings.Builder + + msg.WriteString(strconv.Itoa(a.HTTPStatus)) + + for _, m := range a.Messages { + msg.WriteString(": ") + msg.WriteString(m.String()) + } + + return msg.String() +} + +type ErrorMessage struct { + ErrorCode string `json:"errorCode"` + Message string `json:"message"` +} + +func (e ErrorMessage) String() string { + return fmt.Sprintf("%s: %s", e.ErrorCode, e.Message) +} + +type ZonesResponse struct { + ID string `json:"id"` + Type string `json:"type"` + Offset int `json:"offset"` + Limit int `json:"limit"` + Items []Zone `json:"items"` +} + +type Zone struct { + ID string `json:"id"` + Type string `json:"type"` + Metadata ZoneMetadata `json:"metadata"` + Properties ZoneProperties `json:"properties"` +} + +type ZoneMetadata struct { + CreatedDate time.Time `json:"createdDate"` + CreatedBy string `json:"createdBy"` + CreatedByUserID string `json:"createdByUserId"` + LastModifiedDate time.Time `json:"lastModifiedDate"` + LastModifiedBy string `json:"lastModifiedBy"` + LastModifiedByUserID string `json:"lastModifiedByUserId"` + ResourceURN string `json:"resourceURN"` + State string `json:"state"` + Nameservers []string `json:"nameservers"` +} + +type ZoneProperties struct { + ZoneName string `json:"zoneName"` + Description string `json:"description"` + Enabled bool `json:"enabled"` +} + +type RecordResponse struct { + ID string `json:"id"` + Type string `json:"type"` + Metadata RecordMetadata `json:"metadata"` + Properties RecordProperties `json:"properties"` +} + +type RecordMetadata struct { + CreatedDate time.Time `json:"createdDate"` + CreatedBy string `json:"createdBy"` + CreatedByUserID string `json:"createdByUserId"` + LastModifiedDate time.Time `json:"lastModifiedDate"` + LastModifiedBy string `json:"lastModifiedBy"` + LastModifiedByUserID string `json:"lastModifiedByUserId"` + ResourceURN string `json:"resourceURN"` + State string `json:"state"` + Fqdn string `json:"fqdn"` + ZoneID string `json:"zoneId"` +} + +type RecordProperties struct { + Name string `json:"name"` + Type string `json:"type,omitempty"` + Content string `json:"content,omitempty"` + TTL int `json:"ttl,omitempty"` + Priority int `json:"priority,omitempty"` + Enabled bool `json:"enabled,omitempty"` +} diff --git a/providers/dns/ionoscloud/ionoscloud.go b/providers/dns/ionoscloud/ionoscloud.go new file mode 100644 index 000000000..0c33fba9f --- /dev/null +++ b/providers/dns/ionoscloud/ionoscloud.go @@ -0,0 +1,184 @@ +// Package ionoscloud implements a DNS provider for solving the DNS-01 challenge using Ionos Cloud. +package ionoscloud + +import ( + "context" + "errors" + "fmt" + "net/http" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/ionoscloud/internal" +) + +// Environment variables names. +const ( + envNamespace = "IONOSCLOUD_" + + EnvAPIToken = envNamespace + "API_TOKEN" + + 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 { + APIToken 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, 120*time.Second), + 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 *internal.Client + + zoneIDs map[string]string + recordIDs map[string]string + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for Ionos Cloud. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIToken) + if err != nil { + return nil, fmt.Errorf("ionoscloud: %w", err) + } + + config := NewDefaultConfig() + config.APIToken = values[EnvAPIToken] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Ionos Cloud. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("ionoscloud: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.APIToken) + if err != nil { + return nil, fmt.Errorf("ionoscloud: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + zoneIDs: make(map[string]string), + recordIDs: make(map[string]string), + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("ionoscloud: could not find zone for domain %q: %w", domain, err) + } + + zones, err := d.client.RetrieveZones(ctx, dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("ionoscloud: retrieve zones: %w", err) + } + + if len(zones) != 1 { + return fmt.Errorf("ionoscloud: zone ID not found for domain %q", domain) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("ionoscloud: %w", err) + } + + zoneID := zones[0].ID + + request := internal.RecordProperties{ + Name: subDomain, + Type: "TXT", + Content: info.Value, + TTL: d.config.TTL, + } + + record, err := d.client.CreateRecord(ctx, zoneID, request) + if err != nil { + return fmt.Errorf("ionoscloud: create record: %w", err) + } + + d.recordIDsMu.Lock() + d.zoneIDs[token] = zoneID + d.recordIDs[token] = record.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) + + d.recordIDsMu.Lock() + zoneID, ok := d.zoneIDs[token] + d.recordIDsMu.Unlock() + + if !ok { + return fmt.Errorf("ionoscloud: unknown zone ID for '%s' '%s'", info.EffectiveFQDN, token) + } + + d.recordIDsMu.Lock() + recordID, ok := d.recordIDs[token] + d.recordIDsMu.Unlock() + + if !ok { + return fmt.Errorf("ionoscloud: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) + } + + err := d.client.DeleteRecord(context.Background(), zoneID, recordID) + if err != nil { + return fmt.Errorf("ionoscloud: delete record: %w", err) + } + + d.recordIDsMu.Lock() + delete(d.zoneIDs, token) + 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/ionoscloud/ionoscloud.toml b/providers/dns/ionoscloud/ionoscloud.toml new file mode 100644 index 000000000..a8fedce6c --- /dev/null +++ b/providers/dns/ionoscloud/ionoscloud.toml @@ -0,0 +1,22 @@ +Name = "Ionos Cloud" +Description = '''''' +URL = "https://cloud.ionos.de/network/cloud-dns" +Code = "ionoscloud" +Since = "v4.30.0" + +Example = ''' +IONOSCLOUD_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns ionoscloud -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + IONOSCLOUD_API_TOKEN = "API token" + [Configuration.Additional] + IONOSCLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + IONOSCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + IONOSCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + IONOSCLOUD_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://api.ionos.com/docs/dns/v1/" diff --git a/providers/dns/ionoscloud/ionoscloud_test.go b/providers/dns/ionoscloud/ionoscloud_test.go new file mode 100644 index 000000000..8282e08fc --- /dev/null +++ b/providers/dns/ionoscloud/ionoscloud_test.go @@ -0,0 +1,173 @@ +package ionoscloud + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvAPIToken).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvAPIToken: "secret", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "ionoscloud: some credentials information are missing: IONOSCLOUD_API_TOKEN", + }, + } + + 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 + apiToken string + expected string + }{ + { + desc: "success", + apiToken: "secret", + }, + { + desc: "missing credentials", + expected: "ionoscloud: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIToken = test.apiToken + + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.APIToken = "secret" + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + WithAuthorization("Bearer secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("GET /zones", + servermock.ResponseFromInternal("zones.json"), + servermock.CheckQueryParameter().Strict(). + With("filter.zoneName", "example.com")). + Route("POST /zones/e74d0d15-f567-4b7b-9069-26ee1f93bae3/records", + servermock.ResponseFromInternal("create_record.json"), + servermock.CheckRequestJSONBodyFromInternal("create_record-request.json")). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("DELETE /zones/e74d0d15-f567-4b7b-9069-26ee1f93bae3/records/90d81ac0-3a30-44d4-95a5-12959effa6ee", + servermock.Noop(). + WithStatusCode(http.StatusAccepted)). + Build(t) + + token := "abc" + + provider.zoneIDs[token] = "e74d0d15-f567-4b7b-9069-26ee1f93bae3" + provider.recordIDs[token] = "90d81ac0-3a30-44d4-95a5-12959effa6ee" + + err := provider.CleanUp("example.com", token, "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 3b7b8c5fc..1270e0f9d 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -92,6 +92,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internetbs" "github.com/go-acme/lego/v4/providers/dns/inwx" "github.com/go-acme/lego/v4/providers/dns/ionos" + "github.com/go-acme/lego/v4/providers/dns/ionoscloud" "github.com/go-acme/lego/v4/providers/dns/ipv64" "github.com/go-acme/lego/v4/providers/dns/iwantmyname" "github.com/go-acme/lego/v4/providers/dns/joker" @@ -358,6 +359,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return inwx.NewDNSProvider() case "ionos": return ionos.NewDNSProvider() + case "ionoscloud": + return ionoscloud.NewDNSProvider() case "ipv64": return ipv64.NewDNSProvider() case "iwantmyname": From 5b30df22b57dc9ae15fab96c8be5ef1d73b978e3 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 16 Dec 2025 17:20:51 +0100 Subject: [PATCH 242/298] chore: update dependencies (#2753) --- go.mod | 115 ++++++++--------- go.sum | 244 +++++++++++++++++------------------ providers/dns/vultr/vultr.go | 2 +- 3 files changed, 178 insertions(+), 183 deletions(-) diff --git a/go.mod b/go.mod index cd019fac3..9def9b0eb 100644 --- a/go.mod +++ b/go.mod @@ -16,22 +16,22 @@ require ( github.com/BurntSushi/toml v1.5.0 github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 - github.com/alibabacloud-go/tea v1.3.13 - github.com/aliyun/credentials-go v1.4.7 - github.com/aws/aws-sdk-go-v2 v1.40.0 - github.com/aws/aws-sdk-go-v2/config v1.32.2 - github.com/aws/aws-sdk-go-v2/credentials v1.19.2 - github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.8 - github.com/aws/aws-sdk-go-v2/service/route53 v1.61.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1 - github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 + github.com/alibabacloud-go/tea v1.3.14 + github.com/aliyun/credentials-go v1.4.10 + github.com/aws/aws-sdk-go-v2 v1.41.0 + github.com/aws/aws-sdk-go-v2/config v1.32.5 + github.com/aws/aws-sdk-go-v2/credentials v1.19.5 + github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.10 + github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0 + github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 github.com/aziontech/azionapi-go-sdk v0.144.0 - github.com/baidubce/bce-sdk-go v0.9.252 + github.com/baidubce/bce-sdk-go v0.9.254 github.com/cenkalti/backoff/v5 v5.0.3 github.com/dnsimple/dnsimple-go/v4 v4.0.0 github.com/exoscale/egoscale/v3 v3.1.31 github.com/go-acme/alidns-20150109/v4 v4.7.0 - github.com/go-acme/esa-20240910/v2 v2.40.1 + github.com/go-acme/esa-20240910/v2 v2.40.3 github.com/go-acme/tencentclouddnspod v1.1.25 github.com/go-acme/tencentedgdeone v1.1.48 github.com/go-jose/go-jose/v4 v4.1.3 @@ -42,16 +42,16 @@ require ( github.com/gophercloud/gophercloud v1.14.1 github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/hashicorp/go-retryablehttp v0.7.8 - github.com/hashicorp/go-version v1.7.0 - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.178 + github.com/hashicorp/go-version v1.8.0 + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.180 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 github.com/labbsr0x/bindman-dns-webhook v1.0.2 github.com/ldez/grignotin v0.10.1 - github.com/linode/linodego v1.61.0 + github.com/linode/linodego v1.62.0 github.com/liquidweb/liquidweb-go v1.6.4 github.com/mattn/go-isatty v0.0.20 - github.com/miekg/dns v1.1.68 + github.com/miekg/dns v1.1.69 github.com/mimuret/golang-iij-dpf v0.9.1 github.com/namedotcom/go/v4 v4.0.2 github.com/nrdcg/auroradns v1.1.0 @@ -64,8 +64,8 @@ require ( github.com/nrdcg/mailinabox v0.3.0 github.com/nrdcg/namesilo v0.5.0 github.com/nrdcg/nodion v0.1.0 - github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.0 - github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.0 + github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.1 + github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.1 github.com/nrdcg/porkbun v0.4.0 github.com/nrdcg/vegadns v0.3.0 github.com/nzdjb/go-metaname v1.0.0 @@ -74,29 +74,29 @@ require ( 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.3.3 - github.com/sacloud/iaas-api-go v1.22.0 + github.com/sacloud/iaas-api-go v1.23.1 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35 github.com/selectel/domains-go v1.1.0 github.com/selectel/go-selvpcclient/v4 v4.1.0 github.com/softlayer/softlayer-go v1.2.1 github.com/stretchr/testify v1.11.1 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.3 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.12 github.com/transip/gotransip/v6 v6.26.1 github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 github.com/urfave/cli/v2 v2.27.7 - github.com/vinyldns/go-vinyldns v0.9.16 - github.com/volcengine/volc-sdk-golang v1.0.229 - github.com/vultr/govultr/v3 v3.25.0 - github.com/yandex-cloud/go-genproto v0.38.0 - github.com/yandex-cloud/go-sdk/services/dns v0.0.20 - github.com/yandex-cloud/go-sdk/v2 v2.28.0 - golang.org/x/crypto v0.45.0 - golang.org/x/net v0.47.0 - golang.org/x/oauth2 v0.33.0 - golang.org/x/text v0.31.0 + github.com/vinyldns/go-vinyldns v0.9.17 + github.com/volcengine/volc-sdk-golang v1.0.230 + github.com/vultr/govultr/v3 v3.26.0 + github.com/yandex-cloud/go-genproto v0.41.0 + github.com/yandex-cloud/go-sdk/services/dns v0.0.23 + github.com/yandex-cloud/go-sdk/v2 v2.33.0 + golang.org/x/crypto v0.46.0 + golang.org/x/net v0.48.0 + golang.org/x/oauth2 v0.34.0 + golang.org/x/text v0.32.0 golang.org/x/time v0.14.0 - google.golang.org/api v0.256.0 - gopkg.in/ns1/ns1-go.v2 v2.15.2 + google.golang.org/api v0.257.0 + gopkg.in/ns1/ns1-go.v2 v2.16.0 gopkg.in/yaml.v2 v2.4.0 software.sslmate.com/src/go-pkcs12 v0.6.0 ) @@ -117,20 +117,20 @@ require ( github.com/alibabacloud-go/debug v1.0.1 // indirect github.com/alibabacloud-go/openapi-util v0.1.1 // indirect github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.14 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 // indirect - github.com/aws/smithy-go v1.23.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect + github.com/aws/smithy-go v1.24.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -151,7 +151,7 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.23.0 // indirect - github.com/go-resty/resty/v2 v2.16.5 // indirect + github.com/go-resty/resty/v2 v2.17.0 // indirect github.com/goccy/go-yaml v1.9.8 // indirect github.com/gofrs/flock v0.13.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect @@ -183,12 +183,11 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sacloud/go-http v0.1.9 // indirect - github.com/sacloud/packages-go v0.0.11 // indirect + github.com/sacloud/packages-go v0.0.12 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect github.com/sony/gobreaker v1.0.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -202,23 +201,23 @@ require ( github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.3.1 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect - golang.org/x/mod v0.29.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/tools v0.38.0 // indirect + golang.org/x/mod v0.30.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/tools v0.39.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect - google.golang.org/grpc v1.76.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect + google.golang.org/grpc v1.77.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index af9e95e02..05aecdb9a 100644 --- a/go.sum +++ b/go.sum @@ -143,8 +143,9 @@ github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/Ke github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= -github.com/alibabacloud-go/tea v1.3.13 h1:WhGy6LIXaMbBM6VBYcsDCz6K/TPsT1Ri2hPmmZffZ94= github.com/alibabacloud-go/tea v1.3.13/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= +github.com/alibabacloud-go/tea v1.3.14 h1:/Uzj5ZCFPpbPR+Bs7jfzsyXkYIVsi5TOIuQNOWwc/9c= +github.com/alibabacloud-go/tea v1.3.14/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= @@ -154,8 +155,8 @@ github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6q github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= -github.com/aliyun/credentials-go v1.4.7 h1:T17dLqEtPUFvjDRRb5giVvLh6dFT8IcNFJJb7MeyCxw= -github.com/aliyun/credentials-go v1.4.7/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= +github.com/aliyun/credentials-go v1.4.10 h1:4PtFGTW6eMpKd8YUNL6yVh52c/3PZdEOklELEbn2ui8= +github.com/aliyun/credentials-go v1.4.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -168,54 +169,54 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc= -github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 h1:DHctwEM8P8iTXFxC/QK0MRjwEpWQeM9yzidCRjldUz0= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3/go.mod h1:xdCzcZEtnSTKVDOmUZs4l/j3pSV6rpo1WXl5ugNsL8Y= -github.com/aws/aws-sdk-go-v2/config v1.32.2 h1:4liUsdEpUUPZs5WVapsJLx5NPmQhQdez7nYFcovrytk= -github.com/aws/aws-sdk-go-v2/config v1.32.2/go.mod h1:l0hs06IFz1eCT+jTacU/qZtC33nvcnLADAPL/XyrkZI= -github.com/aws/aws-sdk-go-v2/credentials v1.19.2 h1:qZry8VUyTK4VIo5aEdUcBjPZHL2v4FyQ3QEOaWcFLu4= -github.com/aws/aws-sdk-go-v2/credentials v1.19.2/go.mod h1:YUqm5a1/kBnoK+/NY5WEiMocZihKSo15/tJdmdXnM5g= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14/go.mod h1:VymhrMJUWs69D8u0/lZ7jSB6WgaG/NqHi3gX0aYf6U0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs0ESzr1XCOU2KXJCWcq3E2vfjY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM= +github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4= +github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4= +github.com/aws/aws-sdk-go-v2/config v1.32.5 h1:pz3duhAfUgnxbtVhIK39PGF/AHYyrzGEyRD9Og0QrE8= +github.com/aws/aws-sdk-go-v2/config v1.32.5/go.mod h1:xmDjzSUs/d0BB7ClzYPAZMmgQdrodNjPPhd6bGASwoE= +github.com/aws/aws-sdk-go-v2/credentials v1.19.5 h1:xMo63RlqP3ZZydpJDMBsH9uJ10hgHYfQFIk1cHDXrR4= +github.com/aws/aws-sdk-go-v2/credentials v1.19.5/go.mod h1:hhbH6oRcou+LpXfA/0vPElh/e0M3aFeOblE1sssAAEk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.14 h1:ITi7qiDSv/mSGDSWNpZ4k4Ve0DQR6Ug2SJQ8zEHoDXg= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.14/go.mod h1:k1xtME53H1b6YpZt74YmwlONMWf4ecM+lut1WQLAF/U= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 h1:CjMzUs78RDDv4ROu3JnJn/Ig1r6ZD7/T2DXLLRpejic= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16/go.mod h1:uVW4OLBqbJXSHJYA9svT9BluSvvwbzLQ2Crf6UPzR3c= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5 h1:Hjkh7kE6D81PgrHlE/m9gx+4TyyeLHuY8xJs7yXN5C4= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.5/go.mod h1:nPRXgyCfAurhyaTMoBMwRBYBhaHI4lNPAnJmjM0Tslc= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 h1:FIouAnCE46kyYqyhs0XEBDFFSREtdnr8HQuLPQPLCrY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14 h1:FzQE21lNtUor0Fb7QNgnEyiRCBlolLTX/Z1j65S7teM= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.14/go.mod h1:s1ydyWG9pm3ZwmmYN21HKyG9WzAZhYVW85wMHs5FV6w= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.8 h1:jhwva7OKpYXrTQmCG4L7lF2FvB2irs1oRyGAwmQ4lmA= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.8/go.mod h1:x+omzRoqYYFX+H8/va+Gt2Yg4xGaHZMRowr77Y/UGIA= -github.com/aws/aws-sdk-go-v2/service/route53 v1.61.0 h1:W3+0Cbc9awFBr9Yt7nFUkvB4N4e7vVIGtKD1qDttXn4= -github.com/aws/aws-sdk-go-v2/service/route53 v1.61.0/go.mod h1:Wa3q5R2uwIfIL3HZH+vG1/P9y7CjjfzTgcz5IWXlsZs= -github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1 h1:OgQy/+0+Kc3khtqiEOk23xQAglXi3Tj0y5doOxbi5tg= -github.com/aws/aws-sdk-go-v2/service/s3 v1.92.1/go.mod h1:wYNqY3L02Z3IgRYxOBPH9I1zD9Cjh9hI5QOy/eOjQvw= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 h1:MxMBdKTYBjPQChlJhi4qlEueqB1p1KcbTEa7tD5aqPs= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.2/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 h1:ksUT5KtgpZd3SAiFJNJ0AFEJVva3gjBmN7eXUZjzUwQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.5/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 h1:GtsxyiF3Nd3JahRBJbxLCCdYW9ltGQYrFWg8XdkGDd8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 h1:a5UTtD4mHBU3t0o6aHQZFJTNKVfxFWfPX7J0Lr7G+uY= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.2/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 h1:DIBqIrJ7hv+e4CmIk2z3pyKT+3B6qVMgRsawHiR3qso= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7/go.mod h1:vLm00xmBke75UmpNvOcZQ/Q30ZFjbczeLFqGx5urmGo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.10 h1:MQuZZ6Tq1qQabPlkVxrCMdyVl70Ogl4AERZKo+y9Wzo= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.10/go.mod h1:U5C3JME1ibKESmpzBAqlRpTYZfVbTqrb5ICJm+sVVd8= +github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0 h1:80pDB3Tpmb2RCSZORrK9/3iQxsd+w6vSzVqpT1FGiwE= +github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0/go.mod h1:6EZUGGNLPLh5Unt30uEoA+KQcByERfXIkax9qrc80nA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0 h1:SWTxh/EcUCDVqi/0s26V6pVUq0BBG7kx0tDTmF/hCgA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 h1:eYnlt6QxnFINKzwxP5/Ucs1vkG7VT3Iezmvfgc2waUw= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.7/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM= -github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= +github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/aziontech/azionapi-go-sdk v0.144.0 h1:T+/w18o+FCiZsk3Z0ACBVVe7c/5EGLG15S3P8JfuPfo= github.com/aziontech/azionapi-go-sdk v0.144.0/go.mod h1:OKxP/R0iVXnJJakYwMhh2BGAXnud8Ruy55Ak9ANuWoU= -github.com/baidubce/bce-sdk-go v0.9.252 h1:TONS/utgfEkDjvHllVZFBrTsjsNhk51rhWuj3ppcL4s= -github.com/baidubce/bce-sdk-go v0.9.252/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/baidubce/bce-sdk-go v0.9.254 h1:A7GtBOt7z2lnV7fqlZPZefhcBFg7z6iliUAhEOiIhoE= +github.com/baidubce/bce-sdk-go v0.9.254/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -314,8 +315,8 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-acme/alidns-20150109/v4 v4.7.0 h1:PqJ/wR0JTpL4v0Owu1uM7bPQ1Yww0eQLAuuSdLjjQaQ= github.com/go-acme/alidns-20150109/v4 v4.7.0/go.mod h1:btQvB6xZoN6ykKB74cPhiR+uvhrEE2AFVXm6RDmCHm0= -github.com/go-acme/esa-20240910/v2 v2.40.1 h1:pog3UlF5d+3LPoo1L8u8PqB189recIXX8T7pGoEz18A= -github.com/go-acme/esa-20240910/v2 v2.40.1/go.mod h1:ZYdN9EN9ikn26SNapxCVjZ65pHT/1qm4fzuJ7QGVX6g= +github.com/go-acme/esa-20240910/v2 v2.40.3 h1:xXOMRex148wKEHbv7Izn73/HdAxSmz5GOaF4HdnqN+M= +github.com/go-acme/esa-20240910/v2 v2.40.3/go.mod h1:ZYdN9EN9ikn26SNapxCVjZ65pHT/1qm4fzuJ7QGVX6g= github.com/go-acme/tencentclouddnspod v1.1.25 h1:7H3ZKshkaHzCXfRpAHVB5nvxeDDl2XLeNZfrNHiZj/s= github.com/go-acme/tencentclouddnspod v1.1.25/go.mod h1:XXfzp0AYV7UAUsHKT6R0KAUJFhqAUXmWGF07Elpa5cE= github.com/go-acme/tencentedgdeone v1.1.48 h1:WLyLBsRVhSLFmtbEFXk0naLODSQn7X6J0Fc/qR8xVUk= @@ -356,8 +357,8 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= -github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= +github.com/go-resty/resty/v2 v2.17.0 h1:pW9DeXcaL4Rrym4EZ8v7L19zZiIlWPg5YXAcVmt+gN0= +github.com/go-resty/resty/v2 v2.17.0/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -515,8 +516,8 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= -github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= +github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -531,8 +532,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.178 h1:eNVkjzdPMgM2qih9aECiFUI8S9zgpOwXxeBPAwQqtvU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.178/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.180 h1:uia+R3K1izQRGpxTV+bS4q3/ueMW7ProAMWqM6OlqOU= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.180/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -606,8 +607,8 @@ github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufp github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/linode/linodego v1.61.0 h1:9g20NWl+/SbhDFj6X5EOZXtM2hBm1Mx8I9h8+F3l1LM= -github.com/linode/linodego v1.61.0/go.mod h1:64o30geLNwR0NeYh5HM/WrVCBXcSqkKnRK3x9xoRuJI= +github.com/linode/linodego v1.62.0 h1:eCo1sepsIPGkI66Cz9IaCylWxKDD2aSc5UYq20iBMfw= +github.com/linode/linodego v1.62.0/go.mod h1:FoIEsuZMRlXiUt6RnuGcPTek5iAO3VfE6bjMpGlcQ2U= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= @@ -643,8 +644,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= -github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= +github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc= +github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g= github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34= github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= @@ -705,10 +706,10 @@ github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE= github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.0 h1:bppmFqrJ87U4gWilemAW9oa4Qepf2JBTK/mPgaZLP2A= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.0/go.mod h1:SfDIKzNQ5AGNMMOA3LGqSPnn63F6Gc4E4bsKArqymvg= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.0 h1:IHPZs4Mo/lxyo+gYB+baheb2kGmHtNGQk2DKPDHqPjA= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.0/go.mod h1:yELd0uJLiIyv9sGIh5ZRCHEB1B2QFNURWkQIMqb3ZwE= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.1 h1:yHD01L6wN7mhGikS08izrMuEp9PRtvingePXkjRHrSg= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.1/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.1 h1:9ApYlc4bjup9WnxOFmgvh00bDqd6KMqAbAR4klKzluA= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.1/go.mod h1:iOzhDeDcQGJZVgSDKrl5p3HUWexNo3LOlicf0D9ltgk= github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= github.com/nrdcg/vegadns v0.3.0 h1:11FQMw7xVIRUWO9o5+Z/5YZhmPWlm4oxUUH3F6EVqQU= @@ -816,10 +817,10 @@ github.com/sacloud/api-client-go v0.3.3 h1:ZpSAyGpITA8UFO3Hq4qMHZLGuNI1FgxAxo4sq github.com/sacloud/api-client-go v0.3.3/go.mod h1:0p3ukcWYXRCc2AUWTl1aA+3sXLvurvvDqhRaLZRLBwo= github.com/sacloud/go-http v0.1.9 h1:Xa5PY8/pb7XWhwG9nAeXSrYXPbtfBWqawgzxD5co3VE= github.com/sacloud/go-http v0.1.9/go.mod h1:DpDG+MSyxYaBwPJ7l3aKLMzwYdTVtC5Bo63HActcgoE= -github.com/sacloud/iaas-api-go v1.22.0 h1:nvLQNuxcfxILvoxA6WcnTjU9A8yv8dPI1OSYHAPxBJk= -github.com/sacloud/iaas-api-go v1.22.0/go.mod h1:PLcolyFlby/0ExZTOdUf9xzhkEMBuVzORadXDNN21no= -github.com/sacloud/packages-go v0.0.11 h1:hrRWLmfPM9w7GBs6xb5/ue6pEMl8t1UuDKyR/KfteHo= -github.com/sacloud/packages-go v0.0.11/go.mod h1:XNF5MCTWcHo9NiqWnYctVbASSSZR3ZOmmQORIzcurJ8= +github.com/sacloud/iaas-api-go v1.23.1 h1:rjYG0vVoxWyETiwc7R8YdD7CIzs9vVNEOzu7w6dgGzc= +github.com/sacloud/iaas-api-go v1.23.1/go.mod h1:EGIHOWRB9azOv7HPCVM8WpOEl28WIV9TNRbnEVg+Q3U= +github.com/sacloud/packages-go v0.0.12 h1:MKeZNN3FQn1heqUSRBrbZw89YusZA1n4kammjMFZYvQ= +github.com/sacloud/packages-go v0.0.12/go.mod h1:XNF5MCTWcHo9NiqWnYctVbASSSZR3ZOmmQORIzcurJ8= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= @@ -841,13 +842,8 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0= github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= -github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q= -github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA= -github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= github.com/softlayer/softlayer-go v1.2.1 h1:8ucHxn5laVsVPb0/aMGnr6tOMt1I9BgEtU5mn70OGKw= github.com/softlayer/softlayer-go v1.2.1/go.mod h1:Gz9/ktcmB7Z8EJlu+QEJJpkv8lAmnhYdB9Tc6gedjmo= github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ= @@ -905,8 +901,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.25/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.3 h1:r05ohLc0LVEpiEQeOJ5QwCiKk6XM9kjTca6+UAbNR/8= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.3/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.12 h1:/ABtv4x4FSGxGW0d6Sc88iQn6Up2LalWKwt/Tj7Dtz8= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.12/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= @@ -919,12 +915,12 @@ github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419/go.mod h1:QN0/ github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= -github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= -github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= -github.com/volcengine/volc-sdk-golang v1.0.229 h1:gOkDltTS6Fta8OyfYrbeY9bqCHHyiJuGYNJpR5MR+Fo= -github.com/volcengine/volc-sdk-golang v1.0.229/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= -github.com/vultr/govultr/v3 v3.25.0 h1:rS8/Vdy8HlHArwmD4MtLY+hbbpYAbcnZueZrE6b0oUg= -github.com/vultr/govultr/v3 v3.25.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= +github.com/vinyldns/go-vinyldns v0.9.17 h1:hfPZfCaxcRBX6Gsgl42rLCeoal58/BH8kkvJShzjjdI= +github.com/vinyldns/go-vinyldns v0.9.17/go.mod h1:pwWhE9K/leGDOIduVhRGvQ3ecVMHWRfEnKYUTEU3gB4= +github.com/volcengine/volc-sdk-golang v1.0.230 h1:84/MOF0zUPtAHt3e1+MsFq5qrnQRC+e3XzTUwIOzZxw= +github.com/volcengine/volc-sdk-golang v1.0.230/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= +github.com/vultr/govultr/v3 v3.26.0 h1:pm/GM+RZo9T1JLQzrUti5HiNAIFZFEHcPFMOWGvvNIY= +github.com/vultr/govultr/v3 v3.26.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= @@ -933,12 +929,12 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yandex-cloud/go-genproto v0.38.0 h1:uB3btG7mLOnu53ehYtRARCk04+80sBpxDrSkP3qC6G8= -github.com/yandex-cloud/go-genproto v0.38.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= -github.com/yandex-cloud/go-sdk/services/dns v0.0.20 h1:xHBRa+IIYpTgMbTbmZf7aEKIqrJMcZGIF8ea4XIyLX0= -github.com/yandex-cloud/go-sdk/services/dns v0.0.20/go.mod h1:8nYQULLJbbe51qdBY7Ay5v8wtDgdH7nHCMZs4XkwzLg= -github.com/yandex-cloud/go-sdk/v2 v2.28.0 h1:KDOrN75xokZBYbgjq6Pjvo+hEpu32xFhErtomLBML5s= -github.com/yandex-cloud/go-sdk/v2 v2.28.0/go.mod h1:6vmAhqoCVYSJEb5OuhHUqIdxDy2b9uUXp1e5sqMhTmo= +github.com/yandex-cloud/go-genproto v0.41.0 h1:l0HWC7S82XgfioqOQ+d2wx7PRB5Eo71KiUb4PiWbDXQ= +github.com/yandex-cloud/go-genproto v0.41.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-sdk/services/dns v0.0.23 h1:fR4tqSRKTpzh4RczXJbU7EOXh4+kJnp+dccRpL2PLPQ= +github.com/yandex-cloud/go-sdk/services/dns v0.0.23/go.mod h1:Lgly3dyKBGrAIpIo6nrkEpQOoSQYlnik1HLKMeZcA98= +github.com/yandex-cloud/go-sdk/v2 v2.33.0 h1:wuvpirhYcHSejLDXSxLGsNoZHqkjrXevzVxw7SrrN/0= +github.com/yandex-cloud/go-sdk/v2 v2.33.0/go.mod h1:OqkwauVaBxbrrfN+JOYBIuE8GrHz1g0Z42VIkbsGvPI= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= @@ -962,22 +958,22 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -1030,8 +1026,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1075,8 +1071,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1134,16 +1130,16 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1160,8 +1156,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1247,8 +1243,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1263,8 +1259,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1283,8 +1279,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1350,8 +1346,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1380,8 +1376,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= -google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= +google.golang.org/api v0.257.0 h1:8Y0lzvHlZps53PEaw+G29SsQIkuKrumGWs9puiexNAA= +google.golang.org/api v0.257.0/go.mod h1:4eJrr+vbVaZSqs7vovFd1Jb/A6ml6iw2e6FBYf3GAO4= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1422,10 +1418,10 @@ google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxH google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= -google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1443,8 +1439,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= -google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1477,8 +1473,8 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/ns1/ns1-go.v2 v2.15.2 h1:aBVyKeEH3rBFWwX72xPPjEuRL4+Lp5P9GlAcrzu0Y5M= -gopkg.in/ns1/ns1-go.v2 v2.15.2/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= +gopkg.in/ns1/ns1-go.v2 v2.16.0 h1:mUczKFnrCystSV7yIODzVSbENoud3T7DwstmyVZfqg4= +gopkg.in/ns1/ns1-go.v2 v2.16.0/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/providers/dns/vultr/vultr.go b/providers/dns/vultr/vultr.go index 2064cee19..f97a321c1 100644 --- a/providers/dns/vultr/vultr.go +++ b/providers/dns/vultr/vultr.go @@ -107,7 +107,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("vultr: %w", err) } - req := govultr.DomainRecordReq{ + req := govultr.DomainRecordCreateReq{ Name: subDomain, Type: "TXT", Data: `"` + info.Value + `"`, From 4e6426cb2ed62ccdd6ef218792e0b8ce6010947c Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 16 Dec 2025 02:31:35 +0100 Subject: [PATCH 243/298] Prepare release v4.30.0 --- CHANGELOG.md | 24 ++++++++++++++++++- acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3b7909e5..64e62717d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,33 @@ # Changelog -lego is an independent, free, and open-source project, if you value it, consider [supporting it](https://donate.ldez.dev)! ❤️ +lego is an independent, free, open-source project, if you value it, consider [supporting it](https://donate.ldez.dev)! ❤️ Everybody thinks that the others will donate, but in the end, nobody does. So if you think that lego is worth it, please consider [donating](https://donate.ldez.dev). +## v4.30.0 + +- Release date: 2025-12-16 +- Tag: [v4.30.0](https://github.com/go-acme/lego/releases/tag/v4.30.0) + +### Added + +- **[dnsprovider]** Add DNS provider for Ionos Cloud +- **[dnsprovider]** Add DNS provider for Virtualname +- **[dnsprovider]** Add DNS Provider for Neodigit +- **[dnsprovider]** Add DNS provider for Syse.no +- **[dnsprovider]** Add DNS provider for Gravity +- **[dnsprovider]** Add DNS provider for hosting.nl + +### Changed + +- **[cli]** feat: remove email requirement + +### Fixed + +- **[dnsprovider]** autodns: use the right response structure + ## v4.29.0 - Release date: 2025-11-29 diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 1c2078b38..c6028c4b0 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.29.0" + ourUserAgent = "xenolf-acme/4.30.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index e0fbb90e2..f63ded320 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.29.0+dev-detach" +const defaultVersion = "v4.30.0+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index bbffcdbd4..04e513138 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.29.0" + ourUserAgent = "goacme-lego/4.30.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From 222cd85cbc0b61dba99f4f65caaec404f0c6682a Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 16 Dec 2025 02:32:19 +0100 Subject: [PATCH 244/298] Detach v4.30.0 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index c6028c4b0..f720f0d61 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index f63ded320..69a570785 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.30.0+dev-release" +const defaultVersion = "v4.30.0+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 04e513138..1d5d08e2b 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From 43dc1aa835a07b83a45b8c56ec1b9b8af934474b Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 16 Dec 2025 19:04:42 +0100 Subject: [PATCH 245/298] chore: fix attest-build-provenance subject-checksums path (#2755) --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3ba055979..6a0d3b703 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,6 +67,7 @@ jobs: # https://goreleaser.com/ci/actions/ - name: Run GoReleaser + id: goreleaser uses: goreleaser/goreleaser-action@v6 with: version: v2.13.0 @@ -78,7 +79,7 @@ jobs: - uses: actions/attest-build-provenance@v3 with: - subject-checksums: ./dist/lego_*_checksums.txt + subject-checksums: ./dist/lego_${{ fromJSON(steps.goreleaser.outputs.metadata).version }}_checksums.txt github-token: ${{ secrets.GH_TOKEN_REPO }} - uses: actions/attest-build-provenance@v3 with: From 5574de68cd9dc3364cb03cf9951f5f28c21881be Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 16 Dec 2025 19:33:30 +0100 Subject: [PATCH 246/298] fix: downgrade aliyun credentials to v1.4.7 (#2756) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9def9b0eb..9aaf50171 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 github.com/alibabacloud-go/tea v1.3.14 - github.com/aliyun/credentials-go v1.4.10 + github.com/aliyun/credentials-go v1.4.7 github.com/aws/aws-sdk-go-v2 v1.41.0 github.com/aws/aws-sdk-go-v2/config v1.32.5 github.com/aws/aws-sdk-go-v2/credentials v1.19.5 diff --git a/go.sum b/go.sum index 05aecdb9a..5e63fdba3 100644 --- a/go.sum +++ b/go.sum @@ -155,8 +155,8 @@ github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6q github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= -github.com/aliyun/credentials-go v1.4.10 h1:4PtFGTW6eMpKd8YUNL6yVh52c/3PZdEOklELEbn2ui8= -github.com/aliyun/credentials-go v1.4.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= +github.com/aliyun/credentials-go v1.4.7 h1:T17dLqEtPUFvjDRRb5giVvLh6dFT8IcNFJJb7MeyCxw= +github.com/aliyun/credentials-go v1.4.7/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= From 27075d562add17f782773c44dfd10992e9be9de0 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 16 Dec 2025 19:25:40 +0100 Subject: [PATCH 247/298] Prepare release v4.30.1 --- CHANGELOG.md | 9 +++++++++ acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- go.mod | 2 ++ providers/dns/internal/useragent/useragent.go | 4 ++-- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64e62717d..a9974d550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ Everybody thinks that the others will donate, but in the end, nobody does. So if you think that lego is worth it, please consider [donating](https://donate.ldez.dev). +## v4.30.1 + +- Release date: 2025-12-16 +- Tag: [v4.30.1](https://github.com/go-acme/lego/releases/tag/v4.30.1) + +Due to an error related to `aliyun/credentials-go`, some artifacts of the v4.30.0 release have not been published. + +This release contains the same things as v4.30.0. + ## v4.30.0 - Release date: 2025-12-16 diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index f720f0d61..e4ff20980 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.30.0" + ourUserAgent = "xenolf-acme/4.30.1" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 69a570785..d5963a601 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.30.0+dev-detach" +const defaultVersion = "v4.30.1+dev-release" var version = "" diff --git a/go.mod b/go.mod index 9aaf50171..dd1a5b4b7 100644 --- a/go.mod +++ b/go.mod @@ -222,3 +222,5 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +retract v4.30.0 // Problem related to misuse of sycalls by aliyun/credentials-go diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 1d5d08e2b..3db3877ef 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.30.0" + ourUserAgent = "goacme-lego/4.30.1" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From 7af0efdf722e1b1abf3e783b0be72ecf93bf308c Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Tue, 16 Dec 2025 19:28:51 +0100 Subject: [PATCH 248/298] Detach v4.30.1 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index e4ff20980..25443fa05 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index d5963a601..da867f0cd 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.30.1+dev-release" +const defaultVersion = "v4.30.1+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 3db3877ef..4f8e693c2 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From a5cc0e155518e825ec2ec017610822f06aebb767 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 18 Dec 2025 14:29:52 +0100 Subject: [PATCH 249/298] feat: improve ACME error types (#2761) --- acme/api/internal/sender/sender.go | 69 +++++++++++++----------- acme/api/internal/sender/sender_test.go | 70 +++++++++++++++++++++++++ acme/errors.go | 24 ++++++++- 3 files changed, 131 insertions(+), 32 deletions(-) diff --git a/acme/api/internal/sender/sender.go b/acme/api/internal/sender/sender.go index d5db5d410..d8859edf4 100644 --- a/acme/api/internal/sender/sender.go +++ b/acme/api/internal/sender/sender.go @@ -120,39 +120,46 @@ func (d *Doer) formatUserAgent() string { } func checkError(req *http.Request, resp *http.Response) error { - if resp.StatusCode >= http.StatusBadRequest { - body, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("%d :: %s :: %s :: %w", resp.StatusCode, req.Method, req.URL, err) - } - - var errorDetails *acme.ProblemDetails - - err = json.Unmarshal(body, &errorDetails) - if err != nil { - return fmt.Errorf("%d ::%s :: %s :: %w :: %s", resp.StatusCode, req.Method, req.URL, err, string(body)) - } - - errorDetails.Method = req.Method - errorDetails.URL = req.URL.String() - - if errorDetails.HTTPStatus == 0 { - errorDetails.HTTPStatus = resp.StatusCode - } - - // Check for errors we handle specifically - if errorDetails.HTTPStatus == http.StatusBadRequest && errorDetails.Type == acme.BadNonceErr { - return &acme.NonceError{ProblemDetails: errorDetails} - } - - if errorDetails.HTTPStatus == http.StatusConflict && errorDetails.Type == acme.AlreadyReplacedErr { - return &acme.AlreadyReplacedError{ProblemDetails: errorDetails} - } - - return errorDetails + if resp.StatusCode < http.StatusBadRequest { + return nil } - return nil + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("%d :: %s :: %s :: %w", resp.StatusCode, req.Method, req.URL, err) + } + + var errorDetails *acme.ProblemDetails + + err = json.Unmarshal(body, &errorDetails) + if err != nil { + return fmt.Errorf("%d ::%s :: %s :: %w :: %s", resp.StatusCode, req.Method, req.URL, err, string(body)) + } + + errorDetails.Method = req.Method + errorDetails.URL = req.URL.String() + + if errorDetails.HTTPStatus == 0 { + errorDetails.HTTPStatus = resp.StatusCode + } + + // Check for errors we handle specifically + switch { + case errorDetails.HTTPStatus == http.StatusBadRequest && errorDetails.Type == acme.BadNonceErr: + return &acme.NonceError{ProblemDetails: errorDetails} + + case errorDetails.HTTPStatus == http.StatusConflict && errorDetails.Type == acme.AlreadyReplacedErr: + return &acme.AlreadyReplacedError{ProblemDetails: errorDetails} + + case errorDetails.HTTPStatus == http.StatusTooManyRequests && errorDetails.Type == acme.RateLimitedErr: + return &acme.RateLimitedError{ + ProblemDetails: errorDetails, + RetryAfter: resp.Header.Get("Retry-After"), + } + + default: + return errorDetails + } } type httpsOnly struct { diff --git a/acme/api/internal/sender/sender_test.go b/acme/api/internal/sender/sender_test.go index 1f25c6d26..73701ab11 100644 --- a/acme/api/internal/sender/sender_test.go +++ b/acme/api/internal/sender/sender_test.go @@ -1,11 +1,14 @@ package sender import ( + "bytes" + "io" "net/http" "net/http/httptest" "strings" "testing" + "github.com/go-acme/lego/v4/acme" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -78,3 +81,70 @@ func TestDo_failWithHTTP(t *testing.T) { _, err := sender.Post(server.URL, strings.NewReader("data"), "text/plain", nil) require.ErrorContains(t, err, "HTTPS is required: http://") } + +func Test_checkError(t *testing.T) { + testCases := []struct { + desc string + resp *http.Response + assert func(t *testing.T, err error) + }{ + { + desc: "default", + resp: &http.Response{ + StatusCode: http.StatusNotFound, + Body: io.NopCloser(bytes.NewBufferString(`{"type":"urn:ietf:params:acme:error:example","detail":"message","status":404}`)), + }, + assert: errorAs[*acme.ProblemDetails], + }, + { + desc: "badNonce", + resp: &http.Response{ + StatusCode: http.StatusBadRequest, + Body: io.NopCloser(bytes.NewBufferString(`{"type":"urn:ietf:params:acme:error:badNonce","detail":"message","status":400}`)), + }, + assert: errorAs[*acme.NonceError], + }, + { + desc: "alreadyReplaced", + resp: &http.Response{ + StatusCode: http.StatusConflict, + Body: io.NopCloser(bytes.NewBufferString(`{"type":"urn:ietf:params:acme:error:alreadyReplaced","detail":"message","status":409}`)), + }, + assert: errorAs[*acme.AlreadyReplacedError], + }, + { + desc: "rateLimited", + resp: &http.Response{ + StatusCode: http.StatusConflict, + Header: http.Header{ + "Retry-After": []string{"1"}, + }, + Body: io.NopCloser(bytes.NewBufferString(`{"type":"urn:ietf:params:acme:error:rateLimited","detail":"message","status":429}`)), + }, + assert: errorAs[*acme.RateLimitedError], + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + req := httptest.NewRequestWithContext(t.Context(), http.MethodPost, "https://example.com", nil) + + err := checkError(req, test.resp) + require.Error(t, err) + + pb := &acme.ProblemDetails{} + assert.ErrorAs(t, err, &pb) + + test.assert(t, err) + }) + } +} + +func errorAs[T error](t *testing.T, err error) { + t.Helper() + + var zero T + assert.ErrorAs(t, err, &zero) +} diff --git a/acme/errors.go b/acme/errors.go index 161a47c38..be4721c9d 100644 --- a/acme/errors.go +++ b/acme/errors.go @@ -10,6 +10,7 @@ const ( errNS = "urn:ietf:params:acme:error:" BadNonceErr = errNS + "badNonce" AlreadyReplacedErr = errNS + "alreadyReplaced" + RateLimitedErr = errNS + "rateLimited" ) // ProblemDetails the problem details object. @@ -63,9 +64,30 @@ type NonceError struct { *ProblemDetails } +func (e *NonceError) Unwrap() error { + return e.ProblemDetails +} + // AlreadyReplacedError represents the error which is returned -// If the Server rejects the request because the identified certificate has already been marked as replaced. +// if the Server rejects the request because the identified certificate has already been marked as replaced. // - https://www.rfc-editor.org/rfc/rfc9773.html#section-5 type AlreadyReplacedError struct { *ProblemDetails } + +func (e *AlreadyReplacedError) Unwrap() error { + return e.ProblemDetails +} + +// RateLimitedError represents the error which is returned +// if the server rejects the request because the client has exceeded the rate limit. +// - https://www.rfc-editor.org/rfc/rfc8555.html#section-6.6 +type RateLimitedError struct { + *ProblemDetails + + RetryAfter string +} + +func (e *RateLimitedError) Unwrap() error { + return e.ProblemDetails +} From bb98b9a899764592a65873761a560818ad2f1597 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 19 Dec 2025 02:27:38 +0100 Subject: [PATCH 250/298] chore: update pebble to v2.9.0 (#2763) --- .github/workflows/pr.yml | 4 +-- e2e/fixtures/certs/localhost/cert.pem | 35 ++++++++++++++------------- e2e/fixtures/certs/pebble.minica.pem | 23 +++++++++--------- e2e/readme.md | 4 +-- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 626d9f6e9..151a2a6e0 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -44,10 +44,10 @@ jobs: install-only: true - name: Install Pebble - run: go install github.com/letsencrypt/pebble/v2/cmd/pebble@v2.8.0 + run: go install github.com/letsencrypt/pebble/v2/cmd/pebble@v2.9.0 - name: Install challtestsrv - run: go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@v2.8.0 + run: go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@v2.9.0 - name: Set up a Memcached server uses: niden/actions-memcached@v7 diff --git a/e2e/fixtures/certs/localhost/cert.pem b/e2e/fixtures/certs/localhost/cert.pem index 2866a2b48..d81d29e70 100644 --- a/e2e/fixtures/certs/localhost/cert.pem +++ b/e2e/fixtures/certs/localhost/cert.pem @@ -1,19 +1,20 @@ -----BEGIN CERTIFICATE----- -MIIDGzCCAgOgAwIBAgIIbEfayDFsBtwwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE -AxMVbWluaWNhIHJvb3QgY2EgMjRlMmRiMCAXDTE3MTIwNjE5NDIxMFoYDzIxMDcx -MjA2MTk0MjEwWjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQCbFMW3DXXdErvQf2lCZ0qz0DGEWadDoF0O2neM5mVa -VQ7QGW0xc5Qwvn3Tl62C0JtwLpF0pG2BICIN+DHdVaIUwkf77iBS2doH1I3waE1I -8GkV9JrYmFY+j0dA1SwBmqUZNXhLNwZGq1a91nFSI59DZNy/JciqxoPX2K++ojU2 -FPpuXe2t51NmXMsszpa+TDqF/IeskA9A/ws6UIh4Mzhghx7oay2/qqj2IIPjAmJj -i73kdUvtEry3wmlkBvtVH50+FscS9WmPC5h3lDTk5nbzSAXKuFusotuqy3XTgY5B -PiRAwkZbEY43JNfqenQPHo7mNTt29i+NVVrBsnAa5ovrAgMBAAGjYzBhMA4GA1Ud -DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T -AQH/BAIwADAiBgNVHREEGzAZgglsb2NhbGhvc3SCBnBlYmJsZYcEfwAAATANBgkq -hkiG9w0BAQsFAAOCAQEAYIkXff8H28KS0KyLHtbbSOGU4sujHHVwiVXSATACsNAE -D0Qa8hdtTQ6AUqA6/n8/u1tk0O4rPE/cTpsM3IJFX9S3rZMRsguBP7BSr1Lq/XAB -7JP/CNHt+Z9aKCKcg11wIX9/B9F7pyKM3TdKgOpqXGV6TMuLjg5PlYWI/07lVGFW -/mSJDRs8bSCFmbRtEqc4lpwlrpz+kTTnX6G7JDLfLWYw/xXVqwFfdengcDTHCc8K -wtgGq/Gu6vcoBxIO3jaca+OIkMfxxXmGrcNdseuUCa3RMZ8Qy03DqGu6Y6XQyK4B -W8zIG6H9SVKkAznM2yfYhW8v2ktcaZ95/OBHY97ZIw== +MIIDMDCCAhigAwIBAgIILDt8c2fMw2IwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE +AxMVbWluaWNhIHJvb3QgY2EgNTM0NWU2MB4XDTI1MDkwMzIzNDAwNVoXDTI3MTAw +MzIzNDAwNVowFDESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAmxTFtw113RK70H9pQmdKs9AxhFmnQ6BdDtp3jOZlWlUO +0BltMXOUML5905etgtCbcC6RdKRtgSAiDfgx3VWiFMJH++4gUtnaB9SN8GhNSPBp +FfSa2JhWPo9HQNUsAZqlGTV4SzcGRqtWvdZxUiOfQ2TcvyXIqsaD19ivvqI1NhT6 +bl3tredTZlzLLM6Wvkw6hfyHrJAPQP8LOlCIeDM4YIce6Gstv6qo9iCD4wJiY4u9 +5HVL7RK8t8JpZAb7VR+dPhbHEvVpjwuYd5Q05OZ280gFyrhbrKLbqst104GOQT4k +QMJGWxGONyTX6np0Dx6O5jU7dvYvjVVawbJwGuaL6wIDAQABo3oweDAOBgNVHQ8B +Af8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAfBgNV +HSMEGDAWgBSu8RGpErgYUoYnQuwCq+/ggTiEjDAiBgNVHREEGzAZgglsb2NhbGhv +c3SCBnBlYmJsZYcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAAB0gkekXCNOwqWmY +vQ2lLJ8Zk2WzQ9B+VOC27IgxEEuskZyCpyXAbJB9sCGQWZhAARyaI4SPRGGagcug +d1SwDWdPGeSJzF3aDnXDYoP9Zw2KqiqVZTngeoiw8Yn0F8PNriANwRLybouX7mMc +4V7T5+2k4SUs7pFH4KO0a0XBCcjXDjdKuBljftRTXCHzJzfRtmieCCuZlpnp5sHx +hKa/uxKGyyZB+4Y3MrzsiQSCBOr9G4TH9RofmNcawl+tsVe08zLV/XVhrbakKEs7 +Y7MGHSj3BkPFF32NObc0znqWzTaUD9hU+rXWGANM4sXd4dagdnxfrb7i0WYhcUFj +9Try8Q== -----END CERTIFICATE----- diff --git a/e2e/fixtures/certs/pebble.minica.pem b/e2e/fixtures/certs/pebble.minica.pem index a69a4c419..5578b5b55 100644 --- a/e2e/fixtures/certs/pebble.minica.pem +++ b/e2e/fixtures/certs/pebble.minica.pem @@ -1,19 +1,20 @@ -----BEGIN CERTIFICATE----- -MIIDCTCCAfGgAwIBAgIIJOLbes8sTr4wDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE -AxMVbWluaWNhIHJvb3QgY2EgMjRlMmRiMCAXDTE3MTIwNjE5NDIxMFoYDzIxMTcx -MjA2MTk0MjEwWjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSAyNGUyZGIwggEi +MIIDPzCCAiegAwIBAgIIU0Xm9UFdQxUwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE +AxMVbWluaWNhIHJvb3QgY2EgNTM0NWU2MCAXDTI1MDkwMzIzNDAwNVoYDzIxMjUw +OTAzMjM0MDA1WjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSA1MzQ1ZTYwggEi MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5WgZNoVJandj43kkLyU50vzCZ alozvdRo3OFiKoDtmqKPNWRNO2hC9AUNxTDJco51Yc42u/WV3fPbbhSznTiOOVtn Ajm6iq4I5nZYltGGZetGDOQWr78y2gWY+SG078MuOO2hyDIiKtVc3xiXYA+8Hluu 9F8KbqSS1h55yxZ9b87eKR+B0zu2ahzBCIHKmKWgc6N13l7aDxxY3D6uq8gtJRU0 toumyLbdzGcupVvjbjDP11nl07RESDWBLG1/g3ktJvqIa4BWgU2HMh4rND6y8OD3 Hy3H8MY6CElL+MOCbFJjWqhtOxeFyZZV9q3kYnk9CAuQJKMEGuN4GU6tzhW1AgMB -AAGjRTBDMA4GA1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB -BQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsFAAOCAQEAF85v -d40HK1ouDAtWeO1PbnWfGEmC5Xa478s9ddOd9Clvp2McYzNlAFfM7kdcj6xeiNhF -WPIfaGAi/QdURSL/6C1KsVDqlFBlTs9zYfh2g0UXGvJtj1maeih7zxFLvet+fqll -xseM4P9EVJaQxwuK/F78YBt0tCNfivC6JNZMgxKF59h0FBpH70ytUSHXdz7FKwix -Mfn3qEb9BXSk0Q3prNV5sOV3vgjEtB4THfDxSz9z3+DepVnW3vbbqwEbkXdk3j82 -2muVldgOUgTwK8eT+XdofVdntzU/kzygSAtAQwLJfn51fS1GvEcYGBc1bDryIqmF -p9BI7gVKtWSZYegicA== +AAGjezB5MA4GA1UdDwEB/wQEAwIChDATBgNVHSUEDDAKBggrBgEFBQcDATASBgNV +HRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSu8RGpErgYUoYnQuwCq+/ggTiEjDAf +BgNVHSMEGDAWgBSu8RGpErgYUoYnQuwCq+/ggTiEjDANBgkqhkiG9w0BAQsFAAOC +AQEAXDVYov1+f6EL7S41LhYQkEX/GyNNzsEvqxE9U0+3Iri5JfkcNOiA9O9L6Z+Y +bqcsXV93s3vi4r4WSWuc//wHyJYrVe5+tK4nlFpbJOvfBUtnoBDyKNxXzZCxFJVh +f9uc8UejRfQMFbDbhWY/x83y9BDufJHHq32OjCIN7gp2UR8rnfYvlz7Zg4qkJBsn +DG4dwd+pRTCFWJOVIG0JoNhK3ZmE7oJ1N4H38XkZ31NPcMksKxpsLLIS9+mosZtg +4olL7tMPJklx5ZaeMFaKRDq4Gdxkbw4+O4vRgNm3Z8AXWKknOdfgdpqLUPPhRcP4 +v1lhy71EhBuXXwRQJry0lTdF+w== -----END CERTIFICATE----- diff --git a/e2e/readme.md b/e2e/readme.md index 7a2367c9b..171170507 100644 --- a/e2e/readme.md +++ b/e2e/readme.md @@ -2,8 +2,8 @@ - Install [Pebble](https://github.com/letsencrypt/pebble): ```bash -go install github.com/letsencrypt/pebble/v2/cmd/pebble@main -go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@main +go install github.com/letsencrypt/pebble/v2/cmd/pebble@v2.9.0 +go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@v2.9.0 ``` - Launch tests: From 96168f78ded8a0db96b80d083834bf05d2bde313 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 21 Dec 2025 22:55:28 +0100 Subject: [PATCH 251/298] Add DNS provider for ISPConfig (#2762) --- README.md | 47 +-- cmd/zz_gen_cmd_dnshelp.go | 24 ++ docs/content/dns/zz_gen_ispconfig.md | 72 ++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/ispconfig/internal/client.go | 318 ++++++++++++++++++ .../dns/ispconfig/internal/client_test.go | 175 ++++++++++ .../fixtures/client_get_id-request.json | 4 + .../internal/fixtures/client_get_id.json | 5 + .../fixtures/dns_txt_add-request.json | 17 + .../internal/fixtures/dns_txt_add.json | 5 + .../fixtures/dns_txt_delete-request.json | 5 + .../internal/fixtures/dns_txt_delete.json | 5 + .../fixtures/dns_txt_get-request.json | 7 + .../internal/fixtures/dns_txt_get.json | 7 + .../fixtures/dns_zone_get-request.json | 4 + .../internal/fixtures/dns_zone_get.json | 32 ++ .../fixtures/dns_zone_get_id-request.json | 4 + .../internal/fixtures/dns_zone_get_id.json | 5 + .../ispconfig/internal/fixtures/error.json | 5 + .../internal/fixtures/login-request.json | 5 + .../ispconfig/internal/fixtures/login.json | 5 + providers/dns/ispconfig/internal/readme.md | 249 ++++++++++++++ providers/dns/ispconfig/internal/types.go | 95 ++++++ providers/dns/ispconfig/ispconfig.go | 220 ++++++++++++ providers/dns/ispconfig/ispconfig.toml | 27 ++ providers/dns/ispconfig/ispconfig_test.go | 173 ++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 27 files changed, 1498 insertions(+), 22 deletions(-) create mode 100644 docs/content/dns/zz_gen_ispconfig.md create mode 100644 providers/dns/ispconfig/internal/client.go create mode 100644 providers/dns/ispconfig/internal/client_test.go create mode 100644 providers/dns/ispconfig/internal/fixtures/client_get_id-request.json create mode 100644 providers/dns/ispconfig/internal/fixtures/client_get_id.json create mode 100644 providers/dns/ispconfig/internal/fixtures/dns_txt_add-request.json create mode 100644 providers/dns/ispconfig/internal/fixtures/dns_txt_add.json create mode 100644 providers/dns/ispconfig/internal/fixtures/dns_txt_delete-request.json create mode 100644 providers/dns/ispconfig/internal/fixtures/dns_txt_delete.json create mode 100644 providers/dns/ispconfig/internal/fixtures/dns_txt_get-request.json create mode 100644 providers/dns/ispconfig/internal/fixtures/dns_txt_get.json create mode 100644 providers/dns/ispconfig/internal/fixtures/dns_zone_get-request.json create mode 100644 providers/dns/ispconfig/internal/fixtures/dns_zone_get.json create mode 100644 providers/dns/ispconfig/internal/fixtures/dns_zone_get_id-request.json create mode 100644 providers/dns/ispconfig/internal/fixtures/dns_zone_get_id.json create mode 100644 providers/dns/ispconfig/internal/fixtures/error.json create mode 100644 providers/dns/ispconfig/internal/fixtures/login-request.json create mode 100644 providers/dns/ispconfig/internal/fixtures/login.json create mode 100644 providers/dns/ispconfig/internal/readme.md create mode 100644 providers/dns/ispconfig/internal/types.go create mode 100644 providers/dns/ispconfig/ispconfig.go create mode 100644 providers/dns/ispconfig/ispconfig.toml create mode 100644 providers/dns/ispconfig/ispconfig_test.go diff --git a/README.md b/README.md index ff9473e58..5530028cb 100644 --- a/README.md +++ b/README.md @@ -169,115 +169,120 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Ionos Cloud IPv64 + ISPConfig iwantmyname (Deprecated) Joker Joohoi's ACME-DNS - KeyHelp + KeyHelp Liara Lima-City Linode (v4) - Liquid Web + Liquid Web Loopia LuaDNS Mail-in-a-Box - ManageEngine CloudDNS + ManageEngine CloudDNS Manual Metaname Metaregistrar - mijn.host + mijn.host Mittwald myaddr.{tools,dev,io} MyDNS.jp - MythicBeasts + MythicBeasts Name.com Namecheap Namesilo - NearlyFreeSpeech.NET + NearlyFreeSpeech.NET Neodigit Netcup Netlify - Nicmanager + Nicmanager NIFCloud Njalla Nodion - NS1 + NS1 Octenium Open Telekom Cloud Oracle Cloud - OVH + OVH plesk.com Porkbun PowerDNS - Rackspace + Rackspace Rain Yun/雨云 RcodeZero reg.ru - Regfish + Regfish RFC2136 RimuHosting RU CENTER - Sakura Cloud + Sakura Cloud Scaleway Selectel Selectel v2 - SelfHost.(de|eu) + SelfHost.(de|eu) Servercow Shellrent Simply.com - Sonic + Sonic Spaceship Stackpath Syse - Technitium + Technitium Tencent Cloud DNS Tencent EdgeOne Timeweb Cloud - TransIP + TransIP UKFast SafeDNS Ultradns United-Domains - Variomedia + Variomedia VegaDNS Vercel Versio.[nl|eu|uk] - VinylDNS + VinylDNS Virtualname VK Cloud Volcano Engine/火山引擎 - Vscale + Vscale Vultr webnames.ca webnames.ru - Websupport + Websupport WEDOS West.cn/西部数码 Yandex 360 - Yandex Cloud + Yandex Cloud Yandex PDD Zone.ee ZoneEdit + Zonomi + + + diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index e62c337ff..ac0a52427 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -100,6 +100,7 @@ func allDNSCodes() string { "ionos", "ionoscloud", "ipv64", + "ispconfig", "iwantmyname", "joker", "keyhelp", @@ -2083,6 +2084,29 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/ipv64`) + case "ispconfig": + // generated from: providers/dns/ispconfig/ispconfig.toml + ew.writeln(`Configuration for ISPConfig.`) + ew.writeln(`Code: 'ispconfig'`) + ew.writeln(`Since: 'v4.31.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "ISPCONFIG_PASSWORD": Password`) + ew.writeln(` - "ISPCONFIG_SERVER_URL": Server URL`) + ew.writeln(` - "ISPCONFIG_USERNAME": Username`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "ISPCONFIG_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "ISPCONFIG_INSECURE_SKIP_VERIFY": Whether to verify the API certificate`) + ew.writeln(` - "ISPCONFIG_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "ISPCONFIG_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "ISPCONFIG_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/ispconfig`) + case "iwantmyname": // generated from: providers/dns/iwantmyname/iwantmyname.toml ew.writeln(`Configuration for iwantmyname (Deprecated).`) diff --git a/docs/content/dns/zz_gen_ispconfig.md b/docs/content/dns/zz_gen_ispconfig.md new file mode 100644 index 000000000..96b08a8e0 --- /dev/null +++ b/docs/content/dns/zz_gen_ispconfig.md @@ -0,0 +1,72 @@ +--- +title: "ISPConfig" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: ispconfig +dnsprovider: + since: "v4.31.0" + code: "ispconfig" + url: "https://www.ispconfig.org/" +--- + + + + + + +Configuration for [ISPConfig](https://www.ispconfig.org/). + + + + +- Code: `ispconfig` +- Since: v4.31.0 + + +Here is an example bash command using the ISPConfig provider: + +```bash +ISPCONFIG_SERVER_URL="https://example.com:8080/remote/json.php" \ +ISPCONFIG_USERNAME="xxx" \ +ISPCONFIG_PASSWORD="yyy" \ +lego --email you@example.com --dns ispconfig -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `ISPCONFIG_PASSWORD` | Password | +| `ISPCONFIG_SERVER_URL` | Server URL | +| `ISPCONFIG_USERNAME` | Username | + +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 | +|--------------------------------|-------------| +| `ISPCONFIG_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `ISPCONFIG_INSECURE_SKIP_VERIFY` | Whether to verify the API certificate | +| `ISPCONFIG_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `ISPCONFIG_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `ISPCONFIG_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/index.html) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index fdb13f57a..7de6ed5a1 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/ispconfig/internal/client.go b/providers/dns/ispconfig/internal/client.go new file mode 100644 index 000000000..9280fdec1 --- /dev/null +++ b/providers/dns/ispconfig/internal/client.go @@ -0,0 +1,318 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +type Client struct { + serverURL string + HTTPClient *http.Client +} + +func NewClient(serverURL string) (*Client, error) { + _, err := url.Parse(serverURL) + if err != nil { + return nil, fmt.Errorf("server URL: %w", err) + } + + return &Client{ + serverURL: serverURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) Login(ctx context.Context, username, password string) (string, error) { + payload := LoginRequest{ + Username: username, + Password: password, + ClientLogin: false, + } + + endpoint, err := url.Parse(c.serverURL) + if err != nil { + return "", err + } + + endpoint.RawQuery = "login" + + req, err := newJSONRequest(ctx, endpoint, payload) + if err != nil { + return "", err + } + + var response APIResponse + + err = c.do(req, &response) + if err != nil { + return "", err + } + + return extractResponse[string](response) +} + +func (c *Client) GetClientID(ctx context.Context, sessionID, sysUserID string) (int, error) { + payload := ClientIDRequest{ + SessionID: sessionID, + SysUserID: sysUserID, + } + + endpoint, err := url.Parse(c.serverURL) + if err != nil { + return 0, err + } + + endpoint.RawQuery = "client_get_id" + + req, err := newJSONRequest(ctx, endpoint, payload) + if err != nil { + return 0, err + } + + var response APIResponse + + err = c.do(req, &response) + if err != nil { + return 0, err + } + + return extractResponse[int](response) +} + +// GetZoneID returns the zone ID for the given name. +func (c *Client) GetZoneID(ctx context.Context, sessionID, name string) (int, error) { + payload := map[string]any{ + "session_id": sessionID, + "origin": name, + } + + endpoint, err := url.Parse(c.serverURL) + if err != nil { + return 0, err + } + + endpoint.RawQuery = "dns_zone_get_id" + + req, err := newJSONRequest(ctx, endpoint, payload) + if err != nil { + return 0, err + } + + var response APIResponse + + err = c.do(req, &response) + if err != nil { + return 0, err + } + + return extractResponse[int](response) +} + +// GetZone returns the zone information for the zone ID. +func (c *Client) GetZone(ctx context.Context, sessionID, zoneID string) (*Zone, error) { + payload := map[string]any{ + "session_id": sessionID, + "primary_id": zoneID, + } + + endpoint, err := url.Parse(c.serverURL) + if err != nil { + return nil, err + } + + endpoint.RawQuery = "dns_zone_get" + + req, err := newJSONRequest(ctx, endpoint, payload) + if err != nil { + return nil, err + } + + var response APIResponse + + err = c.do(req, &response) + if err != nil { + return nil, err + } + + return extractResponse[*Zone](response) +} + +// GetTXT returns the TXT record for the given name. +// `name` must be a fully qualified domain name, e.g. "example.com.". +func (c *Client) GetTXT(ctx context.Context, sessionID, name string) (*Record, error) { + payload := GetTXTRequest{ + SessionID: sessionID, + PrimaryID: struct { + Name string `json:"name"` + Type string `json:"type"` + }{ + Name: name, + Type: "txt", + }, + } + + endpoint, err := url.Parse(c.serverURL) + if err != nil { + return nil, err + } + + endpoint.RawQuery = "dns_txt_get" + + req, err := newJSONRequest(ctx, endpoint, payload) + if err != nil { + return nil, err + } + + var response APIResponse + + err = c.do(req, &response) + if err != nil { + return nil, err + } + + return extractResponse[*Record](response) +} + +// AddTXT adds a TXT record. +// It returns the ID of the newly created record. +func (c *Client) AddTXT(ctx context.Context, sessionID, clientID string, params RecordParams) (string, error) { + payload := AddTXTRequest{ + SessionID: sessionID, + ClientID: clientID, + Params: ¶ms, + UpdateSerial: true, + } + + endpoint, err := url.Parse(c.serverURL) + if err != nil { + return "", err + } + + endpoint.RawQuery = "dns_txt_add" + + req, err := newJSONRequest(ctx, endpoint, payload) + if err != nil { + return "", err + } + + var response APIResponse + + err = c.do(req, &response) + if err != nil { + return "", err + } + + return extractResponse[string](response) +} + +// DeleteTXT deletes a TXT record. +// It returns the number of deleted records. +func (c *Client) DeleteTXT(ctx context.Context, sessionID, recordID string) (int, error) { + payload := DeleteTXTRequest{ + SessionID: sessionID, + PrimaryID: recordID, + UpdateSerial: true, + } + + endpoint, err := url.Parse(c.serverURL) + if err != nil { + return 0, err + } + + endpoint.RawQuery = "dns_txt_delete" + + req, err := newJSONRequest(ctx, endpoint, payload) + if err != nil { + return 0, err + } + + var response APIResponse + + err = c.do(req, &response) + if err != nil { + return 0, err + } + + return extractResponse[int](response) +} + +func (c *Client) do(req *http.Request, result any) error { + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + raw, _ := io.ReadAll(resp.Body) + + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func extractResponse[T any](response APIResponse) (T, error) { + if response.Code != "ok" { + var zero T + + return zero, &APIError{APIResponse: response} + } + + var result T + + err := json.Unmarshal(response.Response, &result) + if err != nil { + var zero T + return zero, fmt.Errorf("unable to unmarshal response: %s, %w", string(response.Response), err) + } + + return result, nil +} diff --git a/providers/dns/ispconfig/internal/client_test.go b/providers/dns/ispconfig/internal/client_test.go new file mode 100644 index 000000000..a4db3d5f7 --- /dev/null +++ b/providers/dns/ispconfig/internal/client_test.go @@ -0,0 +1,175 @@ +package internal + +import ( + "net/http/httptest" + "testing" + "time" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(server.URL) + if err != nil { + return nil, err + } + + client.HTTPClient = server.Client() + + return client, nil + }) +} + +func TestClient_Login(t *testing.T) { + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture("login.json"), + servermock.CheckRequestJSONBodyFromFixture("login-request.json"), + servermock.CheckQueryParameter().Strict(). + With("login", ""), + ). + Build(t) + + sessionID, err := client.Login(t.Context(), "user", "secret") + require.NoError(t, err) + + assert.Equal(t, "abc", sessionID) +} + +func TestClient_Login_error(t *testing.T) { + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture("error.json"), + ). + Build(t) + + _, err := client.Login(t.Context(), "user", "secret") + require.EqualError(t, err, `code: remote_fault, message: The login failed. Username or password wrong., response: false`) +} + +func TestClient_GetClientID(t *testing.T) { + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture("client_get_id.json"), + servermock.CheckRequestJSONBodyFromFixture("client_get_id-request.json"), + servermock.CheckQueryParameter().Strict(). + With("client_get_id", ""), + ). + Build(t) + + id, err := client.GetClientID(t.Context(), "sessionA", "sysA") + require.NoError(t, err) + + assert.Equal(t, 123, id) +} + +func TestClient_GetZoneID(t *testing.T) { + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture("dns_zone_get_id.json"), + servermock.CheckRequestJSONBodyFromFixture("dns_zone_get_id-request.json"), + servermock.CheckQueryParameter().Strict(). + With("dns_zone_get_id", ""), + ). + Build(t) + + zoneID, err := client.GetZoneID(t.Context(), "sessionA", "example.com") + require.NoError(t, err) + + assert.Equal(t, 123, zoneID) +} + +func TestClient_GetZone(t *testing.T) { + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture("dns_zone_get.json"), + servermock.CheckRequestJSONBodyFromFixture("dns_zone_get-request.json"), + servermock.CheckQueryParameter().Strict(). + With("dns_zone_get", ""), + ). + Build(t) + + zone, err := client.GetZone(t.Context(), "sessionA", "example.com.") + require.NoError(t, err) + + expected := &Zone{ + ID: "456", + ServerID: "123", + SysUserID: "789", + SysGroupID: "2", + Origin: "example.com.", + Serial: "2025102902", + Active: "Y", + } + + assert.Equal(t, expected, zone) +} + +func TestClient_GetTXT(t *testing.T) { + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture("dns_txt_get.json"), + servermock.CheckRequestJSONBodyFromFixture("dns_txt_get-request.json"), + servermock.CheckQueryParameter().Strict(). + With("dns_txt_get", ""), + ). + Build(t) + + record, err := client.GetTXT(t.Context(), "sessionA", "example.com.") + require.NoError(t, err) + + expected := &Record{ID: 123} + + assert.Equal(t, expected, record) +} + +func TestClient_AddTXT(t *testing.T) { + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture("dns_txt_add.json"), + servermock.CheckRequestJSONBodyFromFixture("dns_txt_add-request.json"), + servermock.CheckQueryParameter().Strict(). + With("dns_txt_add", ""), + ). + Build(t) + + now := time.Date(2025, 12, 25, 1, 1, 1, 0, time.UTC) + + params := RecordParams{ + ServerID: "serverA", + Zone: "example.com.", + Name: "foo.example.com.", + Type: "txt", + Data: "txtTXTtxt", + Aux: "0", + TTL: "3600", + Active: "y", + Stamp: now.Format("2006-01-02 15:04:05"), + UpdateSerial: true, + } + + recordID, err := client.AddTXT(t.Context(), "sessionA", "clientA", params) + require.NoError(t, err) + + assert.Equal(t, "123", recordID) +} + +func TestClient_DeleteTXT(t *testing.T) { + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture("dns_txt_delete.json"), + servermock.CheckRequestJSONBodyFromFixture("dns_txt_delete-request.json"), + servermock.CheckQueryParameter().Strict(). + With("dns_txt_delete", ""), + ). + Build(t) + + count, err := client.DeleteTXT(t.Context(), "sessionA", "123") + require.NoError(t, err) + + assert.Equal(t, 1, count) +} diff --git a/providers/dns/ispconfig/internal/fixtures/client_get_id-request.json b/providers/dns/ispconfig/internal/fixtures/client_get_id-request.json new file mode 100644 index 000000000..ba573f824 --- /dev/null +++ b/providers/dns/ispconfig/internal/fixtures/client_get_id-request.json @@ -0,0 +1,4 @@ +{ + "session_id": "sessionA", + "sys_userid": "sysA" +} diff --git a/providers/dns/ispconfig/internal/fixtures/client_get_id.json b/providers/dns/ispconfig/internal/fixtures/client_get_id.json new file mode 100644 index 000000000..7b9f667a0 --- /dev/null +++ b/providers/dns/ispconfig/internal/fixtures/client_get_id.json @@ -0,0 +1,5 @@ +{ + "code": "ok", + "message": "foo", + "response": 123 +} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_txt_add-request.json b/providers/dns/ispconfig/internal/fixtures/dns_txt_add-request.json new file mode 100644 index 000000000..bf5242cd1 --- /dev/null +++ b/providers/dns/ispconfig/internal/fixtures/dns_txt_add-request.json @@ -0,0 +1,17 @@ +{ + "session_id": "sessionA", + "client_id": "clientA", + "params": { + "server_id": "serverA", + "zone": "example.com.", + "name": "foo.example.com.", + "type": "txt", + "data": "txtTXTtxt", + "aux": "0", + "ttl": "3600", + "active": "y", + "stamp": "2025-12-25 01:01:01", + "update_serial": true + }, + "update_serial": true +} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_txt_add.json b/providers/dns/ispconfig/internal/fixtures/dns_txt_add.json new file mode 100644 index 000000000..7980619fe --- /dev/null +++ b/providers/dns/ispconfig/internal/fixtures/dns_txt_add.json @@ -0,0 +1,5 @@ +{ + "code": "ok", + "message": "foo", + "response": "123" +} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_txt_delete-request.json b/providers/dns/ispconfig/internal/fixtures/dns_txt_delete-request.json new file mode 100644 index 000000000..240976654 --- /dev/null +++ b/providers/dns/ispconfig/internal/fixtures/dns_txt_delete-request.json @@ -0,0 +1,5 @@ +{ + "session_id": "sessionA", + "primary_id": "123", + "update_serial": true +} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_txt_delete.json b/providers/dns/ispconfig/internal/fixtures/dns_txt_delete.json new file mode 100644 index 000000000..960b650bd --- /dev/null +++ b/providers/dns/ispconfig/internal/fixtures/dns_txt_delete.json @@ -0,0 +1,5 @@ +{ + "code": "ok", + "message": "foo", + "response": 1 +} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_txt_get-request.json b/providers/dns/ispconfig/internal/fixtures/dns_txt_get-request.json new file mode 100644 index 000000000..8bda44067 --- /dev/null +++ b/providers/dns/ispconfig/internal/fixtures/dns_txt_get-request.json @@ -0,0 +1,7 @@ +{ + "session_id": "sessionA", + "primary_id": { + "name": "example.com.", + "type": "txt" + } +} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_txt_get.json b/providers/dns/ispconfig/internal/fixtures/dns_txt_get.json new file mode 100644 index 000000000..f707d50c3 --- /dev/null +++ b/providers/dns/ispconfig/internal/fixtures/dns_txt_get.json @@ -0,0 +1,7 @@ +{ + "code": "ok", + "message": "foo", + "response": { + "id": 123 + } +} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_zone_get-request.json b/providers/dns/ispconfig/internal/fixtures/dns_zone_get-request.json new file mode 100644 index 000000000..3d44d468f --- /dev/null +++ b/providers/dns/ispconfig/internal/fixtures/dns_zone_get-request.json @@ -0,0 +1,4 @@ +{ + "primary_id": "example.com.", + "session_id": "sessionA" +} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_zone_get.json b/providers/dns/ispconfig/internal/fixtures/dns_zone_get.json new file mode 100644 index 000000000..37975d0e6 --- /dev/null +++ b/providers/dns/ispconfig/internal/fixtures/dns_zone_get.json @@ -0,0 +1,32 @@ +{ + "code": "ok", + "message": "foo", + "response": { + "id": "456", + "sys_userid": "789", + "sys_groupid": "2", + "sys_perm_user": "riud", + "sys_perm_group": "riud", + "sys_perm_other": "", + "server_id": "123", + "origin": "example.com.", + "ns": "ns1.example.org.", + "mbox": "support.example.net.", + "serial": "2025102902", + "refresh": "7200", + "retry": "540", + "expire": "604800", + "minimum": "3600", + "ttl": "3600", + "active": "Y", + "xfer": "", + "also_notify": "", + "update_acl": "", + "dnssec_initialized": "N", + "dnssec_wanted": "N", + "dnssec_algo": "ECDSAP256SHA256", + "dnssec_last_signed": "0", + "dnssec_info": "", + "rendered_zone": "" + } +} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_zone_get_id-request.json b/providers/dns/ispconfig/internal/fixtures/dns_zone_get_id-request.json new file mode 100644 index 000000000..e3084242e --- /dev/null +++ b/providers/dns/ispconfig/internal/fixtures/dns_zone_get_id-request.json @@ -0,0 +1,4 @@ +{ + "origin": "example.com", + "session_id": "sessionA" +} diff --git a/providers/dns/ispconfig/internal/fixtures/dns_zone_get_id.json b/providers/dns/ispconfig/internal/fixtures/dns_zone_get_id.json new file mode 100644 index 000000000..7b9f667a0 --- /dev/null +++ b/providers/dns/ispconfig/internal/fixtures/dns_zone_get_id.json @@ -0,0 +1,5 @@ +{ + "code": "ok", + "message": "foo", + "response": 123 +} diff --git a/providers/dns/ispconfig/internal/fixtures/error.json b/providers/dns/ispconfig/internal/fixtures/error.json new file mode 100644 index 000000000..a9c76546c --- /dev/null +++ b/providers/dns/ispconfig/internal/fixtures/error.json @@ -0,0 +1,5 @@ +{ + "code": "remote_fault", + "message": "The login failed. Username or password wrong.", + "response": false +} diff --git a/providers/dns/ispconfig/internal/fixtures/login-request.json b/providers/dns/ispconfig/internal/fixtures/login-request.json new file mode 100644 index 000000000..c3293a2e8 --- /dev/null +++ b/providers/dns/ispconfig/internal/fixtures/login-request.json @@ -0,0 +1,5 @@ +{ + "username": "user", + "password": "secret", + "client_login": false +} diff --git a/providers/dns/ispconfig/internal/fixtures/login.json b/providers/dns/ispconfig/internal/fixtures/login.json new file mode 100644 index 000000000..e380a86ec --- /dev/null +++ b/providers/dns/ispconfig/internal/fixtures/login.json @@ -0,0 +1,5 @@ +{ + "code": "ok", + "message": "foo", + "response": "abc" +} diff --git a/providers/dns/ispconfig/internal/readme.md b/providers/dns/ispconfig/internal/readme.md new file mode 100644 index 000000000..2284c338f --- /dev/null +++ b/providers/dns/ispconfig/internal/readme.md @@ -0,0 +1,249 @@ +## Error Response + +```json +{ + "code": "", + "message": "", + "response": false +} +``` + +## Login Endpoint + +* URL: `?login` +* HTTP Method: `POST` + +- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/login.html +- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/examples/login.php + +### Request Body (JSON) + +```json +{ + "username": "", + "password": "", + "client_login": false +} +``` + +### Response Body (JSON) + +```json +{ + "code": "ok", + "message": "foo", + "response": "abc" +} +``` + +- `response`: is the `sessionID` + +## Get Client ID Endpoint + +* URL: `?client_get_id` +* HTTP Method: `POST` + +- function `client_get_id`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/client.inc.php#L97 +- TABLE `sys_user`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/install/sql/ispconfig3.sql?ref_type=heads#L1852 +- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/client_get_id.html +- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/examples/client_get_id.php + +### Request Body (JSON) + +```json +{ + "session_id": "", + "sys_userid": "" +} +``` + +### Response Body (JSON) + +```json +{ + "code": "ok", + "message": "foo", + "response": 123 +} +``` + +## DNS Zone Get ID Endpoint + +* URL: `?dns_zone_get_id` +* HTTP Method: `POST` + +- function `dns_zone_get_id`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L142 +- TABLE `dns_soa`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/install/sql/ispconfig3.sql?ref_type=heads#L615 + +### Request Body (JSON) + +```json +{ + "session_id": "", + "origin": "" +} +``` + +### Response Body (JSON) + +```json +{ + "code": "ok", + "message": "foo", + "response": 123 +} +``` + +## DNS Zone Get Endpoint + +* URL: `?dns_zone_get` +* HTTP Method: `POST` + +- function `dns_zone_get`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L87 +- function `getDataRecord`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remoting_lib.inc.php#L248 +- TABLE `dns_soa`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/install/sql/ispconfig3.sql?ref_type=heads#L615 +- Depending on the request, the response may be an array or an object (`primary_id` can be a string, an array or an object). +- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/dns_zone_get.html +- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/examples/dns_zone_get.php + +### Request Body (JSON) + +```json +{ + "session_id": "", + "primary_id": "" +} +``` + +### Response Body (JSON) + +```json +{ + "code": "ok", + "message": "foo", + "response": { + "id": 456, + "server_id": 123, + "sys_userid": 789 + } +} +``` + +## DNS TXT Get Endpoint + +* URL: `?dns_txt_get` +* HTTP Method: `POST` + +- function `dns_txt_get`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L640 +- function `dns_rr_get`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L195 +- form: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/web/dns/form/dns_txt.tform.php +- TABLE `dns_rr`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/install/sql/ispconfig3.sql?ref_type=heads#L490 +- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/dns_txt_get.html +- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/examples/dns_txt_get.php + +### Request Body (JSON) + +```json +{ + "session_id": "", + "primary_id": { + "name": ".", + "type": "TXT" + } +} +``` + +### Response Body (JSON) + +```json +{ + "code": "ok", + "message": "foo", + "response": { + "id": 123 + } +} +``` + +## DNS TXT Add Endpoint + +* URL: `?dns_txt_add` +* HTTP Method: `POST` + +- function `dns_txt_add`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L645 +- function `dns_rr_add` https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L212 +- form: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/web/dns/form/dns_txt.tform.php +- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/dns_txt_add.html +- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/examples/dns_txt_add.php + +### Request Body (JSON) + +```json +{ + "session_id": "", + "client_id": "", + "params": { + "server_id": "", + "zone": "", + "name": ".", + "type": "txt", + "data": "", + "aux": "0", + "ttl": "3600", + "active": "y", + "stamp": "", + "update_serial": true + }, + "update_serial": true +} +``` + +- `stamp`: (ex: `2025-12-17 23:35:58`) +- `serial`: (ex: `1766010947`) + +### Response Body (JSON) + +```json +{ + "code": "ok", + "message": "foo", + "response": "123" +} +``` + +## DNS TXT Delete Endpoint + +* URL: `?dns_txt_delete` +* HTTP Method: `POST` + +- function `dns_txt_delete`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L655 +- function `dns_rr_delete`: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/lib/classes/remote.d/dns.inc.php#L247 +- form: https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/interface/web/dns/form/dns_txt.tform.php +- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/dns_txt_delete.html +- https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/examples/dns_txt_delete.php + +### Request Body (JSON) + +```json +{ + "session_id": "", + "primary_id": "", + "update_serial": true +} +``` + +### Response Body (JSON) + +```json +{ + "code": "ok", + "message": "foo", + "response": 1 +} +``` + +--- + +https://www.ispconfig.org/ +https://git.ispconfig.org/ispconfig/ispconfig3 +https://forum.howtoforge.com/#ispconfig-3.23 diff --git a/providers/dns/ispconfig/internal/types.go b/providers/dns/ispconfig/internal/types.go new file mode 100644 index 000000000..7db0846cc --- /dev/null +++ b/providers/dns/ispconfig/internal/types.go @@ -0,0 +1,95 @@ +package internal + +import ( + "encoding/json" + "strings" +) + +type APIError struct { + APIResponse +} + +func (e *APIError) Error() string { + var msg strings.Builder + + msg.WriteString("code: " + e.Code) + + if e.Message != "" { + msg.WriteString(", message: " + e.Message) + } + + if len(e.Response) > 0 { + msg.WriteString(", response: " + string(e.Response)) + } + + return msg.String() +} + +type APIResponse struct { + Code string `json:"code"` + Message string `json:"message"` + Response json.RawMessage `json:"response"` +} + +type LoginRequest struct { + Username string `json:"username"` + Password string `json:"password"` + ClientLogin bool `json:"client_login"` +} + +type ClientIDRequest struct { + SessionID string `json:"session_id"` + SysUserID string `json:"sys_userid"` +} + +type Zone struct { + ID string `json:"id"` + ServerID string `json:"server_id"` + SysUserID string `json:"sys_userid"` + SysGroupID string `json:"sys_groupid"` + Origin string `json:"origin"` + Serial string `json:"serial"` + Active string `json:"active"` +} + +type GetTXTRequest struct { + SessionID string `json:"session_id"` + PrimaryID struct { + Name string `json:"name"` + Type string `json:"type"` + } `json:"primary_id"` +} + +type Record struct { + ID int `json:"id"` +} + +type AddTXTRequest struct { + SessionID string `json:"session_id"` + ClientID string `json:"client_id"` + Params *RecordParams `json:"params,omitempty"` + UpdateSerial bool `json:"update_serial"` +} + +type RecordParams struct { + ServerID string `json:"server_id"` + Zone string `json:"zone"` + Name string `json:"name"` + // 'a','aaaa','alias','cname','hinfo','mx','naptr','ns','ds','ptr','rp','srv','txt' + Type string `json:"type"` + Data string `json:"data"` + // "0" + Aux string `json:"aux"` + TTL string `json:"ttl"` + // 'n','y' + Active string `json:"active"` + // `2025-12-17 23:35:58` + Stamp string `json:"stamp"` + UpdateSerial bool `json:"update_serial"` +} + +type DeleteTXTRequest struct { + SessionID string `json:"session_id"` + PrimaryID string `json:"primary_id"` + UpdateSerial bool `json:"update_serial"` +} diff --git a/providers/dns/ispconfig/ispconfig.go b/providers/dns/ispconfig/ispconfig.go new file mode 100644 index 000000000..9396430b7 --- /dev/null +++ b/providers/dns/ispconfig/ispconfig.go @@ -0,0 +1,220 @@ +// Package ispconfig implements a DNS provider for solving the DNS-01 challenge using ISPConfig. +package ispconfig + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net/http" + "strconv" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/ispconfig/internal" +) + +// Environment variables names. +const ( + envNamespace = "ISPCONFIG_" + + EnvServerURL = envNamespace + "SERVER_URL" + EnvUsername = envNamespace + "USERNAME" + EnvPassword = envNamespace + "PASSWORD" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" + EnvInsecureSkipVerify = envNamespace + "INSECURE_SKIP_VERIFY" +) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + ServerURL string + Username string + Password string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client + InsecureSkipVerify bool +} + +// 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 *internal.Client + + recordIDs map[string]string + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for ISPConfig. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvServerURL, EnvUsername, EnvPassword) + if err != nil { + return nil, fmt.Errorf("ispconfig: %w", err) + } + + config := NewDefaultConfig() + config.ServerURL = values[EnvServerURL] + config.Username = values[EnvUsername] + config.Password = values[EnvPassword] + config.InsecureSkipVerify = env.GetOrDefaultBool(EnvInsecureSkipVerify, false) + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for ISPConfig. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("ispconfig: the configuration of the DNS provider is nil") + } + + if config.ServerURL == "" { + return nil, errors.New("ispconfig: missing server URL") + } + + if config.Username == "" || config.Password == "" { + return nil, errors.New("ispconfig: credentials missing") + } + + client, err := internal.NewClient(config.ServerURL) + if err != nil { + return nil, fmt.Errorf("ispconfig: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + if config.InsecureSkipVerify { + client.HTTPClient.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + recordIDs: make(map[string]string), + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + sessionID, err := d.client.Login(ctx, d.config.Username, d.config.Password) + if err != nil { + return fmt.Errorf("ispconfig: login: %w", err) + } + + zoneID, err := d.findZone(ctx, sessionID, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("ispconfig: get zone id: %w", err) + } + + zone, err := d.client.GetZone(ctx, sessionID, strconv.Itoa(zoneID)) + if err != nil { + return fmt.Errorf("ispconfig: get zone: %w", err) + } + + clientID, err := d.client.GetClientID(ctx, sessionID, zone.SysUserID) + if err != nil { + return fmt.Errorf("ispconfig: get client id: %w", err) + } + + params := internal.RecordParams{ + ServerID: "serverA", + Zone: zone.ID, + Name: info.EffectiveFQDN, + Type: "txt", + Data: info.Value, + Aux: "0", + TTL: strconv.Itoa(d.config.TTL), + Active: "y", + Stamp: time.Now().UTC().Format("2006-01-02 15:04:05"), + } + + recordID, err := d.client.AddTXT(ctx, sessionID, strconv.Itoa(clientID), params) + if err != nil { + return fmt.Errorf("ispconfig: add txt record: %w", err) + } + + d.recordIDsMu.Lock() + d.recordIDs[token] = recordID + d.recordIDsMu.Unlock() + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + // gets the record's unique ID + d.recordIDsMu.Lock() + recordID, ok := d.recordIDs[token] + d.recordIDsMu.Unlock() + + if !ok { + return fmt.Errorf("ispconfig: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) + } + + sessionID, err := d.client.Login(ctx, d.config.Username, d.config.Password) + if err != nil { + return fmt.Errorf("ispconfig: login: %w", err) + } + + _, err = d.client.DeleteTXT(ctx, sessionID, recordID) + if err != nil { + return fmt.Errorf("ispconfig: delete txt record: %w", err) + } + + 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 +} + +func (d *DNSProvider) findZone(ctx context.Context, sessionID, fqdn string) (int, error) { + for domain := range dns01.UnFqdnDomainsSeq(fqdn) { + zoneID, err := d.client.GetZoneID(ctx, sessionID, domain) + if err == nil { + return zoneID, nil + } + } + + return 0, fmt.Errorf("zone not found for %q", fqdn) +} diff --git a/providers/dns/ispconfig/ispconfig.toml b/providers/dns/ispconfig/ispconfig.toml new file mode 100644 index 000000000..a1cb89210 --- /dev/null +++ b/providers/dns/ispconfig/ispconfig.toml @@ -0,0 +1,27 @@ +Name = "ISPConfig" +Description = '''''' +URL = "https://www.ispconfig.org/" +Code = "ispconfig" +Since = "v4.31.0" + +Example = ''' +ISPCONFIG_SERVER_URL="https://example.com:8080/remote/json.php" \ +ISPCONFIG_USERNAME="xxx" \ +ISPCONFIG_PASSWORD="yyy" \ +lego --email you@example.com --dns ispconfig -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + ISPCONFIG_SERVER_URL = "Server URL" + ISPCONFIG_USERNAME = "Username" + ISPCONFIG_PASSWORD = "Password" + [Configuration.Additional] + ISPCONFIG_INSECURE_SKIP_VERIFY = "Whether to verify the API certificate" + ISPCONFIG_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + ISPCONFIG_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + ISPCONFIG_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + ISPCONFIG_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://git.ispconfig.org/ispconfig/ispconfig3/-/blob/develop/remoting_client/API-docs/index.html" diff --git a/providers/dns/ispconfig/ispconfig_test.go b/providers/dns/ispconfig/ispconfig_test.go new file mode 100644 index 000000000..b03463aee --- /dev/null +++ b/providers/dns/ispconfig/ispconfig_test.go @@ -0,0 +1,173 @@ +package ispconfig + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest( + EnvServerURL, + 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{ + EnvServerURL: "https://example.com:80/", + EnvUsername: "user", + EnvPassword: "secret", + }, + }, + { + desc: "missing server URL", + envVars: map[string]string{ + EnvServerURL: "", + EnvUsername: "user", + EnvPassword: "secret", + }, + expected: "ispconfig: some credentials information are missing: ISPCONFIG_SERVER_URL", + }, + { + desc: "missing username", + envVars: map[string]string{ + EnvServerURL: "https://example.com:80/", + EnvUsername: "", + EnvPassword: "secret", + }, + expected: "ispconfig: some credentials information are missing: ISPCONFIG_USERNAME", + }, + { + desc: "missing password", + envVars: map[string]string{ + EnvServerURL: "https://example.com:80/", + EnvUsername: "user", + EnvPassword: "", + }, + expected: "ispconfig: some credentials information are missing: ISPCONFIG_PASSWORD", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "ispconfig: some credentials information are missing: ISPCONFIG_SERVER_URL,ISPCONFIG_USERNAME,ISPCONFIG_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 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 + serverURL string + username string + password string + expected string + }{ + { + desc: "success", + serverURL: "https://example.com:80/", + username: "user", + password: "secret", + }, + { + desc: "missing server URL", + username: "user", + password: "secret", + expected: "ispconfig: missing server URL", + }, + { + desc: "missing username", + serverURL: "https://example.com:80/", + password: "secret", + expected: "ispconfig: credentials missing", + }, + { + desc: "missing password", + serverURL: "https://example.com:80/", + username: "user", + expected: "ispconfig: credentials missing", + }, + { + desc: "missing credentials", + expected: "ispconfig: missing server URL", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.ServerURL = test.serverURL + config.Username = test.username + config.Password = test.password + + 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 1270e0f9d..3a17594e2 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -94,6 +94,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/ionos" "github.com/go-acme/lego/v4/providers/dns/ionoscloud" "github.com/go-acme/lego/v4/providers/dns/ipv64" + "github.com/go-acme/lego/v4/providers/dns/ispconfig" "github.com/go-acme/lego/v4/providers/dns/iwantmyname" "github.com/go-acme/lego/v4/providers/dns/joker" "github.com/go-acme/lego/v4/providers/dns/keyhelp" @@ -363,6 +364,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return ionoscloud.NewDNSProvider() case "ipv64": return ipv64.NewDNSProvider() + case "ispconfig": + return ispconfig.NewDNSProvider() case "iwantmyname": return iwantmyname.NewDNSProvider() case "joker": From 8b327005b3105a5a70c8ace5cfe7e1f83e148f7c Mon Sep 17 00:00:00 2001 From: Simon Merschjohann Date: Mon, 22 Dec 2025 03:50:29 +0100 Subject: [PATCH 252/298] Add DNS Provider for ISPConfig (DDNS Module) (#2760) Co-authored-by: Fernandez Ludovic --- README.md | 48 ++--- cmd/zz_gen_cmd_dnshelp.go | 24 ++- docs/content/dns/zz_gen_ispconfig.md | 6 +- docs/content/dns/zz_gen_ispconfigddns.md | 74 +++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/ispconfig/ispconfig.toml | 2 +- .../dns/ispconfigddns/internal/client.go | 111 ++++++++++ .../dns/ispconfigddns/internal/client_test.go | 83 ++++++++ providers/dns/ispconfigddns/internal/types.go | 9 + providers/dns/ispconfigddns/ispconfigddns.go | 145 +++++++++++++ .../dns/ispconfigddns/ispconfigddns.toml | 32 +++ .../dns/ispconfigddns/ispconfigddns_test.go | 193 ++++++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 13 files changed, 702 insertions(+), 30 deletions(-) create mode 100644 docs/content/dns/zz_gen_ispconfigddns.md create mode 100644 providers/dns/ispconfigddns/internal/client.go create mode 100644 providers/dns/ispconfigddns/internal/client_test.go create mode 100644 providers/dns/ispconfigddns/internal/types.go create mode 100644 providers/dns/ispconfigddns/ispconfigddns.go create mode 100644 providers/dns/ispconfigddns/ispconfigddns.toml create mode 100644 providers/dns/ispconfigddns/ispconfigddns_test.go diff --git a/README.md b/README.md index 5530028cb..5fa9c1ed1 100644 --- a/README.md +++ b/README.md @@ -169,120 +169,120 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). Ionos Cloud IPv64 - ISPConfig + ISPConfig 3 + ISPConfig 3 - Dynamic DNS (DDNS) Module iwantmyname (Deprecated) Joker - Joohoi's ACME-DNS + Joohoi's ACME-DNS KeyHelp Liara Lima-City - Linode (v4) + Linode (v4) Liquid Web Loopia LuaDNS - Mail-in-a-Box + Mail-in-a-Box ManageEngine CloudDNS Manual Metaname - Metaregistrar + Metaregistrar mijn.host Mittwald myaddr.{tools,dev,io} - MyDNS.jp + MyDNS.jp MythicBeasts Name.com Namecheap - Namesilo + Namesilo NearlyFreeSpeech.NET Neodigit Netcup - Netlify + Netlify Nicmanager NIFCloud Njalla - Nodion + Nodion NS1 Octenium Open Telekom Cloud - Oracle Cloud + Oracle Cloud OVH plesk.com Porkbun - PowerDNS + PowerDNS Rackspace Rain Yun/雨云 RcodeZero - reg.ru + reg.ru Regfish RFC2136 RimuHosting - RU CENTER + RU CENTER Sakura Cloud Scaleway Selectel - Selectel v2 + Selectel v2 SelfHost.(de|eu) Servercow Shellrent - Simply.com + Simply.com Sonic Spaceship Stackpath - Syse + Syse Technitium Tencent Cloud DNS Tencent EdgeOne - Timeweb Cloud + Timeweb Cloud TransIP UKFast SafeDNS Ultradns - United-Domains + United-Domains Variomedia VegaDNS Vercel - Versio.[nl|eu|uk] + Versio.[nl|eu|uk] VinylDNS Virtualname VK Cloud - Volcano Engine/火山引擎 + Volcano Engine/火山引擎 Vscale Vultr webnames.ca - webnames.ru + webnames.ru Websupport WEDOS West.cn/西部数码 - Yandex 360 + Yandex 360 Yandex Cloud Yandex PDD Zone.ee - ZoneEdit + ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index ac0a52427..7677818c9 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -101,6 +101,7 @@ func allDNSCodes() string { "ionoscloud", "ipv64", "ispconfig", + "ispconfigddns", "iwantmyname", "joker", "keyhelp", @@ -2086,7 +2087,7 @@ func displayDNSHelp(w io.Writer, name string) error { case "ispconfig": // generated from: providers/dns/ispconfig/ispconfig.toml - ew.writeln(`Configuration for ISPConfig.`) + ew.writeln(`Configuration for ISPConfig 3.`) ew.writeln(`Code: 'ispconfig'`) ew.writeln(`Since: 'v4.31.0'`) ew.writeln() @@ -2107,6 +2108,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/ispconfig`) + case "ispconfigddns": + // generated from: providers/dns/ispconfigddns/ispconfigddns.toml + ew.writeln(`Configuration for ISPConfig 3 - Dynamic DNS (DDNS) Module.`) + ew.writeln(`Code: 'ispconfigddns'`) + ew.writeln(`Since: 'v4.31.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "ISPCONFIG_DDNS_SERVER_URL": API server URL (ex: https://panel.example.com:8080)`) + ew.writeln(` - "ISPCONFIG_DDNS_TOKEN": DDNS API token`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "ISPCONFIG_DDNS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "ISPCONFIG_DDNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "ISPCONFIG_DDNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "ISPCONFIG_DDNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/ispconfigddns`) + case "iwantmyname": // generated from: providers/dns/iwantmyname/iwantmyname.toml ew.writeln(`Configuration for iwantmyname (Deprecated).`) diff --git a/docs/content/dns/zz_gen_ispconfig.md b/docs/content/dns/zz_gen_ispconfig.md index 96b08a8e0..bd3b375da 100644 --- a/docs/content/dns/zz_gen_ispconfig.md +++ b/docs/content/dns/zz_gen_ispconfig.md @@ -1,5 +1,5 @@ --- -title: "ISPConfig" +title: "ISPConfig 3" date: 2019-03-03T16:39:46+01:00 draft: false slug: ispconfig @@ -14,7 +14,7 @@ dnsprovider: -Configuration for [ISPConfig](https://www.ispconfig.org/). +Configuration for [ISPConfig 3](https://www.ispconfig.org/). @@ -23,7 +23,7 @@ Configuration for [ISPConfig](https://www.ispconfig.org/). - Since: v4.31.0 -Here is an example bash command using the ISPConfig provider: +Here is an example bash command using the ISPConfig 3 provider: ```bash ISPCONFIG_SERVER_URL="https://example.com:8080/remote/json.php" \ diff --git a/docs/content/dns/zz_gen_ispconfigddns.md b/docs/content/dns/zz_gen_ispconfigddns.md new file mode 100644 index 000000000..c59bddda4 --- /dev/null +++ b/docs/content/dns/zz_gen_ispconfigddns.md @@ -0,0 +1,74 @@ +--- +title: "ISPConfig 3 - Dynamic DNS (DDNS) Module" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: ispconfigddns +dnsprovider: + since: "v4.31.0" + code: "ispconfigddns" + url: "https://www.ispconfig.org/" +--- + + + + + + +Configuration for [ISPConfig 3 - Dynamic DNS (DDNS) Module](https://www.ispconfig.org/). + + + + +- Code: `ispconfigddns` +- Since: v4.31.0 + + +Here is an example bash command using the ISPConfig 3 - Dynamic DNS (DDNS) Module provider: + +```bash +ISPCONFIG_DDNS_SERVER_URL="https://panel.example.com:8080" \ +ISPCONFIG_DDNS_TOKEN=xxxxxx \ +lego --email you@example.com --dns ispconfigddns -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `ISPCONFIG_DDNS_SERVER_URL` | API server URL (ex: https://panel.example.com:8080) | +| `ISPCONFIG_DDNS_TOKEN` | DDNS API token | + +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 | +|--------------------------------|-------------| +| `ISPCONFIG_DDNS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `ISPCONFIG_DDNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `ISPCONFIG_DDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `ISPCONFIG_DDNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600) | + +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" %}}). + +ISPConfig DNS provider supports leveraging the [ISPConfig 3 Dynamic DNS (DDNS) Module](https://github.com/mhofer117/ispconfig-ddns-module). + +Requires the DDNS module described at https://www.ispconfig.org/ispconfig/download/ + +See https://www.howtoforge.com/community/threads/ispconfig-3-danymic-dns-ddns-module.87967/ for additional details. + + + +## More information + +- [API documentation](https://github.com/mhofer117/ispconfig-ddns-module/tree/master/lib/updater) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 7de6ed5a1..ae2369a9a 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/ispconfig/ispconfig.toml b/providers/dns/ispconfig/ispconfig.toml index a1cb89210..399544742 100644 --- a/providers/dns/ispconfig/ispconfig.toml +++ b/providers/dns/ispconfig/ispconfig.toml @@ -1,4 +1,4 @@ -Name = "ISPConfig" +Name = "ISPConfig 3" Description = '''''' URL = "https://www.ispconfig.org/" Code = "ispconfig" diff --git a/providers/dns/ispconfigddns/internal/client.go b/providers/dns/ispconfigddns/internal/client.go new file mode 100644 index 000000000..700b58f89 --- /dev/null +++ b/providers/dns/ispconfigddns/internal/client.go @@ -0,0 +1,111 @@ +package internal + +import ( + "context" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" + querystring "github.com/google/go-querystring/query" +) + +const ( + addAction = "add" + deleteAction = "delete" +) + +type Client struct { + token string + serverURL string + + HTTPClient *http.Client +} + +func NewClient(serverURL, token string) (*Client, error) { + _, err := url.Parse(serverURL) + if err != nil { + return nil, fmt.Errorf("server URL: %w", err) + } + + return &Client{ + serverURL: serverURL, + token: token, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) AddTXTRecord(ctx context.Context, zone, fqdn, content string) error { + return c.updateRecord(ctx, UpdateRecord{Action: addAction, Zone: zone, Type: "TXT", Record: fqdn, Data: content}) +} + +func (c *Client) DeleteTXTRecord(ctx context.Context, zone, fqdn, recordContent string) error { + return c.updateRecord(ctx, UpdateRecord{Action: deleteAction, Zone: zone, Type: "TXT", Record: fqdn, Data: recordContent}) +} + +func (c *Client) updateRecord(ctx context.Context, action UpdateRecord) error { + req, err := c.newRequest(ctx, action) + if err != nil { + return err + } + + return c.do(req) +} + +func (c *Client) do(req *http.Request) error { + useragent.SetHeader(req.Header) + + req.SetBasicAuth("anonymous", c.token) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + // The endpoint uses the `DefaultDdnsResponseWriter`, + // and this writer uses HTTP status code to determine if the request was successful or not. + // - https://github.com/mhofer117/ispconfig-ddns-module/blob/8b011a5bb138881d9f13360a5c4fec10c0084613/lib/updater/DdnsUpdater.php#L53-L57 + // - https://github.com/mhofer117/ispconfig-ddns-module/blob/master/lib/updater/response/DefaultDdnsResponseWriter.php + if resp.StatusCode/100 != 2 { + raw, _ := io.ReadAll(resp.Body) + + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return nil +} + +func (c *Client) newRequest(ctx context.Context, action UpdateRecord) (*http.Request, error) { + endpoint, err := url.Parse(c.serverURL) + if err != nil { + return nil, err + } + + endpoint = endpoint.JoinPath("ddns", "update.php") + + values, err := querystring.Values(action) + if err != nil { + return nil, err + } + + endpoint.RawQuery = values.Encode() + + method := http.MethodPost + if action.Action == deleteAction { + method = http.MethodDelete + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), nil) + if err != nil { + return nil, err + } + + req.Header.Set("Accept", "application/json") + + return req, nil +} diff --git a/providers/dns/ispconfigddns/internal/client_test.go b/providers/dns/ispconfigddns/internal/client_test.go new file mode 100644 index 000000000..774e5ee46 --- /dev/null +++ b/providers/dns/ispconfigddns/internal/client_test.go @@ -0,0 +1,83 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +func setupClient(server *httptest.Server) (*Client, error) { + client, err := NewClient(server.URL, "secret") + if err != nil { + return nil, err + } + + client.HTTPClient = server.Client() + + return client, nil +} + +func TestClient_AddTXTRecord(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /ddns/update.php", + servermock.Noop(), + servermock.CheckHeader(). + WithBasicAuth("anonymous", "secret"), + servermock.CheckQueryParameter().Strict(). + With("action", "add"). + With("zone", "example.com"). + With("type", "TXT"). + With("record", "_acme-challenge.example.com."). + With("data", "token"), + ). + Build(t) + + err := client.AddTXTRecord(t.Context(), "example.com", "_acme-challenge.example.com.", "token") + require.NoError(t, err) +} + +func TestClient_AddTXTRecord_error(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /ddns/update.php", + servermock.RawStringResponse("Missing or invalid token."). + WithStatusCode(http.StatusUnauthorized), + ). + Build(t) + + err := client.AddTXTRecord(t.Context(), "example.com", "_acme-challenge.example.com.", "token") + require.EqualError(t, err, "unexpected status code: [status code: 401] body: Missing or invalid token.") +} + +func TestClient_DeleteTXTRecord(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("DELETE /ddns/update.php", + servermock.Noop(), + servermock.CheckHeader(). + WithBasicAuth("anonymous", "secret"), + servermock.CheckQueryParameter().Strict(). + With("action", "delete"). + With("zone", "example.com"). + With("type", "TXT"). + With("record", "_acme-challenge.example.com."). + With("data", "token"), + ). + Build(t) + + err := client.DeleteTXTRecord(t.Context(), "example.com", "_acme-challenge.example.com.", "token") + require.NoError(t, err) +} + +func TestClient_DeleteTXTRecord_error(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("DELETE /ddns/update.php", + servermock.RawStringResponse("Missing or invalid token."). + WithStatusCode(http.StatusUnauthorized), + ). + Build(t) + + err := client.DeleteTXTRecord(t.Context(), "example.com", "_acme-challenge.example.com.", "token") + require.EqualError(t, err, "unexpected status code: [status code: 401] body: Missing or invalid token.") +} diff --git a/providers/dns/ispconfigddns/internal/types.go b/providers/dns/ispconfigddns/internal/types.go new file mode 100644 index 000000000..278738108 --- /dev/null +++ b/providers/dns/ispconfigddns/internal/types.go @@ -0,0 +1,9 @@ +package internal + +type UpdateRecord struct { + Action string `url:"action,omitempty"` + Zone string `url:"zone,omitempty"` + Type string `url:"type,omitempty"` + Record string `url:"record,omitempty"` + Data string `url:"data,omitempty"` +} diff --git a/providers/dns/ispconfigddns/ispconfigddns.go b/providers/dns/ispconfigddns/ispconfigddns.go new file mode 100644 index 000000000..eab5d413f --- /dev/null +++ b/providers/dns/ispconfigddns/ispconfigddns.go @@ -0,0 +1,145 @@ +// Package ispconfigddns implements a DNS provider for solving the DNS-01 challenge using ISPConfig 3 Dynamic DNS (DDNS) Module. +package ispconfigddns + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/ispconfigddns/internal" +) + +// Environment variables names. +const ( + envNamespace = "ISPCONFIG_DDNS_" + + EnvServerURL = envNamespace + "SERVER_URL" + EnvToken = envNamespace + "TOKEN" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + ServerURL string + Token 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, 3600), + 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for ISPConfig 3 Dynamic DNS (DDNS) Module. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvServerURL, EnvToken) + if err != nil { + return nil, fmt.Errorf("ispconfig (DDNS module): %w", err) + } + + config := NewDefaultConfig() + config.ServerURL = values[EnvServerURL] + config.Token = values[EnvToken] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for ISPConfig 3 Dynamic DNS (DDNS) Module. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("ispconfig (DDNS module): the configuration of the DNS provider is nil") + } + + if config.ServerURL == "" { + return nil, errors.New("ispconfig (DDNS module): missing server URL") + } + + if config.Token == "" { + return nil, errors.New("ispconfig (DDNS module): missing token") + } + + client, err := internal.NewClient(config.ServerURL, config.Token) + if err != nil { + return nil, fmt.Errorf("ispconfig (DDNS module): %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +// Adjusting here to control checking compliance to spec. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +// Present creates a TXT record to fulfill the dns-01 challenge. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("ispconfig (DDNS module): could not find zone for domain %q: %w", domain, err) + } + + err = d.client.AddTXTRecord(context.Background(), dns01.UnFqdn(zone), info.EffectiveFQDN, info.Value) + if err != nil { + return fmt.Errorf("ispconfig (DDNS module): add record: %w", err) + } + + 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) + + zone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("ispconfig (DDNS module): could not find zone for domain %q: %w", domain, err) + } + + err = d.client.DeleteTXTRecord(context.Background(), dns01.UnFqdn(zone), info.EffectiveFQDN, info.Value) + if err != nil { + return fmt.Errorf("ispconfig (DDNS module): delete record: %w", err) + } + + return nil +} diff --git a/providers/dns/ispconfigddns/ispconfigddns.toml b/providers/dns/ispconfigddns/ispconfigddns.toml new file mode 100644 index 000000000..84e82904f --- /dev/null +++ b/providers/dns/ispconfigddns/ispconfigddns.toml @@ -0,0 +1,32 @@ +Name = "ISPConfig 3 - Dynamic DNS (DDNS) Module" +Description = '''''' +URL = "https://www.ispconfig.org/" +Code = "ispconfigddns" +Since = "v4.31.0" + +Example = ''' +ISPCONFIG_DDNS_SERVER_URL="https://panel.example.com:8080" \ +ISPCONFIG_DDNS_TOKEN=xxxxxx \ +lego --email you@example.com --dns ispconfigddns -d '*.example.com' -d example.com run +''' + +Additional = ''' +ISPConfig DNS provider supports leveraging the [ISPConfig 3 Dynamic DNS (DDNS) Module](https://github.com/mhofer117/ispconfig-ddns-module). + +Requires the DDNS module described at https://www.ispconfig.org/ispconfig/download/ + +See https://www.howtoforge.com/community/threads/ispconfig-3-danymic-dns-ddns-module.87967/ for additional details. +''' + +[Configuration] + [Configuration.Credentials] + ISPCONFIG_DDNS_SERVER_URL = "API server URL (ex: https://panel.example.com:8080)" + ISPCONFIG_DDNS_TOKEN = "DDNS API token" + [Configuration.Additional] + ISPCONFIG_DDNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + ISPCONFIG_DDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + ISPCONFIG_DDNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)" + ISPCONFIG_DDNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://github.com/mhofer117/ispconfig-ddns-module/tree/master/lib/updater" diff --git a/providers/dns/ispconfigddns/ispconfigddns_test.go b/providers/dns/ispconfigddns/ispconfigddns_test.go new file mode 100644 index 000000000..58e7a8f54 --- /dev/null +++ b/providers/dns/ispconfigddns/ispconfigddns_test.go @@ -0,0 +1,193 @@ +package ispconfigddns + +import ( + "net/http/httptest" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvServerURL, EnvToken). + WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvServerURL: "https://example.com", + EnvToken: "secret", + }, + }, + { + desc: "missing server URL", + envVars: map[string]string{ + EnvServerURL: "", + EnvToken: "secret", + }, + expected: "ispconfig (DDNS module): some credentials information are missing: ISPCONFIG_DDNS_SERVER_URL", + }, + { + desc: "missing token", + envVars: map[string]string{ + EnvServerURL: "https://example.com", + EnvToken: "", + }, + expected: "ispconfig (DDNS module): some credentials information are missing: ISPCONFIG_DDNS_TOKEN", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "ispconfig (DDNS module): some credentials information are missing: ISPCONFIG_DDNS_SERVER_URL,ISPCONFIG_DDNS_TOKEN", + }, + } + + 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) + } else { + require.EqualError(t, err, test.expected) + } + }) + } +} + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + serverURL string + token string + expected string + }{ + { + desc: "success", + serverURL: "https://example.com", + token: "secret", + }, + { + desc: "missing server URL", + serverURL: "", + token: "secret", + expected: "ispconfig (DDNS module): missing server URL", + }, + { + desc: "missing token", + serverURL: "https://example.com", + token: "", + expected: "ispconfig (DDNS module): missing token", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.ServerURL = test.serverURL + config.Token = test.token + + p, err := NewDNSProviderConfig(config) + + if test.expected == "" { + 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) + + err = provider.CleanUp(envTest.GetDomain(), "", "123d==") + require.NoError(t, err) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder(func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.HTTPClient = server.Client() + config.Token = "secret" + config.ServerURL = server.URL + + return NewDNSProviderConfig(config) + }, + servermock.CheckHeader(). + WithBasicAuth("anonymous", "secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("POST /ddns/update.php", + servermock.DumpRequest(), + servermock.CheckQueryParameter().Strict(). + With("action", "add"). + With("zone", "example.com"). + With("type", "TXT"). + With("record", "_acme-challenge.example.com."). + With("data", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"), + ). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("DELETE /ddns/update.php", + servermock.DumpRequest(), + servermock.CheckQueryParameter().Strict(). + With("action", "delete"). + With("zone", "example.com"). + With("type", "TXT"). + With("record", "_acme-challenge.example.com."). + With("data", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"), + ). + Build(t) + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 3a17594e2..38155e164 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -95,6 +95,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/ionoscloud" "github.com/go-acme/lego/v4/providers/dns/ipv64" "github.com/go-acme/lego/v4/providers/dns/ispconfig" + "github.com/go-acme/lego/v4/providers/dns/ispconfigddns" "github.com/go-acme/lego/v4/providers/dns/iwantmyname" "github.com/go-acme/lego/v4/providers/dns/joker" "github.com/go-acme/lego/v4/providers/dns/keyhelp" @@ -366,6 +367,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return ipv64.NewDNSProvider() case "ispconfig": return ispconfig.NewDNSProvider() + case "ispconfigddns": + return ispconfigddns.NewDNSProvider() case "iwantmyname": return iwantmyname.NewDNSProvider() case "joker": From ee616417a181239b0b77f6fe00acff3a22ae35b9 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 23 Dec 2025 13:52:22 +0100 Subject: [PATCH 253/298] f5xc: add an option to configure the domain of the server (#2767) --- cmd/zz_gen_cmd_dnshelp.go | 1 + docs/content/dns/zz_gen_f5xc.md | 1 + providers/dns/f5xc/f5xc.go | 5 +- providers/dns/f5xc/f5xc.toml | 1 + providers/dns/f5xc/f5xc_test.go | 7 +- providers/dns/f5xc/internal/client.go | 29 ++++++-- providers/dns/f5xc/internal/client_test.go | 87 +++++++++++++++++++--- 7 files changed, 111 insertions(+), 20 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 7677818c9..601222903 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -1449,6 +1449,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(` - "F5XC_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "F5XC_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "F5XC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "F5XC_SERVER": Server domain (Default: console.ves.volterra.io)`) ew.writeln(` - "F5XC_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) ew.writeln() diff --git a/docs/content/dns/zz_gen_f5xc.md b/docs/content/dns/zz_gen_f5xc.md index c8a664a00..52488f1f7 100644 --- a/docs/content/dns/zz_gen_f5xc.md +++ b/docs/content/dns/zz_gen_f5xc.md @@ -54,6 +54,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | `F5XC_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `F5XC_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `F5XC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `F5XC_SERVER` | Server domain (Default: console.ves.volterra.io) | | `F5XC_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/providers/dns/f5xc/f5xc.go b/providers/dns/f5xc/f5xc.go index 6f8a8c493..76a6e0262 100644 --- a/providers/dns/f5xc/f5xc.go +++ b/providers/dns/f5xc/f5xc.go @@ -22,6 +22,7 @@ const ( EnvToken = envNamespace + "API_TOKEN" EnvTenantName = envNamespace + "TENANT_NAME" + EnvServer = envNamespace + "SERVER" EnvGroupName = envNamespace + "GROUP_NAME" EnvTTL = envNamespace + "TTL" @@ -34,6 +35,7 @@ const ( type Config struct { APIToken string TenantName string + Server string GroupName string PropagationTimeout time.Duration @@ -71,6 +73,7 @@ func NewDNSProvider() (*DNSProvider, error) { config.APIToken = values[EnvToken] config.TenantName = values[EnvTenantName] config.GroupName = values[EnvGroupName] + config.Server = env.GetOrFile(EnvServer) return NewDNSProviderConfig(config) } @@ -85,7 +88,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("f5xc: missing group name") } - client, err := internal.NewClient(config.APIToken, config.TenantName) + client, err := internal.NewClient(config.APIToken, config.TenantName, config.Server) if err != nil { return nil, fmt.Errorf("f5xc: %w", err) } diff --git a/providers/dns/f5xc/f5xc.toml b/providers/dns/f5xc/f5xc.toml index 7a4cab419..f5a843c97 100644 --- a/providers/dns/f5xc/f5xc.toml +++ b/providers/dns/f5xc/f5xc.toml @@ -17,6 +17,7 @@ lego --email you@example.com --dns f5xc -d '*.example.com' -d example.com run F5XC_TENANT_NAME = "XC Tenant shortname" F5XC_GROUP_NAME = "Group name" [Configuration.Additional] + F5XC_SERVER = "Server domain (Default: console.ves.volterra.io)" F5XC_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" F5XC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" F5XC_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" diff --git a/providers/dns/f5xc/f5xc_test.go b/providers/dns/f5xc/f5xc_test.go index 98f7484e7..890a4cf09 100644 --- a/providers/dns/f5xc/f5xc_test.go +++ b/providers/dns/f5xc/f5xc_test.go @@ -9,7 +9,12 @@ import ( const envDomain = envNamespace + "DOMAIN" -var envTest = tester.NewEnvTest(EnvToken, EnvTenantName, EnvGroupName).WithDomain(envDomain) +var envTest = tester.NewEnvTest( + EnvToken, + EnvTenantName, + EnvServer, + EnvGroupName, +).WithDomain(envDomain) func TestNewDNSProvider(t *testing.T) { testCases := []struct { diff --git a/providers/dns/f5xc/internal/client.go b/providers/dns/f5xc/internal/client.go index b0b5d0468..7beab0d03 100644 --- a/providers/dns/f5xc/internal/client.go +++ b/providers/dns/f5xc/internal/client.go @@ -14,7 +14,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) -const defaultHost = "console.ves.volterra.io" +const defaultServer = "console.ves.volterra.io" const authorizationHeader = "Authorization" @@ -27,18 +27,14 @@ type Client struct { } // NewClient creates a new Client. -func NewClient(apiToken, tenantName string) (*Client, error) { +func NewClient(apiToken, tenantName, server string) (*Client, error) { if apiToken == "" { return nil, errors.New("credentials missing") } - if tenantName == "" { - return nil, errors.New("missing tenant name") - } - - baseURL, err := url.Parse(fmt.Sprintf("https://%s.%s", tenantName, defaultHost)) + baseURL, err := createBaseURL(tenantName, server) if err != nil { - return nil, fmt.Errorf("parse base URL: %w", err) + return nil, err } return &Client{ @@ -209,3 +205,20 @@ func parseError(req *http.Request, resp *http.Response) error { return &apiErr } + +func createBaseURL(tenant, server string) (*url.URL, error) { + if tenant == "" { + return nil, errors.New("missing tenant name") + } + + if server == "" { + server = defaultServer + } + + baseURL, err := url.Parse(fmt.Sprintf("https://%s.%s", tenant, server)) + if err != nil { + return nil, fmt.Errorf("parse base URL: %w", err) + } + + return baseURL, nil +} diff --git a/providers/dns/f5xc/internal/client_test.go b/providers/dns/f5xc/internal/client_test.go index 0357abb16..bb188ef3f 100644 --- a/providers/dns/f5xc/internal/client_test.go +++ b/providers/dns/f5xc/internal/client_test.go @@ -14,7 +14,7 @@ import ( func mockBuilder() *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { - client, err := NewClient("secret", "shortname") + client, err := NewClient("secret", "shortname", "") if err != nil { return nil, err } @@ -28,7 +28,7 @@ func mockBuilder() *servermock.Builder[*Client] { WithAuthorization("APIToken secret")) } -func TestClient_Create(t *testing.T) { +func TestClient_CreateRRSet(t *testing.T) { client := mockBuilder(). Route("POST /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA", servermock.ResponseFromFixture("create.json"), @@ -62,7 +62,7 @@ func TestClient_Create(t *testing.T) { assert.Equal(t, expected, result) } -func TestClient_Create_error(t *testing.T) { +func TestClient_CreateRRSet_error(t *testing.T) { client := mockBuilder(). Route("POST /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA", servermock.Noop().WithStatusCode(http.StatusBadRequest)). @@ -81,7 +81,7 @@ func TestClient_Create_error(t *testing.T) { require.Error(t, err) } -func TestClient_Get(t *testing.T) { +func TestClient_GetRRSet(t *testing.T) { client := mockBuilder(). Route("GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", servermock.ResponseFromFixture("get.json")). @@ -108,7 +108,7 @@ func TestClient_Get(t *testing.T) { assert.Equal(t, expected, result) } -func TestClient_Get_not_found(t *testing.T) { +func TestClient_GetRRSet_not_found(t *testing.T) { client := mockBuilder(). Route("GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", servermock.ResponseFromFixture("error_404.json").WithStatusCode(http.StatusNotFound)). @@ -120,7 +120,7 @@ func TestClient_Get_not_found(t *testing.T) { assert.Nil(t, result) } -func TestClient_Get_error(t *testing.T) { +func TestClient_GetRRSet_error(t *testing.T) { client := mockBuilder(). Route("GET /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", servermock.Noop().WithStatusCode(http.StatusBadRequest)). @@ -130,7 +130,7 @@ func TestClient_Get_error(t *testing.T) { require.Error(t, err) } -func TestClient_Delete(t *testing.T) { +func TestClient_DeleteRRSet(t *testing.T) { client := mockBuilder(). Route("DELETE /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", servermock.ResponseFromFixture("get.json")). @@ -157,7 +157,7 @@ func TestClient_Delete(t *testing.T) { assert.Equal(t, expected, result) } -func TestClient_Delete_error(t *testing.T) { +func TestClient_DeleteRRSet_error(t *testing.T) { client := mockBuilder(). Route("DELETE /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", servermock.Noop().WithStatusCode(http.StatusBadRequest)). @@ -167,7 +167,7 @@ func TestClient_Delete_error(t *testing.T) { require.Error(t, err) } -func TestClient_Replace(t *testing.T) { +func TestClient_ReplaceRRSet(t *testing.T) { client := mockBuilder(). Route("PUT /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", servermock.ResponseFromFixture("get.json"), @@ -204,7 +204,7 @@ func TestClient_Replace(t *testing.T) { assert.Equal(t, expected, result) } -func TestClient_Replace_error(t *testing.T) { +func TestClient_ReplaceRRSet_error(t *testing.T) { client := mockBuilder(). Route("PUT /api/config/dns/namespaces/system/dns_zones/example.com/rrsets/groupA/www/TXT", servermock.Noop().WithStatusCode(http.StatusBadRequest)). @@ -222,3 +222,70 @@ func TestClient_Replace_error(t *testing.T) { _, err := client.ReplaceRRSet(t.Context(), "example.com", "groupA", "www", "TXT", rrSet) require.Error(t, err) } + +func Test_createBaseURL(t *testing.T) { + testCases := []struct { + desc string + tenant string + server string + expected string + }{ + { + desc: "only tenant", + tenant: "foo", + expected: "https://foo.console.ves.volterra.io", + }, + { + desc: "custom server", + tenant: "foo", + server: "example.com", + expected: "https://foo.example.com", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + baseURL, err := createBaseURL(test.tenant, test.server) + require.NoError(t, err) + + assert.Equal(t, test.expected, baseURL.String()) + }) + } +} + +func Test_createBaseURL_error(t *testing.T) { + testCases := []struct { + desc string + tenant string + server string + expected string + }{ + { + desc: "no tenant", + tenant: "", + expected: "missing tenant name", + }, + { + desc: "invalid tenant", + tenant: "%31", + expected: `parse base URL: parse "https://%31.console.ves.volterra.io": invalid URL escape "%31"`, + }, + { + desc: "invalid host", + tenant: "foo", + server: "192.168.0.%31", + expected: `parse base URL: parse "https://foo.192.168.0.%31": invalid URL escape "%31"`, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + _, err := createBaseURL(test.tenant, test.server) + require.EqualError(t, err, test.expected) + }) + } +} From ff885d99c2e45f166a0c9592ea047d8c938604d3 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 23 Dec 2025 16:39:06 +0100 Subject: [PATCH 254/298] gandiv5: fix API Key header (#2769) --- providers/dns/gandiv5/internal/client.go | 5 +---- providers/dns/gandiv5/internal/client_test.go | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/providers/dns/gandiv5/internal/client.go b/providers/dns/gandiv5/internal/client.go index 36e0dafb1..bfb71c9f6 100644 --- a/providers/dns/gandiv5/internal/client.go +++ b/providers/dns/gandiv5/internal/client.go @@ -17,9 +17,6 @@ import ( // defaultBaseURL endpoint is the Gandi API endpoint used by Present and CleanUp. const defaultBaseURL = "https://api.gandi.net/v5/livedns" -// APIKeyHeader API key header. -const APIKeyHeader = "X-Api-Key" - // Related to Personal Access Token. const authorizationHeader = "Authorization" @@ -133,7 +130,7 @@ func (c *Client) DeleteTXTRecord(ctx context.Context, domain, name string) error func (c *Client) do(req *http.Request, result any) error { if c.apiKey != "" { - req.Header.Set(APIKeyHeader, c.apiKey) + req.Header.Set(authorizationHeader, "Apikey "+c.apiKey) } if c.pat != "" { diff --git a/providers/dns/gandiv5/internal/client_test.go b/providers/dns/gandiv5/internal/client_test.go index 2465566f9..6a4158dcb 100644 --- a/providers/dns/gandiv5/internal/client_test.go +++ b/providers/dns/gandiv5/internal/client_test.go @@ -9,23 +9,29 @@ import ( "github.com/stretchr/testify/require" ) -func mockBuilder() *servermock.Builder[*Client] { +func mockBuilder(apiKey, pat string) *servermock.Builder[*Client] { + checkHeaders := servermock.CheckHeader().WithJSONHeaders() + + if apiKey != "" { + checkHeaders = checkHeaders.WithAuthorization("Apikey secret-apikey") + } else { + checkHeaders = checkHeaders.WithAuthorization("Bearer secret-pat") + } + return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { - client := NewClient("secret", "xxx") + client := NewClient(apiKey, pat) client.BaseURL, _ = url.Parse(server.URL) client.HTTPClient = server.Client() return client, nil }, - servermock.CheckHeader().WithJSONHeaders(). - With("X-Api-Key", "secret"). - WithAuthorization("Bearer xxx"), + checkHeaders, ) } func TestClient_AddTXTRecord(t *testing.T) { - client := mockBuilder(). + client := mockBuilder("secret-apikey", ""). Route("GET /domains/example.com/records/foo/TXT", servermock.ResponseFromFixture("add_txt_record_get.json")). Route("PUT /domains/example.com/records/foo/TXT", @@ -38,7 +44,7 @@ func TestClient_AddTXTRecord(t *testing.T) { } func TestClient_DeleteTXTRecord(t *testing.T) { - client := mockBuilder(). + client := mockBuilder("", "secret-pat"). Route("DELETE /domains/example.com/records/foo/TXT", servermock.ResponseFromFixture("api_response.json")). Build(t) From a6a73754af31fb65963f53845bb9ce55d09466a1 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 29 Dec 2025 14:09:06 +0100 Subject: [PATCH 255/298] Add DNS provider for Alwaysdata (#2770) --- README.md | 88 ++++----- cmd/zz_gen_cmd_dnshelp.go | 22 +++ docs/content/dns/zz_gen_alwaysdata.md | 68 +++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/alwaysdata/alwaysdata.go | 185 +++++++++++++++++ providers/dns/alwaysdata/alwaysdata.toml | 26 +++ providers/dns/alwaysdata/alwaysdata_test.go | 187 ++++++++++++++++++ providers/dns/alwaysdata/internal/client.go | 177 +++++++++++++++++ .../dns/alwaysdata/internal/client_test.go | 124 ++++++++++++ .../alwaysdata/internal/fixtures/domains.json | 16 ++ .../internal/fixtures/record_add-request.json | 8 + .../alwaysdata/internal/fixtures/records.json | 28 +++ providers/dns/alwaysdata/internal/types.go | 33 ++++ providers/dns/zz_gen_dns_providers.go | 3 + 14 files changed, 922 insertions(+), 45 deletions(-) create mode 100644 docs/content/dns/zz_gen_alwaysdata.md create mode 100644 providers/dns/alwaysdata/alwaysdata.go create mode 100644 providers/dns/alwaysdata/alwaysdata.toml create mode 100644 providers/dns/alwaysdata/alwaysdata_test.go create mode 100644 providers/dns/alwaysdata/internal/client.go create mode 100644 providers/dns/alwaysdata/internal/client_test.go create mode 100644 providers/dns/alwaysdata/internal/fixtures/domains.json create mode 100644 providers/dns/alwaysdata/internal/fixtures/record_add-request.json create mode 100644 providers/dns/alwaysdata/internal/fixtures/records.json create mode 100644 providers/dns/alwaysdata/internal/types.go diff --git a/README.md b/README.md index 5fa9c1ed1..aff5052ca 100644 --- a/README.md +++ b/README.md @@ -65,224 +65,224 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). AlibabaCloud ESA all-inkl + Alwaysdata Amazon Lightsail Amazon Route 53 - Anexia CloudDNS + Anexia CloudDNS ArvanCloud Aurora DNS Autodns - Axelname + Axelname Azion Azure (deprecated) Azure DNS - Baidu Cloud + Baidu Cloud Beget.com Binary Lane Bindman - Bluecat + Bluecat BookMyName Brandit (deprecated) Bunny - Checkdomain + Checkdomain Civo Cloud.ru CloudDNS - Cloudflare + Cloudflare ClouDNS CloudXNS (Deprecated) ConoHa v2 - ConoHa v3 + ConoHa v3 Constellix Core-Networks CPanel/WHM - Derak Cloud + Derak Cloud deSEC.io Designate DNSaaS for Openstack Digital Ocean - DirectAdmin + DirectAdmin DNS Made Easy dnsHome.de DNSimple - DNSPod (deprecated) + DNSPod (deprecated) Domain Offensive (do.de) Domeneshop DreamHost - Duck DNS + Duck DNS Dyn DynDnsFree.de Dynu - EasyDNS + EasyDNS EdgeCenter Efficient IP Epik - Exoscale + Exoscale External program F5 XC freemyip.com - G-Core + G-Core Gandi Gandi Live DNS (v5) Gigahost.no - Glesys + Glesys Go Daddy Google Cloud Google Domains - Gravity + Gravity Hetzner Hosting.de Hosting.nl - Hostinger + Hostinger Hosttech HTTP request http.net - Huawei Cloud + Huawei Cloud Hurricane Electric DNS HyperOne IBM Cloud (SoftLayer) - IIJ DNS Platform Service + IIJ DNS Platform Service Infoblox Infomaniak Internet Initiative Japan - Internet.bs + Internet.bs INWX Ionos Ionos Cloud - IPv64 + IPv64 ISPConfig 3 ISPConfig 3 - Dynamic DNS (DDNS) Module iwantmyname (Deprecated) - Joker + Joker Joohoi's ACME-DNS KeyHelp Liara - Lima-City + Lima-City Linode (v4) Liquid Web Loopia - LuaDNS + LuaDNS Mail-in-a-Box ManageEngine CloudDNS Manual - Metaname + Metaname Metaregistrar mijn.host Mittwald - myaddr.{tools,dev,io} + myaddr.{tools,dev,io} MyDNS.jp MythicBeasts Name.com - Namecheap + Namecheap Namesilo NearlyFreeSpeech.NET Neodigit - Netcup + Netcup Netlify Nicmanager NIFCloud - Njalla + Njalla Nodion NS1 Octenium - Open Telekom Cloud + Open Telekom Cloud Oracle Cloud OVH plesk.com - Porkbun + Porkbun PowerDNS Rackspace Rain Yun/雨云 - RcodeZero + RcodeZero reg.ru Regfish RFC2136 - RimuHosting + RimuHosting RU CENTER Sakura Cloud Scaleway - Selectel + Selectel Selectel v2 SelfHost.(de|eu) Servercow - Shellrent + Shellrent Simply.com Sonic Spaceship - Stackpath + Stackpath Syse Technitium Tencent Cloud DNS - Tencent EdgeOne + Tencent EdgeOne Timeweb Cloud TransIP UKFast SafeDNS - Ultradns + Ultradns United-Domains Variomedia VegaDNS - Vercel + Vercel Versio.[nl|eu|uk] VinylDNS Virtualname - VK Cloud + VK Cloud Volcano Engine/火山引擎 Vscale Vultr - webnames.ca + webnames.ca webnames.ru Websupport WEDOS - West.cn/西部数码 + West.cn/西部数码 Yandex 360 Yandex Cloud Yandex PDD - Zone.ee + Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 601222903..220289242 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -17,6 +17,7 @@ func allDNSCodes() string { "alidns", "aliesa", "allinkl", + "alwaysdata", "anexia", "arvancloud", "auroradns", @@ -306,6 +307,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/allinkl`) + case "alwaysdata": + // generated from: providers/dns/alwaysdata/alwaysdata.toml + ew.writeln(`Configuration for Alwaysdata.`) + ew.writeln(`Code: 'alwaysdata'`) + ew.writeln(`Since: 'v4.31.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "ALWAYSDATA_API_KEY": API Key`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "ALWAYSDATA_ACCOUNT": Account name`) + ew.writeln(` - "ALWAYSDATA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "ALWAYSDATA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "ALWAYSDATA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "ALWAYSDATA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/alwaysdata`) + case "anexia": // generated from: providers/dns/anexia/anexia.toml ew.writeln(`Configuration for Anexia CloudDNS.`) diff --git a/docs/content/dns/zz_gen_alwaysdata.md b/docs/content/dns/zz_gen_alwaysdata.md new file mode 100644 index 000000000..75f3cb859 --- /dev/null +++ b/docs/content/dns/zz_gen_alwaysdata.md @@ -0,0 +1,68 @@ +--- +title: "Alwaysdata" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: alwaysdata +dnsprovider: + since: "v4.31.0" + code: "alwaysdata" + url: "https://alwaysdata.com/" +--- + + + + + + +Configuration for [Alwaysdata](https://alwaysdata.com/). + + + + +- Code: `alwaysdata` +- Since: v4.31.0 + + +Here is an example bash command using the Alwaysdata provider: + +```bash +ALWAYSDATA_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns alwaysdata -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `ALWAYSDATA_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 | +|--------------------------------|-------------| +| `ALWAYSDATA_ACCOUNT` | Account name | +| `ALWAYSDATA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `ALWAYSDATA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `ALWAYSDATA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `ALWAYSDATA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://help.alwaysdata.com/en/api/resources/) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index ae2369a9a..ab9ff31c9 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/alwaysdata/alwaysdata.go b/providers/dns/alwaysdata/alwaysdata.go new file mode 100644 index 000000000..b2e0f3957 --- /dev/null +++ b/providers/dns/alwaysdata/alwaysdata.go @@ -0,0 +1,185 @@ +// Package alwaysdata implements a DNS provider for solving the DNS-01 challenge using Alwaysdata. +package alwaysdata + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/alwaysdata/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" +) + +// Environment variables names. +const ( + envNamespace = "ALWAYSDATA_" + + EnvAPIKey = envNamespace + "API_KEY" + EnvAccount = envNamespace + "ACCOUNT" + + 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 + Account 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for Alwaysdata. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIKey) + if err != nil { + return nil, fmt.Errorf("alwaysdata: %w", err) + } + + config := NewDefaultConfig() + config.APIKey = values[EnvAPIKey] + config.Account = env.GetOrFile(EnvAccount) + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Alwaysdata. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("alwaysdata: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.APIKey, config.Account) + if err != nil { + return nil, fmt.Errorf("alwaysdata: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + zone, err := d.findZone(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("alwaysdata: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone.Name) + if err != nil { + return fmt.Errorf("alwaysdata: %w", err) + } + + record := internal.RecordRequest{ + DomainID: zone.ID, + Name: subDomain, + Type: "TXT", + Value: info.Value, + TTL: d.config.TTL, + Annotation: "lego", + } + + err = d.client.AddRecord(ctx, record) + if err != nil { + return fmt.Errorf("alwaysdata: add TXT record: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + zone, err := d.findZone(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("alwaysdata: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone.Name) + if err != nil { + return fmt.Errorf("alwaysdata: %w", err) + } + + records, err := d.client.ListRecords(ctx, zone.ID, subDomain) + if err != nil { + return fmt.Errorf("alwaysdata: list records: %w", err) + } + + for _, record := range records { + if record.Type != "TXT" || record.Value != info.Value { + continue + } + + err = d.client.DeleteRecord(ctx, record.ID) + if err != nil { + return fmt.Errorf("alwaysdata: delete TXT record: %w", err) + } + } + + 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 +} + +func (d *DNSProvider) findZone(ctx context.Context, fqdn string) (*internal.Domain, error) { + domains, err := d.client.ListDomains(ctx) + if err != nil { + return nil, fmt.Errorf("list domains: %w", err) + } + + for a := range dns01.UnFqdnDomainsSeq(fqdn) { + for _, domain := range domains { + if a == domain.Name { + return &domain, nil + } + } + } + + return nil, errors.New("domain not found") +} diff --git a/providers/dns/alwaysdata/alwaysdata.toml b/providers/dns/alwaysdata/alwaysdata.toml new file mode 100644 index 000000000..96d8d9616 --- /dev/null +++ b/providers/dns/alwaysdata/alwaysdata.toml @@ -0,0 +1,26 @@ +Name = "Alwaysdata" +Description = '''''' +URL = "https://alwaysdata.com/" +Code = "alwaysdata" +Since = "v4.31.0" + +Example = ''' +ALWAYSDATA_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ +lego --email you@example.com --dns alwaysdata -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + ALWAYSDATA_API_KEY = "API Key" + [Configuration.Additional] + ALWAYSDATA_ACCOUNT = "Account name" + ALWAYSDATA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + ALWAYSDATA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + ALWAYSDATA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + ALWAYSDATA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://help.alwaysdata.com/en/api/resources/" + APIDocDomains = "https://api.alwaysdata.com/v1/domain/doc/" + APIDocRecords = "https://api.alwaysdata.com/v1/record/doc/" + APIExamples = "https://help.alwaysdata.com/en/api/examples/" diff --git a/providers/dns/alwaysdata/alwaysdata_test.go b/providers/dns/alwaysdata/alwaysdata_test.go new file mode 100644 index 000000000..6084c2ae4 --- /dev/null +++ b/providers/dns/alwaysdata/alwaysdata_test.go @@ -0,0 +1,187 @@ +package alwaysdata + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvAPIKey, EnvAccount).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: "success with an account", + envVars: map[string]string{ + EnvAPIKey: "secret", + EnvAccount: "foo", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "alwaysdata: some credentials information are missing: ALWAYSDATA_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 + account string + expected string + }{ + { + desc: "success", + apiKey: "secret", + }, + { + desc: "success with an account", + apiKey: "secret", + account: "foo", + }, + { + desc: "missing credentials", + expected: "alwaysdata: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIKey = test.apiKey + config.Account = test.account + + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.APIKey = "secret" + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + WithBasicAuth("secret", ""), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("GET /domain/", + servermock.ResponseFromInternal("domains.json")). + Route("POST /record/", + servermock.Noop().WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBodyFromInternal("record_add-request.json")). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("GET /domain/", + servermock.ResponseFromInternal("domains.json")). + Route("GET /record/", + servermock.ResponseFromInternal("records.json"), + servermock.CheckQueryParameter().Strict(). + With("domain", "132"). + With("name", "_acme-challenge"), + ). + Route("DELETE /record/789/", + servermock.Noop()). + Build(t) + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/alwaysdata/internal/client.go b/providers/dns/alwaysdata/internal/client.go new file mode 100644 index 000000000..5db11dcd1 --- /dev/null +++ b/providers/dns/alwaysdata/internal/client.go @@ -0,0 +1,177 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" +) + +const defaultBaseURL = "https://api.alwaysdata.com/v1" + +// Client the Alwaysdata API client. +type Client struct { + apiKey string + account string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(apiKey, account string) (*Client, error) { + if apiKey == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + apiKey: apiKey, + account: account, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) ListDomains(ctx context.Context) ([]Domain, error) { + endpoint := c.BaseURL.JoinPath("domain", "/") + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var result []Domain + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result, nil +} + +func (c *Client) AddRecord(ctx context.Context, record RecordRequest) error { + endpoint := c.BaseURL.JoinPath("record", "/") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) + if err != nil { + return err + } + + err = c.do(req, nil) + if err != nil { + return err + } + + return nil +} + +func (c *Client) DeleteRecord(ctx context.Context, recordID int64) error { + endpoint := c.BaseURL.JoinPath("record", strconv.FormatInt(recordID, 10), "/") + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) ListRecords(ctx context.Context, domainID int64, name string) ([]Record, error) { + endpoint := c.BaseURL.JoinPath("record", "/") + + query := endpoint.Query() + query.Set("domain", strconv.FormatInt(domainID, 10)) + query.Set("name", name) + endpoint.RawQuery = query.Encode() + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + var result []Record + + err = c.do(req, &result) + if err != nil { + return nil, err + } + + return result, nil +} + +func (c *Client) do(req *http.Request, result any) error { + useragent.SetHeader(req.Header) + + user := c.apiKey + + if c.account != "" { + user += "account=" + c.account + } + + req.SetBasicAuth(user, "") + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + raw, _ := io.ReadAll(resp.Body) + + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} diff --git a/providers/dns/alwaysdata/internal/client_test.go b/providers/dns/alwaysdata/internal/client_test.go new file mode 100644 index 000000000..e6a349662 --- /dev/null +++ b/providers/dns/alwaysdata/internal/client_test.go @@ -0,0 +1,124 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("secret", "") + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = clientdebug.Wrap(server.Client()) + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + WithBasicAuth("secret", ""), + ) +} + +func TestClient_ListDomains(t *testing.T) { + client := mockBuilder(). + Route("GET /domain/", + servermock.ResponseFromFixture("domains.json")). + Build(t) + + result, err := client.ListDomains(t.Context()) + require.NoError(t, err) + + expected := []Domain{ + {ID: 132, Name: "example.com", Annotation: "test"}, + {ID: 133, Name: "example.net", IsInternal: true}, + {ID: 134, Name: "example.org"}, + } + + assert.Equal(t, expected, result) +} + +func TestClient_AddRecord(t *testing.T) { + t.Setenv("LEGO_DEBUG_DNS_API_HTTP_CLIENT", "true") + + client := mockBuilder(). + Route("POST /record/", + servermock.Noop().WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBodyFromFixture("record_add-request.json")). + Build(t) + + record := RecordRequest{ + DomainID: 132, + Name: "_acme-challenge", + Type: "TXT", + Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 120, + Annotation: "lego", + } + + err := client.AddRecord(t.Context(), record) + require.NoError(t, err) +} + +func TestClient_DeleteRecord(t *testing.T) { + client := mockBuilder(). + Route("DELETE /record/789/", + servermock.Noop()). + Build(t) + + err := client.DeleteRecord(t.Context(), 789) + require.NoError(t, err) +} + +func TestClient_ListRecords(t *testing.T) { + client := mockBuilder(). + Route("GET /record/", + servermock.ResponseFromFixture("records.json"), + servermock.CheckQueryParameter().Strict(). + With("domain", "132"). + With("name", "_acme-challenge"), + ). + Build(t) + + result, err := client.ListRecords(t.Context(), 132, "_acme-challenge") + require.NoError(t, err) + + expected := []Record{ + { + ID: 789, + Domain: &Domain{ + Href: "/v1/domain/132/", + }, + Type: "TXT", + Name: "_acme-challenge", + Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 120, + Annotation: "lego", + }, + { + ID: 11619270, + Domain: &Domain{ + Href: "/v1/domain/118935/", + }, + Name: "home", + Type: "A", + Value: "149.202.90.65", + TTL: 300, + IsUserDefined: true, + IsActive: true, + }, + } + + assert.Equal(t, expected, result) +} diff --git a/providers/dns/alwaysdata/internal/fixtures/domains.json b/providers/dns/alwaysdata/internal/fixtures/domains.json new file mode 100644 index 000000000..dc34a948f --- /dev/null +++ b/providers/dns/alwaysdata/internal/fixtures/domains.json @@ -0,0 +1,16 @@ +[ + { + "id": 132, + "name": "example.com", + "annotation": "test" + }, + { + "id": 133, + "name": "example.net", + "is_internal": true + }, + { + "id": 134, + "name": "example.org" + } +] diff --git a/providers/dns/alwaysdata/internal/fixtures/record_add-request.json b/providers/dns/alwaysdata/internal/fixtures/record_add-request.json new file mode 100644 index 000000000..5b6db2646 --- /dev/null +++ b/providers/dns/alwaysdata/internal/fixtures/record_add-request.json @@ -0,0 +1,8 @@ +{ + "domain": 132, + "name": "_acme-challenge", + "type": "TXT", + "value": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "ttl": 120, + "annotation": "lego" +} diff --git a/providers/dns/alwaysdata/internal/fixtures/records.json b/providers/dns/alwaysdata/internal/fixtures/records.json new file mode 100644 index 000000000..fa207395a --- /dev/null +++ b/providers/dns/alwaysdata/internal/fixtures/records.json @@ -0,0 +1,28 @@ +[ + { + "id": 789, + "domain": { + "href": "/v1/domain/132/" + }, + "name": "_acme-challenge", + "type": "TXT", + "value": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "ttl": 120, + "annotation": "lego" + }, + { + "id": 11619270, + "domain": { + "href": "/v1/domain/118935/" + }, + "type": "A", + "name": "home", + "value": "149.202.90.65", + "priority": null, + "ttl": 300, + "href": "/v1/record/11619270/", + "annotation": "", + "is_user_defined": true, + "is_active": true + } +] diff --git a/providers/dns/alwaysdata/internal/types.go b/providers/dns/alwaysdata/internal/types.go new file mode 100644 index 000000000..b1e66fa5b --- /dev/null +++ b/providers/dns/alwaysdata/internal/types.go @@ -0,0 +1,33 @@ +package internal + +type RecordRequest struct { + ID int64 `json:"id,omitempty"` + DomainID int64 `json:"domain,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Value string `json:"value,omitempty"` + TTL int `json:"ttl,omitempty"` + Annotation string `json:"annotation,omitempty"` + IsUserDefined bool `json:"is_user_defined,omitempty"` + IsActive bool `json:"is_active,omitempty"` +} + +type Record struct { + ID int64 `json:"id,omitempty"` + Domain *Domain `json:"domain,omitempty"` + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` + TTL int `json:"ttl,omitempty"` + Annotation string `json:"annotation,omitempty"` + IsUserDefined bool `json:"is_user_defined,omitempty"` + IsActive bool `json:"is_active,omitempty"` +} + +type Domain struct { + ID int64 `json:"id,omitempty"` + Href string `json:"href,omitempty"` + Name string `json:"name,omitempty"` + IsInternal bool `json:"is_internal,omitempty"` + Annotation string `json:"annotation,omitempty"` +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 38155e164..c5db54109 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/alidns" "github.com/go-acme/lego/v4/providers/dns/aliesa" "github.com/go-acme/lego/v4/providers/dns/allinkl" + "github.com/go-acme/lego/v4/providers/dns/alwaysdata" "github.com/go-acme/lego/v4/providers/dns/anexia" "github.com/go-acme/lego/v4/providers/dns/arvancloud" "github.com/go-acme/lego/v4/providers/dns/auroradns" @@ -199,6 +200,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return aliesa.NewDNSProvider() case "allinkl": return allinkl.NewDNSProvider() + case "alwaysdata": + return alwaysdata.NewDNSProvider() case "anexia": return anexia.NewDNSProvider() case "arvancloud": From 1b634097c13285c9829dc9c16b74a9c86c4ff81e Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 29 Dec 2025 18:33:53 +0100 Subject: [PATCH 256/298] docs: remove email from examples (#2773) --- docs/content/dns/zz_gen_acme-dns.md | 4 ++-- docs/content/dns/zz_gen_active24.md | 2 +- docs/content/dns/zz_gen_alidns.md | 4 ++-- docs/content/dns/zz_gen_aliesa.md | 4 ++-- docs/content/dns/zz_gen_allinkl.md | 2 +- docs/content/dns/zz_gen_alwaysdata.md | 2 +- docs/content/dns/zz_gen_anexia.md | 2 +- docs/content/dns/zz_gen_arvancloud.md | 2 +- docs/content/dns/zz_gen_auroradns.md | 2 +- docs/content/dns/zz_gen_autodns.md | 2 +- docs/content/dns/zz_gen_axelname.md | 2 +- docs/content/dns/zz_gen_azion.md | 2 +- docs/content/dns/zz_gen_azuredns.md | 10 +++++----- docs/content/dns/zz_gen_baiducloud.md | 2 +- docs/content/dns/zz_gen_beget.md | 2 +- docs/content/dns/zz_gen_binarylane.md | 2 +- docs/content/dns/zz_gen_bindman.md | 2 +- docs/content/dns/zz_gen_bluecat.md | 2 +- docs/content/dns/zz_gen_bookmyname.md | 2 +- docs/content/dns/zz_gen_brandit.md | 2 +- docs/content/dns/zz_gen_bunny.md | 2 +- docs/content/dns/zz_gen_checkdomain.md | 2 +- docs/content/dns/zz_gen_civo.md | 2 +- docs/content/dns/zz_gen_clouddns.md | 2 +- docs/content/dns/zz_gen_cloudflare.md | 4 ++-- docs/content/dns/zz_gen_cloudns.md | 2 +- docs/content/dns/zz_gen_cloudru.md | 2 +- docs/content/dns/zz_gen_cloudxns.md | 2 +- docs/content/dns/zz_gen_conoha.md | 2 +- docs/content/dns/zz_gen_conohav3.md | 2 +- docs/content/dns/zz_gen_constellix.md | 2 +- docs/content/dns/zz_gen_corenetworks.md | 2 +- docs/content/dns/zz_gen_cpanel.md | 4 ++-- docs/content/dns/zz_gen_derak.md | 2 +- docs/content/dns/zz_gen_desec.md | 2 +- docs/content/dns/zz_gen_designate.md | 6 +++--- docs/content/dns/zz_gen_digitalocean.md | 2 +- docs/content/dns/zz_gen_directadmin.md | 2 +- docs/content/dns/zz_gen_dnshomede.md | 4 ++-- docs/content/dns/zz_gen_dnsimple.md | 2 +- docs/content/dns/zz_gen_dnsmadeeasy.md | 2 +- docs/content/dns/zz_gen_dnspod.md | 2 +- docs/content/dns/zz_gen_dode.md | 2 +- docs/content/dns/zz_gen_domeneshop.md | 2 +- docs/content/dns/zz_gen_dreamhost.md | 2 +- docs/content/dns/zz_gen_duckdns.md | 2 +- docs/content/dns/zz_gen_dyn.md | 2 +- docs/content/dns/zz_gen_dyndnsfree.md | 2 +- docs/content/dns/zz_gen_dynu.md | 2 +- docs/content/dns/zz_gen_easydns.md | 2 +- docs/content/dns/zz_gen_edgecenter.md | 2 +- docs/content/dns/zz_gen_edgedns.md | 2 +- docs/content/dns/zz_gen_edgeone.md | 2 +- docs/content/dns/zz_gen_efficientip.md | 2 +- docs/content/dns/zz_gen_epik.md | 2 +- docs/content/dns/zz_gen_exec.md | 6 +++--- docs/content/dns/zz_gen_exoscale.md | 2 +- docs/content/dns/zz_gen_f5xc.md | 2 +- docs/content/dns/zz_gen_freemyip.md | 2 +- docs/content/dns/zz_gen_gandi.md | 2 +- docs/content/dns/zz_gen_gandiv5.md | 2 +- docs/content/dns/zz_gen_gcloud.md | 6 +++--- docs/content/dns/zz_gen_gcore.md | 2 +- docs/content/dns/zz_gen_gigahostno.md | 2 +- docs/content/dns/zz_gen_glesys.md | 2 +- docs/content/dns/zz_gen_godaddy.md | 2 +- docs/content/dns/zz_gen_googledomains.md | 2 +- docs/content/dns/zz_gen_gravity.md | 2 +- docs/content/dns/zz_gen_hetzner.md | 2 +- docs/content/dns/zz_gen_hostingde.md | 2 +- docs/content/dns/zz_gen_hostinger.md | 2 +- docs/content/dns/zz_gen_hostingnl.md | 2 +- docs/content/dns/zz_gen_hosttech.md | 2 +- docs/content/dns/zz_gen_httpnet.md | 2 +- docs/content/dns/zz_gen_httpreq.md | 2 +- docs/content/dns/zz_gen_huaweicloud.md | 2 +- docs/content/dns/zz_gen_hurricane.md | 4 ++-- docs/content/dns/zz_gen_hyperone.md | 2 +- docs/content/dns/zz_gen_ibmcloud.md | 2 +- docs/content/dns/zz_gen_iij.md | 2 +- docs/content/dns/zz_gen_iijdpf.md | 2 +- docs/content/dns/zz_gen_infoblox.md | 2 +- docs/content/dns/zz_gen_infomaniak.md | 2 +- docs/content/dns/zz_gen_internetbs.md | 2 +- docs/content/dns/zz_gen_inwx.md | 4 ++-- docs/content/dns/zz_gen_ionos.md | 2 +- docs/content/dns/zz_gen_ionoscloud.md | 2 +- docs/content/dns/zz_gen_ipv64.md | 2 +- docs/content/dns/zz_gen_ispconfig.md | 2 +- docs/content/dns/zz_gen_ispconfigddns.md | 2 +- docs/content/dns/zz_gen_iwantmyname.md | 2 +- docs/content/dns/zz_gen_joker.md | 6 +++--- docs/content/dns/zz_gen_keyhelp.md | 2 +- docs/content/dns/zz_gen_liara.md | 2 +- docs/content/dns/zz_gen_limacity.md | 2 +- docs/content/dns/zz_gen_linode.md | 2 +- docs/content/dns/zz_gen_liquidweb.md | 2 +- docs/content/dns/zz_gen_loopia.md | 2 +- docs/content/dns/zz_gen_luadns.md | 2 +- docs/content/dns/zz_gen_mailinabox.md | 2 +- docs/content/dns/zz_gen_manageengine.md | 2 +- docs/content/dns/zz_gen_manual.md | 4 ++-- docs/content/dns/zz_gen_metaname.md | 2 +- docs/content/dns/zz_gen_metaregistrar.md | 2 +- docs/content/dns/zz_gen_mijnhost.md | 2 +- docs/content/dns/zz_gen_mittwald.md | 2 +- docs/content/dns/zz_gen_myaddr.md | 2 +- docs/content/dns/zz_gen_mydnsjp.md | 2 +- docs/content/dns/zz_gen_mythicbeasts.md | 2 +- docs/content/dns/zz_gen_namecheap.md | 2 +- docs/content/dns/zz_gen_namedotcom.md | 2 +- docs/content/dns/zz_gen_namesilo.md | 2 +- docs/content/dns/zz_gen_nearlyfreespeech.md | 2 +- docs/content/dns/zz_gen_neodigit.md | 2 +- docs/content/dns/zz_gen_netcup.md | 2 +- docs/content/dns/zz_gen_netlify.md | 2 +- docs/content/dns/zz_gen_nicmanager.md | 4 ++-- docs/content/dns/zz_gen_nicru.md | 2 +- docs/content/dns/zz_gen_nifcloud.md | 2 +- docs/content/dns/zz_gen_njalla.md | 2 +- docs/content/dns/zz_gen_nodion.md | 2 +- docs/content/dns/zz_gen_ns1.md | 2 +- docs/content/dns/zz_gen_octenium.md | 2 +- docs/content/dns/zz_gen_oraclecloud.md | 4 ++-- docs/content/dns/zz_gen_otc.md | 2 +- docs/content/dns/zz_gen_ovh.md | 6 +++--- docs/content/dns/zz_gen_pdns.md | 2 +- docs/content/dns/zz_gen_plesk.md | 2 +- docs/content/dns/zz_gen_porkbun.md | 2 +- docs/content/dns/zz_gen_rackspace.md | 2 +- docs/content/dns/zz_gen_rainyun.md | 2 +- docs/content/dns/zz_gen_rcodezero.md | 2 +- docs/content/dns/zz_gen_regfish.md | 2 +- docs/content/dns/zz_gen_regru.md | 2 +- docs/content/dns/zz_gen_rfc2136.md | 4 ++-- docs/content/dns/zz_gen_rimuhosting.md | 2 +- docs/content/dns/zz_gen_route53.md | 2 +- docs/content/dns/zz_gen_safedns.md | 2 +- docs/content/dns/zz_gen_sakuracloud.md | 2 +- docs/content/dns/zz_gen_scaleway.md | 2 +- docs/content/dns/zz_gen_selectel.md | 2 +- docs/content/dns/zz_gen_selectelv2.md | 2 +- docs/content/dns/zz_gen_selfhostde.md | 2 +- docs/content/dns/zz_gen_servercow.md | 2 +- docs/content/dns/zz_gen_shellrent.md | 2 +- docs/content/dns/zz_gen_simply.md | 2 +- docs/content/dns/zz_gen_sonic.md | 2 +- docs/content/dns/zz_gen_spaceship.md | 2 +- docs/content/dns/zz_gen_stackpath.md | 2 +- docs/content/dns/zz_gen_syse.md | 4 ++-- docs/content/dns/zz_gen_technitium.md | 2 +- docs/content/dns/zz_gen_tencentcloud.md | 2 +- docs/content/dns/zz_gen_timewebcloud.md | 2 +- docs/content/dns/zz_gen_transip.md | 2 +- docs/content/dns/zz_gen_ultradns.md | 2 +- docs/content/dns/zz_gen_uniteddomains.md | 2 +- docs/content/dns/zz_gen_variomedia.md | 2 +- docs/content/dns/zz_gen_vercel.md | 2 +- docs/content/dns/zz_gen_versio.md | 2 +- docs/content/dns/zz_gen_vinyldns.md | 2 +- docs/content/dns/zz_gen_virtualname.md | 2 +- docs/content/dns/zz_gen_vkcloud.md | 2 +- docs/content/dns/zz_gen_volcengine.md | 2 +- docs/content/dns/zz_gen_vscale.md | 2 +- docs/content/dns/zz_gen_vultr.md | 2 +- docs/content/dns/zz_gen_webnames.md | 2 +- docs/content/dns/zz_gen_webnamesca.md | 2 +- docs/content/dns/zz_gen_websupport.md | 2 +- docs/content/dns/zz_gen_wedos.md | 2 +- docs/content/dns/zz_gen_westcn.md | 2 +- docs/content/dns/zz_gen_yandex.md | 2 +- docs/content/dns/zz_gen_yandex360.md | 2 +- docs/content/dns/zz_gen_yandexcloud.md | 4 ++-- docs/content/dns/zz_gen_zoneedit.md | 2 +- docs/content/dns/zz_gen_zoneee.md | 2 +- docs/content/dns/zz_gen_zonomi.md | 2 +- providers/dns/acmedns/acmedns.toml | 4 ++-- providers/dns/active24/active24.toml | 2 +- providers/dns/alidns/alidns.toml | 4 ++-- providers/dns/aliesa/aliesa.toml | 4 ++-- providers/dns/allinkl/allinkl.toml | 2 +- providers/dns/alwaysdata/alwaysdata.toml | 2 +- providers/dns/anexia/anexia.toml | 2 +- providers/dns/arvancloud/arvancloud.toml | 2 +- providers/dns/auroradns/auroradns.toml | 2 +- providers/dns/autodns/autodns.toml | 2 +- providers/dns/axelname/axelname.toml | 2 +- providers/dns/azion/azion.toml | 2 +- providers/dns/azuredns/azuredns.toml | 10 +++++----- providers/dns/baiducloud/baiducloud.toml | 2 +- providers/dns/beget/beget.toml | 2 +- providers/dns/binarylane/binarylane.toml | 2 +- providers/dns/bindman/bindman.toml | 2 +- providers/dns/bluecat/bluecat.toml | 2 +- providers/dns/bookmyname/bookmyname.toml | 2 +- providers/dns/brandit/brandit.toml | 2 +- providers/dns/bunny/bunny.toml | 2 +- providers/dns/checkdomain/checkdomain.toml | 2 +- providers/dns/civo/civo.toml | 2 +- providers/dns/clouddns/clouddns.toml | 2 +- providers/dns/cloudflare/cloudflare.toml | 4 ++-- providers/dns/cloudns/cloudns.toml | 2 +- providers/dns/cloudru/cloudru.toml | 2 +- providers/dns/cloudxns/cloudxns.toml | 2 +- providers/dns/conoha/conoha.toml | 2 +- providers/dns/conohav3/conohav3.toml | 2 +- providers/dns/constellix/constellix.toml | 2 +- providers/dns/corenetworks/corenetworks.toml | 2 +- providers/dns/cpanel/cpanel.toml | 4 ++-- providers/dns/derak/derak.toml | 2 +- providers/dns/desec/desec.toml | 2 +- providers/dns/designate/designate.toml | 6 +++--- providers/dns/digitalocean/digitalocean.toml | 2 +- providers/dns/directadmin/directadmin.toml | 2 +- providers/dns/dnshomede/dnshomede.toml | 4 ++-- providers/dns/dnsimple/dnsimple.toml | 2 +- providers/dns/dnsmadeeasy/dnsmadeeasy.toml | 2 +- providers/dns/dnspod/dnspod.toml | 2 +- providers/dns/dode/dode.toml | 2 +- providers/dns/domeneshop/domeneshop.toml | 2 +- providers/dns/dreamhost/dreamhost.toml | 2 +- providers/dns/duckdns/duckdns.toml | 2 +- providers/dns/dyn/dyn.toml | 2 +- providers/dns/dyndnsfree/dyndnsfree.toml | 2 +- providers/dns/dynu/dynu.toml | 2 +- providers/dns/easydns/easydns.toml | 2 +- providers/dns/edgecenter/edgecenter.toml | 2 +- providers/dns/edgedns/edgedns.toml | 2 +- providers/dns/edgeone/edgeone.toml | 2 +- providers/dns/efficientip/efficientip.toml | 2 +- providers/dns/epik/epik.toml | 2 +- providers/dns/exec/exec.toml | 6 +++--- providers/dns/exoscale/exoscale.toml | 2 +- providers/dns/f5xc/f5xc.toml | 2 +- providers/dns/freemyip/freemyip.toml | 2 +- providers/dns/gandi/gandi.toml | 2 +- providers/dns/gandiv5/gandiv5.toml | 2 +- providers/dns/gcloud/gcloud.toml | 6 +++--- providers/dns/gcore/gcore.toml | 2 +- providers/dns/gigahostno/gigahostno.toml | 2 +- providers/dns/glesys/glesys.toml | 2 +- providers/dns/godaddy/godaddy.toml | 2 +- providers/dns/googledomains/googledomains.toml | 2 +- providers/dns/gravity/gravity.toml | 2 +- providers/dns/hetzner/hetzner.toml | 2 +- providers/dns/hostingde/hostingde.toml | 2 +- providers/dns/hostinger/hostinger.toml | 2 +- providers/dns/hostingnl/hostingnl.toml | 2 +- providers/dns/hosttech/hosttech.toml | 2 +- providers/dns/httpnet/httpnet.toml | 2 +- providers/dns/httpreq/httpreq.toml | 2 +- providers/dns/huaweicloud/huaweicloud.toml | 2 +- providers/dns/hurricane/hurricane.toml | 4 ++-- providers/dns/hyperone/hyperone.toml | 2 +- providers/dns/ibmcloud/ibmcloud.toml | 2 +- providers/dns/iij/iij.toml | 2 +- providers/dns/iijdpf/iijdpf.toml | 2 +- providers/dns/infoblox/infoblox.toml | 2 +- providers/dns/infomaniak/infomaniak.toml | 2 +- providers/dns/internetbs/internetbs.toml | 2 +- providers/dns/inwx/inwx.toml | 4 ++-- providers/dns/ionos/ionos.toml | 2 +- providers/dns/ionoscloud/ionoscloud.toml | 2 +- providers/dns/ipv64/ipv64.toml | 2 +- providers/dns/ispconfig/ispconfig.toml | 2 +- providers/dns/ispconfigddns/ispconfigddns.toml | 2 +- providers/dns/iwantmyname/iwantmyname.toml | 2 +- providers/dns/joker/joker.toml | 6 +++--- providers/dns/keyhelp/keyhelp.toml | 2 +- providers/dns/liara/liara.toml | 2 +- providers/dns/limacity/limacity.toml | 2 +- providers/dns/linode/linode.toml | 2 +- providers/dns/liquidweb/liquidweb.toml | 2 +- providers/dns/loopia/loopia.toml | 2 +- providers/dns/luadns/luadns.toml | 2 +- providers/dns/mailinabox/mailinabox.toml | 2 +- providers/dns/manageengine/manageengine.toml | 2 +- providers/dns/manual/manual.toml | 4 ++-- providers/dns/metaname/metaname.toml | 2 +- providers/dns/metaregistrar/metaregistrar.toml | 2 +- providers/dns/mijnhost/mijnhost.toml | 2 +- providers/dns/mittwald/mittwald.toml | 2 +- providers/dns/myaddr/myaddr.toml | 2 +- providers/dns/mydnsjp/mydnsjp.toml | 2 +- providers/dns/mythicbeasts/mythicbeasts.toml | 2 +- providers/dns/namecheap/namecheap.toml | 2 +- providers/dns/namedotcom/namedotcom.toml | 2 +- providers/dns/namesilo/namesilo.toml | 2 +- providers/dns/nearlyfreespeech/nearlyfreespeech.toml | 2 +- providers/dns/neodigit/neodigit.toml | 2 +- providers/dns/netcup/netcup.toml | 2 +- providers/dns/netlify/netlify.toml | 2 +- providers/dns/nicmanager/nicmanager.toml | 4 ++-- providers/dns/nicru/nicru.toml | 2 +- providers/dns/nifcloud/nifcloud.toml | 2 +- providers/dns/njalla/njalla.toml | 2 +- providers/dns/nodion/nodion.toml | 2 +- providers/dns/ns1/ns1.toml | 2 +- providers/dns/octenium/octenium.toml | 2 +- providers/dns/oraclecloud/oraclecloud.toml | 4 ++-- providers/dns/otc/otc.toml | 2 +- providers/dns/ovh/ovh.toml | 6 +++--- providers/dns/pdns/pdns.toml | 2 +- providers/dns/plesk/plesk.toml | 2 +- providers/dns/porkbun/porkbun.toml | 2 +- providers/dns/rackspace/rackspace.toml | 2 +- providers/dns/rainyun/rainyun.toml | 2 +- providers/dns/rcodezero/rcodezero.toml | 2 +- providers/dns/regfish/regfish.toml | 2 +- providers/dns/regru/regru.toml | 2 +- providers/dns/rfc2136/rfc2136.toml | 4 ++-- providers/dns/rimuhosting/rimuhosting.toml | 2 +- providers/dns/route53/route53.toml | 2 +- providers/dns/safedns/safedns.toml | 2 +- providers/dns/sakuracloud/sakuracloud.toml | 2 +- providers/dns/scaleway/scaleway.toml | 2 +- providers/dns/selectel/selectel.toml | 2 +- providers/dns/selectelv2/selectelv2.toml | 2 +- providers/dns/selfhostde/selfhostde.toml | 2 +- providers/dns/servercow/servercow.toml | 2 +- providers/dns/shellrent/shellrent.toml | 2 +- providers/dns/simply/simply.toml | 2 +- providers/dns/sonic/sonic.toml | 2 +- providers/dns/spaceship/spaceship.toml | 2 +- providers/dns/stackpath/stackpath.toml | 2 +- providers/dns/syse/syse.toml | 4 ++-- providers/dns/technitium/technitium.toml | 2 +- providers/dns/tencentcloud/tencentcloud.toml | 2 +- providers/dns/timewebcloud/timewebcloud.toml | 2 +- providers/dns/transip/transip.toml | 2 +- providers/dns/ultradns/ultradns.toml | 2 +- providers/dns/uniteddomains/uniteddomains.toml | 2 +- providers/dns/variomedia/variomedia.toml | 2 +- providers/dns/vercel/vercel.toml | 2 +- providers/dns/versio/versio.toml | 2 +- providers/dns/vinyldns/vinyldns.toml | 2 +- providers/dns/virtualname/virtualname.toml | 2 +- providers/dns/vkcloud/vkcloud.toml | 2 +- providers/dns/volcengine/volcengine.toml | 2 +- providers/dns/vscale/vscale.toml | 2 +- providers/dns/vultr/vultr.toml | 2 +- providers/dns/webnames/webnames.toml | 2 +- providers/dns/webnamesca/webnamesca.toml | 2 +- providers/dns/websupport/websupport.toml | 2 +- providers/dns/wedos/wedos.toml | 2 +- providers/dns/westcn/westcn.toml | 2 +- providers/dns/yandex/yandex.toml | 2 +- providers/dns/yandex360/yandex360.toml | 2 +- providers/dns/yandexcloud/yandexcloud.toml | 4 ++-- providers/dns/zoneedit/zoneedit.toml | 2 +- providers/dns/zoneee/zoneee.toml | 2 +- providers/dns/zonomi/zonomi.toml | 2 +- 352 files changed, 408 insertions(+), 408 deletions(-) diff --git a/docs/content/dns/zz_gen_acme-dns.md b/docs/content/dns/zz_gen_acme-dns.md index cb3d24016..5564dba1b 100644 --- a/docs/content/dns/zz_gen_acme-dns.md +++ b/docs/content/dns/zz_gen_acme-dns.md @@ -28,13 +28,13 @@ Here is an example bash command using the Joohoi's ACME-DNS provider: ```bash ACME_DNS_API_BASE=http://10.0.0.8:4443 \ ACME_DNS_STORAGE_PATH=/root/.lego-acme-dns-accounts.json \ -lego --email you@example.com --dns "acme-dns" -d '*.example.com' -d example.com run +lego --dns "acme-dns" -d '*.example.com' -d example.com run # or ACME_DNS_API_BASE=http://10.0.0.8:4443 \ ACME_DNS_STORAGE_BASE_URL=http://10.10.10.10:80 \ -lego --email you@example.com --dns "acme-dns" -d '*.example.com' -d example.com run +lego --dns "acme-dns" -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_active24.md b/docs/content/dns/zz_gen_active24.md index cadc6660c..6ec5c467a 100644 --- a/docs/content/dns/zz_gen_active24.md +++ b/docs/content/dns/zz_gen_active24.md @@ -28,7 +28,7 @@ Here is an example bash command using the Active24 provider: ```bash ACTIVE24_API_KEY="xxx" \ ACTIVE24_SECRET="yyy" \ -lego --email you@example.com --dns active24 -d '*.example.com' -d example.com run +lego --dns active24 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_alidns.md b/docs/content/dns/zz_gen_alidns.md index 7a7a36e8a..e498a31dd 100644 --- a/docs/content/dns/zz_gen_alidns.md +++ b/docs/content/dns/zz_gen_alidns.md @@ -28,13 +28,13 @@ Here is an example bash command using the Alibaba Cloud DNS provider: ```bash # Setup using instance RAM role ALICLOUD_RAM_ROLE=lego \ -lego --email you@example.com --dns alidns -d '*.example.com' -d example.com run +lego --dns alidns -d '*.example.com' -d example.com run # Or, using credentials ALICLOUD_ACCESS_KEY=abcdefghijklmnopqrstuvwx \ ALICLOUD_SECRET_KEY=your-secret-key \ ALICLOUD_SECURITY_TOKEN=your-sts-token \ -lego --email you@example.com --dns alidns - -d '*.example.com' -d example.com run +lego --dns alidns - -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_aliesa.md b/docs/content/dns/zz_gen_aliesa.md index b286a718a..af28f9a4e 100644 --- a/docs/content/dns/zz_gen_aliesa.md +++ b/docs/content/dns/zz_gen_aliesa.md @@ -28,13 +28,13 @@ Here is an example bash command using the AlibabaCloud ESA provider: ```bash # Setup using instance RAM role ALIESA_RAM_ROLE=lego \ -lego --email you@example.com --dns aliesa -d '*.example.com' -d example.com run +lego --dns aliesa -d '*.example.com' -d example.com run # Or, using credentials ALIESA_ACCESS_KEY=abcdefghijklmnopqrstuvwx \ ALIESA_SECRET_KEY=your-secret-key \ ALIESA_SECURITY_TOKEN=your-sts-token \ -lego --email you@example.com --dns aliesa - -d '*.example.com' -d example.com run +lego --dns aliesa - -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_allinkl.md b/docs/content/dns/zz_gen_allinkl.md index 2415c812f..2db6ae2c5 100644 --- a/docs/content/dns/zz_gen_allinkl.md +++ b/docs/content/dns/zz_gen_allinkl.md @@ -28,7 +28,7 @@ Here is an example bash command using the all-inkl provider: ```bash ALL_INKL_LOGIN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ ALL_INKL_PASSWORD=yyyyyyyyyyyyyyyyyyyyyyyyyy \ -lego --email you@example.com --dns allinkl -d '*.example.com' -d example.com run +lego --dns allinkl -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_alwaysdata.md b/docs/content/dns/zz_gen_alwaysdata.md index 75f3cb859..6ec332d16 100644 --- a/docs/content/dns/zz_gen_alwaysdata.md +++ b/docs/content/dns/zz_gen_alwaysdata.md @@ -27,7 +27,7 @@ Here is an example bash command using the Alwaysdata provider: ```bash ALWAYSDATA_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns alwaysdata -d '*.example.com' -d example.com run +lego --dns alwaysdata -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_anexia.md b/docs/content/dns/zz_gen_anexia.md index 4256d957c..e12ec7cfd 100644 --- a/docs/content/dns/zz_gen_anexia.md +++ b/docs/content/dns/zz_gen_anexia.md @@ -27,7 +27,7 @@ Here is an example bash command using the Anexia CloudDNS provider: ```bash ANEXIA_TOKEN=xxx \ -lego --email you@example.com --dns anexia -d '*.example.com' -d example.com run +lego --dns anexia -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_arvancloud.md b/docs/content/dns/zz_gen_arvancloud.md index b9fa1af8d..96d495f71 100644 --- a/docs/content/dns/zz_gen_arvancloud.md +++ b/docs/content/dns/zz_gen_arvancloud.md @@ -27,7 +27,7 @@ Here is an example bash command using the ArvanCloud provider: ```bash ARVANCLOUD_API_KEY="Apikey xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \ -lego --email you@example.com --dns arvancloud -d '*.example.com' -d example.com run +lego --dns arvancloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_auroradns.md b/docs/content/dns/zz_gen_auroradns.md index 9fffe34bc..d608c85bb 100644 --- a/docs/content/dns/zz_gen_auroradns.md +++ b/docs/content/dns/zz_gen_auroradns.md @@ -28,7 +28,7 @@ Here is an example bash command using the Aurora DNS provider: ```bash AURORA_API_KEY=xxxxx \ AURORA_SECRET=yyyyyy \ -lego --email you@example.com --dns auroradns -d '*.example.com' -d example.com run +lego --dns auroradns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_autodns.md b/docs/content/dns/zz_gen_autodns.md index 73f41b980..f1f25e916 100644 --- a/docs/content/dns/zz_gen_autodns.md +++ b/docs/content/dns/zz_gen_autodns.md @@ -28,7 +28,7 @@ Here is an example bash command using the Autodns provider: ```bash AUTODNS_API_USER=username \ AUTODNS_API_PASSWORD=supersecretpassword \ -lego --email you@example.com --dns autodns -d '*.example.com' -d example.com run +lego --dns autodns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_axelname.md b/docs/content/dns/zz_gen_axelname.md index b1bb3e166..91476e521 100644 --- a/docs/content/dns/zz_gen_axelname.md +++ b/docs/content/dns/zz_gen_axelname.md @@ -28,7 +28,7 @@ Here is an example bash command using the Axelname provider: ```bash AXELNAME_NICKNAME="yyy" \ AXELNAME_TOKEN="xxx" \ -lego --email you@example.com --dns axelname -d '*.example.com' -d example.com run +lego --dns axelname -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_azion.md b/docs/content/dns/zz_gen_azion.md index af2a281b0..c5ca33552 100644 --- a/docs/content/dns/zz_gen_azion.md +++ b/docs/content/dns/zz_gen_azion.md @@ -27,7 +27,7 @@ Here is an example bash command using the Azion provider: ```bash AZION_PERSONAL_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns azion -d '*.example.com' -d example.com run +lego --dns azion -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_azuredns.md b/docs/content/dns/zz_gen_azuredns.md index 85feaae88..3b2586711 100644 --- a/docs/content/dns/zz_gen_azuredns.md +++ b/docs/content/dns/zz_gen_azuredns.md @@ -31,32 +31,32 @@ Here is an example bash command using the Azure DNS provider: AZURE_CLIENT_ID= \ AZURE_TENANT_ID= \ AZURE_CLIENT_SECRET= \ -lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run +lego --dns azuredns -d '*.example.com' -d example.com run ### Using client certificate AZURE_CLIENT_ID= \ AZURE_TENANT_ID= \ AZURE_CLIENT_CERTIFICATE_PATH= \ -lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run +lego --dns azuredns -d '*.example.com' -d example.com run ### Using Azure CLI az login \ -lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run +lego --dns azuredns -d '*.example.com' -d example.com run ### Using Managed Identity (Azure VM) AZURE_TENANT_ID= \ AZURE_RESOURCE_GROUP= \ -lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run +lego --dns azuredns -d '*.example.com' -d example.com run ### Using Managed Identity (Azure Arc) AZURE_TENANT_ID= \ IMDS_ENDPOINT=http://localhost:40342 \ IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token \ -lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run +lego --dns azuredns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_baiducloud.md b/docs/content/dns/zz_gen_baiducloud.md index 9f59aa156..59a2f9a2d 100644 --- a/docs/content/dns/zz_gen_baiducloud.md +++ b/docs/content/dns/zz_gen_baiducloud.md @@ -28,7 +28,7 @@ Here is an example bash command using the Baidu Cloud provider: ```bash BAIDUCLOUD_ACCESS_KEY_ID="xxx" \ BAIDUCLOUD_SECRET_ACCESS_KEY="yyy" \ -lego --email you@example.com --dns baiducloud -d '*.example.com' -d example.com run +lego --dns baiducloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_beget.md b/docs/content/dns/zz_gen_beget.md index ae1d16a7c..3f03a2ac5 100644 --- a/docs/content/dns/zz_gen_beget.md +++ b/docs/content/dns/zz_gen_beget.md @@ -28,7 +28,7 @@ Here is an example bash command using the Beget.com provider: ```bash BEGET_USERNAME=xxxxxx \ BEGET_PASSWORD=yyyyyy \ -lego --email you@example.com --dns beget -d '*.example.com' -d example.com run +lego --dns beget -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_binarylane.md b/docs/content/dns/zz_gen_binarylane.md index 4d65bb0bc..eebf3c54e 100644 --- a/docs/content/dns/zz_gen_binarylane.md +++ b/docs/content/dns/zz_gen_binarylane.md @@ -27,7 +27,7 @@ Here is an example bash command using the Binary Lane provider: ```bash BINARYLANE_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns binarylane -d '*.example.com' -d example.com run +lego --dns binarylane -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_bindman.md b/docs/content/dns/zz_gen_bindman.md index e12f25b7a..fcceb8962 100644 --- a/docs/content/dns/zz_gen_bindman.md +++ b/docs/content/dns/zz_gen_bindman.md @@ -27,7 +27,7 @@ Here is an example bash command using the Bindman provider: ```bash BINDMAN_MANAGER_ADDRESS= \ -lego --email you@example.com --dns bindman -d '*.example.com' -d example.com run +lego --dns bindman -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_bluecat.md b/docs/content/dns/zz_gen_bluecat.md index ee45c7f8b..2d9eb5b48 100644 --- a/docs/content/dns/zz_gen_bluecat.md +++ b/docs/content/dns/zz_gen_bluecat.md @@ -32,7 +32,7 @@ BLUECAT_USER_NAME=myusername \ BLUECAT_CONFIG_NAME=myconfig \ BLUECAT_SERVER_URL=https://bam.example.com \ BLUECAT_TTL=30 \ -lego --email you@example.com --dns bluecat -d '*.example.com' -d example.com run +lego --dns bluecat -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_bookmyname.md b/docs/content/dns/zz_gen_bookmyname.md index 3f5d1f2c3..cb7e1d3a1 100644 --- a/docs/content/dns/zz_gen_bookmyname.md +++ b/docs/content/dns/zz_gen_bookmyname.md @@ -28,7 +28,7 @@ Here is an example bash command using the BookMyName provider: ```bash BOOKMYNAME_USERNAME="xxx" \ BOOKMYNAME_PASSWORD="yyy" \ -lego --email you@example.com --dns bookmyname -d '*.example.com' -d example.com run +lego --dns bookmyname -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_brandit.md b/docs/content/dns/zz_gen_brandit.md index 5d1f91214..fdb538684 100644 --- a/docs/content/dns/zz_gen_brandit.md +++ b/docs/content/dns/zz_gen_brandit.md @@ -31,7 +31,7 @@ Here is an example bash command using the Brandit (deprecated) provider: ```bash BRANDIT_API_KEY=xxxxxxxxxxxxxxxxxxxxx \ BRANDIT_API_USERNAME=yyyyyyyyyyyyyyyyyyyy \ -lego --email you@example.com --dns brandit -d '*.example.com' -d example.com run +lego --dns brandit -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_bunny.md b/docs/content/dns/zz_gen_bunny.md index 884c61aea..63c30782a 100644 --- a/docs/content/dns/zz_gen_bunny.md +++ b/docs/content/dns/zz_gen_bunny.md @@ -27,7 +27,7 @@ Here is an example bash command using the Bunny provider: ```bash BUNNY_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ -lego --email you@example.com --dns bunny -d '*.example.com' -d example.com run +lego --dns bunny -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_checkdomain.md b/docs/content/dns/zz_gen_checkdomain.md index 516d87880..e0275f6c9 100644 --- a/docs/content/dns/zz_gen_checkdomain.md +++ b/docs/content/dns/zz_gen_checkdomain.md @@ -27,7 +27,7 @@ Here is an example bash command using the Checkdomain provider: ```bash CHECKDOMAIN_TOKEN=yoursecrettoken \ -lego --email you@example.com --dns checkdomain -d '*.example.com' -d example.com run +lego --dns checkdomain -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_civo.md b/docs/content/dns/zz_gen_civo.md index a2cffe27c..61303b539 100644 --- a/docs/content/dns/zz_gen_civo.md +++ b/docs/content/dns/zz_gen_civo.md @@ -27,7 +27,7 @@ Here is an example bash command using the Civo provider: ```bash CIVO_TOKEN=xxxxxx \ -lego --email you@example.com --dns civo -d '*.example.com' -d example.com run +lego --dns civo -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_clouddns.md b/docs/content/dns/zz_gen_clouddns.md index 27a254873..d10d1d6a1 100644 --- a/docs/content/dns/zz_gen_clouddns.md +++ b/docs/content/dns/zz_gen_clouddns.md @@ -29,7 +29,7 @@ Here is an example bash command using the CloudDNS provider: CLOUDDNS_CLIENT_ID=bLsdFAks23429841238feb177a572aX \ CLOUDDNS_EMAIL=you@example.com \ CLOUDDNS_PASSWORD=b9841238feb177a84330f \ -lego --email you@example.com --dns clouddns -d '*.example.com' -d example.com run +lego --dns clouddns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_cloudflare.md b/docs/content/dns/zz_gen_cloudflare.md index 0fd1d440e..f3390a5fd 100644 --- a/docs/content/dns/zz_gen_cloudflare.md +++ b/docs/content/dns/zz_gen_cloudflare.md @@ -28,12 +28,12 @@ Here is an example bash command using the Cloudflare provider: ```bash CLOUDFLARE_EMAIL=you@example.com \ CLOUDFLARE_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \ -lego --email you@example.com --dns cloudflare -d '*.example.com' -d example.com run +lego --dns cloudflare -d '*.example.com' -d example.com run # or CLOUDFLARE_DNS_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \ -lego --email you@example.com --dns cloudflare -d '*.example.com' -d example.com run +lego --dns cloudflare -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_cloudns.md b/docs/content/dns/zz_gen_cloudns.md index 01d4b7815..26bd838f2 100644 --- a/docs/content/dns/zz_gen_cloudns.md +++ b/docs/content/dns/zz_gen_cloudns.md @@ -28,7 +28,7 @@ Here is an example bash command using the ClouDNS provider: ```bash CLOUDNS_AUTH_ID=xxxx \ CLOUDNS_AUTH_PASSWORD=yyyy \ -lego --email you@example.com --dns cloudns -d '*.example.com' -d example.com run +lego --dns cloudns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_cloudru.md b/docs/content/dns/zz_gen_cloudru.md index 52190b031..6dc3b0030 100644 --- a/docs/content/dns/zz_gen_cloudru.md +++ b/docs/content/dns/zz_gen_cloudru.md @@ -29,7 +29,7 @@ Here is an example bash command using the Cloud.ru provider: CLOUDRU_SERVICE_INSTANCE_ID=ppp \ CLOUDRU_KEY_ID=xxx \ CLOUDRU_SECRET=yyy \ -lego --email you@example.com --dns cloudru -d '*.example.com' -d example.com run +lego --dns cloudru -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_cloudxns.md b/docs/content/dns/zz_gen_cloudxns.md index 0b290b693..b26e5ddb5 100644 --- a/docs/content/dns/zz_gen_cloudxns.md +++ b/docs/content/dns/zz_gen_cloudxns.md @@ -28,7 +28,7 @@ Here is an example bash command using the CloudXNS (Deprecated) provider: ```bash CLOUDXNS_API_KEY=xxxx \ CLOUDXNS_SECRET_KEY=yyyy \ -lego --email you@example.com --dns cloudxns -d '*.example.com' -d example.com run +lego --dns cloudxns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_conoha.md b/docs/content/dns/zz_gen_conoha.md index 4d5f84660..08a979b31 100644 --- a/docs/content/dns/zz_gen_conoha.md +++ b/docs/content/dns/zz_gen_conoha.md @@ -29,7 +29,7 @@ Here is an example bash command using the ConoHa v2 provider: CONOHA_TENANT_ID=487727e3921d44e3bfe7ebb337bf085e \ CONOHA_API_USERNAME=xxxx \ CONOHA_API_PASSWORD=yyyy \ -lego --email you@example.com --dns conoha -d '*.example.com' -d example.com run +lego --dns conoha -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_conohav3.md b/docs/content/dns/zz_gen_conohav3.md index 208f2f91b..e473f9434 100644 --- a/docs/content/dns/zz_gen_conohav3.md +++ b/docs/content/dns/zz_gen_conohav3.md @@ -29,7 +29,7 @@ Here is an example bash command using the ConoHa v3 provider: CONOHAV3_TENANT_ID=487727e3921d44e3bfe7ebb337bf085e \ CONOHAV3_API_USER_ID=xxxx \ CONOHAV3_API_PASSWORD=yyyy \ -lego --email you@example.com --dns conohav3 -d '*.example.com' -d example.com run +lego --dns conohav3 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_constellix.md b/docs/content/dns/zz_gen_constellix.md index 23628e001..d4ce02bac 100644 --- a/docs/content/dns/zz_gen_constellix.md +++ b/docs/content/dns/zz_gen_constellix.md @@ -28,7 +28,7 @@ Here is an example bash command using the Constellix provider: ```bash CONSTELLIX_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ CONSTELLIX_SECRET_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ -lego --email you@example.com --dns constellix -d '*.example.com' -d example.com run +lego --dns constellix -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_corenetworks.md b/docs/content/dns/zz_gen_corenetworks.md index dc756647e..05468b1a3 100644 --- a/docs/content/dns/zz_gen_corenetworks.md +++ b/docs/content/dns/zz_gen_corenetworks.md @@ -28,7 +28,7 @@ Here is an example bash command using the Core-Networks provider: ```bash CORENETWORKS_LOGIN="xxxx" \ CORENETWORKS_PASSWORD="yyyy" \ -lego --email you@example.com --dns corenetworks -d '*.example.com' -d example.com run +lego --dns corenetworks -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_cpanel.md b/docs/content/dns/zz_gen_cpanel.md index 48cb229e7..e5c0cc047 100644 --- a/docs/content/dns/zz_gen_cpanel.md +++ b/docs/content/dns/zz_gen_cpanel.md @@ -31,7 +31,7 @@ Here is an example bash command using the CPanel/WHM provider: CPANEL_USERNAME="yyyy" \ CPANEL_TOKEN="xxxx" \ CPANEL_BASE_URL="https://example.com:2083" \ -lego --email you@example.com --dns cpanel -d '*.example.com' -d example.com run +lego --dns cpanel -d '*.example.com' -d example.com run ## WHM @@ -39,7 +39,7 @@ CPANEL_MODE=whm \ CPANEL_USERNAME="yyyy" \ CPANEL_TOKEN="xxxx" \ CPANEL_BASE_URL="https://example.com:2087" \ -lego --email you@example.com --dns cpanel -d '*.example.com' -d example.com run +lego --dns cpanel -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_derak.md b/docs/content/dns/zz_gen_derak.md index fedbf4683..c5c8c7bc6 100644 --- a/docs/content/dns/zz_gen_derak.md +++ b/docs/content/dns/zz_gen_derak.md @@ -27,7 +27,7 @@ Here is an example bash command using the Derak Cloud provider: ```bash DERAK_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns derak -d '*.example.com' -d example.com run +lego --dns derak -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_desec.md b/docs/content/dns/zz_gen_desec.md index 977a00e06..4dbc713d6 100644 --- a/docs/content/dns/zz_gen_desec.md +++ b/docs/content/dns/zz_gen_desec.md @@ -27,7 +27,7 @@ Here is an example bash command using the deSEC.io provider: ```bash DESEC_TOKEN=x-xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns desec -d '*.example.com' -d example.com run +lego --dns desec -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_designate.md b/docs/content/dns/zz_gen_designate.md index 74cd04920..9703f094d 100644 --- a/docs/content/dns/zz_gen_designate.md +++ b/docs/content/dns/zz_gen_designate.md @@ -28,7 +28,7 @@ Here is an example bash command using the Designate DNSaaS for Openstack provide ```bash # With a `clouds.yaml` OS_CLOUD=my_openstack \ -lego --email you@example.com --dns designate -d '*.example.com' -d example.com run +lego --dns designate -d '*.example.com' -d example.com run # or @@ -37,7 +37,7 @@ OS_REGION_NAME=RegionOne \ OS_PROJECT_ID=23d4522a987d4ab529f722a007c27846 OS_USERNAME=myuser \ OS_PASSWORD=passw0rd \ -lego --email you@example.com --dns designate -d '*.example.com' -d example.com run +lego --dns designate -d '*.example.com' -d example.com run # or @@ -46,7 +46,7 @@ OS_REGION_NAME=RegionOne \ OS_AUTH_TYPE=v3applicationcredential \ OS_APPLICATION_CREDENTIAL_ID=imn74uq0or7dyzz20dwo1ytls4me8dry \ OS_APPLICATION_CREDENTIAL_SECRET=68FuSPSdQqkFQYH5X1OoriEIJOwyLtQ8QSqXZOc9XxFK1A9tzZT6He2PfPw0OMja \ -lego --email you@example.com --dns designate -d '*.example.com' -d example.com run +lego --dns designate -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_digitalocean.md b/docs/content/dns/zz_gen_digitalocean.md index 24307cfb0..4dc43886d 100644 --- a/docs/content/dns/zz_gen_digitalocean.md +++ b/docs/content/dns/zz_gen_digitalocean.md @@ -27,7 +27,7 @@ Here is an example bash command using the Digital Ocean provider: ```bash DO_AUTH_TOKEN=xxxxxx \ -lego --email you@example.com --dns digitalocean -d '*.example.com' -d example.com run +lego --dns digitalocean -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_directadmin.md b/docs/content/dns/zz_gen_directadmin.md index 006cb87d6..1d03dcc4e 100644 --- a/docs/content/dns/zz_gen_directadmin.md +++ b/docs/content/dns/zz_gen_directadmin.md @@ -29,7 +29,7 @@ Here is an example bash command using the DirectAdmin provider: DIRECTADMIN_API_URL="http://example.com:2222" \ DIRECTADMIN_USERNAME=xxxx \ DIRECTADMIN_PASSWORD=yyy \ -lego --email you@example.com --dns directadmin -d '*.example.com' -d example.com run +lego --dns directadmin -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_dnshomede.md b/docs/content/dns/zz_gen_dnshomede.md index b865578e6..ca7f83523 100644 --- a/docs/content/dns/zz_gen_dnshomede.md +++ b/docs/content/dns/zz_gen_dnshomede.md @@ -27,10 +27,10 @@ Here is an example bash command using the dnsHome.de provider: ```bash DNSHOMEDE_CREDENTIALS=example.org:password \ -lego --email you@example.com --dns dnshomede -d '*.example.com' -d example.com run +lego --dns dnshomede -d '*.example.com' -d example.com run DNSHOMEDE_CREDENTIALS=my.example.org:password1,demo.example.org:password2 \ -lego --email you@example.com --dns dnshomede -d my.example.org -d demo.example.org +lego --dns dnshomede -d my.example.org -d demo.example.org ``` diff --git a/docs/content/dns/zz_gen_dnsimple.md b/docs/content/dns/zz_gen_dnsimple.md index d73122273..7799ece88 100644 --- a/docs/content/dns/zz_gen_dnsimple.md +++ b/docs/content/dns/zz_gen_dnsimple.md @@ -27,7 +27,7 @@ Here is an example bash command using the DNSimple provider: ```bash DNSIMPLE_OAUTH_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \ -lego --email you@example.com --dns dnsimple -d '*.example.com' -d example.com run +lego --dns dnsimple -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_dnsmadeeasy.md b/docs/content/dns/zz_gen_dnsmadeeasy.md index 572676fbd..e7f260889 100644 --- a/docs/content/dns/zz_gen_dnsmadeeasy.md +++ b/docs/content/dns/zz_gen_dnsmadeeasy.md @@ -28,7 +28,7 @@ Here is an example bash command using the DNS Made Easy provider: ```bash DNSMADEEASY_API_KEY=xxxxxx \ DNSMADEEASY_API_SECRET=yyyyy \ -lego --email you@example.com --dns dnsmadeeasy -d '*.example.com' -d example.com run +lego --dns dnsmadeeasy -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_dnspod.md b/docs/content/dns/zz_gen_dnspod.md index b9e906052..86112a5ce 100644 --- a/docs/content/dns/zz_gen_dnspod.md +++ b/docs/content/dns/zz_gen_dnspod.md @@ -27,7 +27,7 @@ Here is an example bash command using the DNSPod (deprecated) provider: ```bash DNSPOD_API_KEY=xxxxxx \ -lego --email you@example.com --dns dnspod -d '*.example.com' -d example.com run +lego --dns dnspod -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_dode.md b/docs/content/dns/zz_gen_dode.md index 153650406..28eebe5fa 100644 --- a/docs/content/dns/zz_gen_dode.md +++ b/docs/content/dns/zz_gen_dode.md @@ -27,7 +27,7 @@ Here is an example bash command using the Domain Offensive (do.de) provider: ```bash DODE_TOKEN=xxxxxx \ -lego --email you@example.com --dns dode -d '*.example.com' -d example.com run +lego --dns dode -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_domeneshop.md b/docs/content/dns/zz_gen_domeneshop.md index a519cfbef..0530ab365 100644 --- a/docs/content/dns/zz_gen_domeneshop.md +++ b/docs/content/dns/zz_gen_domeneshop.md @@ -28,7 +28,7 @@ Here is an example bash command using the Domeneshop provider: ```bash DOMENESHOP_API_TOKEN= \ DOMENESHOP_API_SECRET= \ -lego --email example@example.com --dns domeneshop -d '*.example.com' -d example.com run +lego --dns domeneshop -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_dreamhost.md b/docs/content/dns/zz_gen_dreamhost.md index e713b8ad2..b9d273099 100644 --- a/docs/content/dns/zz_gen_dreamhost.md +++ b/docs/content/dns/zz_gen_dreamhost.md @@ -27,7 +27,7 @@ Here is an example bash command using the DreamHost provider: ```bash DREAMHOST_API_KEY="YOURAPIKEY" \ -lego --email you@example.com --dns dreamhost -d '*.example.com' -d example.com run +lego --dns dreamhost -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_duckdns.md b/docs/content/dns/zz_gen_duckdns.md index 1290b82fd..8b60780d2 100644 --- a/docs/content/dns/zz_gen_duckdns.md +++ b/docs/content/dns/zz_gen_duckdns.md @@ -27,7 +27,7 @@ Here is an example bash command using the Duck DNS provider: ```bash DUCKDNS_TOKEN=xxxxxx \ -lego --email you@example.com --dns duckdns -d '*.example.com' -d example.com run +lego --dns duckdns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_dyn.md b/docs/content/dns/zz_gen_dyn.md index f241ea930..e31a90e45 100644 --- a/docs/content/dns/zz_gen_dyn.md +++ b/docs/content/dns/zz_gen_dyn.md @@ -29,7 +29,7 @@ Here is an example bash command using the Dyn provider: DYN_CUSTOMER_NAME=xxxxxx \ DYN_USER_NAME=yyyyy \ DYN_PASSWORD=zzzz \ -lego --email you@example.com --dns dyn -d '*.example.com' -d example.com run +lego --dns dyn -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_dyndnsfree.md b/docs/content/dns/zz_gen_dyndnsfree.md index 6f4cf46ff..ea549b4e2 100644 --- a/docs/content/dns/zz_gen_dyndnsfree.md +++ b/docs/content/dns/zz_gen_dyndnsfree.md @@ -28,7 +28,7 @@ Here is an example bash command using the DynDnsFree.de provider: ```bash DYNDNSFREE_USERNAME="xxx" \ DYNDNSFREE_PASSWORD="yyy" \ -lego --email you@example.com --dns dyndnsfree -d '*.example.com' -d example.com run +lego --dns dyndnsfree -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_dynu.md b/docs/content/dns/zz_gen_dynu.md index 4db76456f..a1f3e762e 100644 --- a/docs/content/dns/zz_gen_dynu.md +++ b/docs/content/dns/zz_gen_dynu.md @@ -27,7 +27,7 @@ Here is an example bash command using the Dynu provider: ```bash DYNU_API_KEY=1234567890abcdefghijklmnopqrstuvwxyz \ -lego --email you@example.com --dns dynu -d '*.example.com' -d example.com run +lego --dns dynu -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_easydns.md b/docs/content/dns/zz_gen_easydns.md index 196e6ab7c..12f69e09c 100644 --- a/docs/content/dns/zz_gen_easydns.md +++ b/docs/content/dns/zz_gen_easydns.md @@ -28,7 +28,7 @@ Here is an example bash command using the EasyDNS provider: ```bash EASYDNS_TOKEN=xxx \ EASYDNS_KEY=yyy \ -lego --email you@example.com --dns easydns -d '*.example.com' -d example.com run +lego --dns easydns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_edgecenter.md b/docs/content/dns/zz_gen_edgecenter.md index 7c7dd9379..1fd9fe5fa 100644 --- a/docs/content/dns/zz_gen_edgecenter.md +++ b/docs/content/dns/zz_gen_edgecenter.md @@ -27,7 +27,7 @@ Here is an example bash command using the EdgeCenter provider: ```bash EDGECENTER_PERMANENT_API_TOKEN=xxxxx \ -lego --email you@example.com --dns edgecenter -d '*.example.com' -d example.com run +lego --dns edgecenter -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_edgedns.md b/docs/content/dns/zz_gen_edgedns.md index 21d819d2c..31b191168 100644 --- a/docs/content/dns/zz_gen_edgedns.md +++ b/docs/content/dns/zz_gen_edgedns.md @@ -30,7 +30,7 @@ AKAMAI_CLIENT_SECRET=abcdefghijklmnopqrstuvwxyz1234567890ABCDEFG= \ AKAMAI_CLIENT_TOKEN=akab-mnbvcxzlkjhgfdsapoiuytrewq1234567 \ AKAMAI_HOST=akab-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.luna.akamaiapis.net \ AKAMAI_ACCESS_TOKEN=akab-1234567890qwerty-asdfghjklzxcvtnu \ -lego --email you@example.com --dns edgedns -d '*.example.com' -d example.com run +lego --dns edgedns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_edgeone.md b/docs/content/dns/zz_gen_edgeone.md index 227127d65..ba5de5ba2 100644 --- a/docs/content/dns/zz_gen_edgeone.md +++ b/docs/content/dns/zz_gen_edgeone.md @@ -28,7 +28,7 @@ Here is an example bash command using the Tencent EdgeOne provider: ```bash EDGEONE_SECRET_ID=abcdefghijklmnopqrstuvwx \ EDGEONE_SECRET_KEY=your-secret-key \ -lego --email you@example.com --dns edgeone -d '*.example.com' -d example.com run +lego --dns edgeone -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_efficientip.md b/docs/content/dns/zz_gen_efficientip.md index 7c151e67a..acca3ebb7 100644 --- a/docs/content/dns/zz_gen_efficientip.md +++ b/docs/content/dns/zz_gen_efficientip.md @@ -30,7 +30,7 @@ EFFICIENTIP_USERNAME="user" \ EFFICIENTIP_PASSWORD="secret" \ EFFICIENTIP_HOSTNAME="ipam.example.org" \ EFFICIENTIP_DNS_NAME="dns.smart" \ -lego --email you@example.com --dns efficientip -d '*.example.com' -d example.com run +lego --dns efficientip -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_epik.md b/docs/content/dns/zz_gen_epik.md index 50f66e8da..a7fc029d3 100644 --- a/docs/content/dns/zz_gen_epik.md +++ b/docs/content/dns/zz_gen_epik.md @@ -27,7 +27,7 @@ Here is an example bash command using the Epik provider: ```bash EPIK_SIGNATURE=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns epik -d '*.example.com' -d example.com run +lego --dns epik -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_exec.md b/docs/content/dns/zz_gen_exec.md index fb2b17e3d..ad2e6906e 100644 --- a/docs/content/dns/zz_gen_exec.md +++ b/docs/content/dns/zz_gen_exec.md @@ -26,7 +26,7 @@ Here is an example bash command using the External program provider: ```bash EXEC_PATH=/the/path/to/myscript.sh \ -lego --email you@example.com --dns exec -d '*.example.com' -d example.com run +lego --dns exec -d '*.example.com' -d example.com run ``` @@ -61,7 +61,7 @@ For example, requesting a certificate for the domain 'my.example.org' can be ach ```bash EXEC_PATH=./update-dns.sh \ -lego --email you@example.com --dns exec --d my.example.org run +lego --dns exec --d my.example.org run ``` It will then call the program './update-dns.sh' with like this: @@ -81,7 +81,7 @@ If you want to use the raw domain, token, and keyAuth values with your program, ```bash EXEC_MODE=RAW \ EXEC_PATH=./update-dns.sh \ -lego --email you@example.com --dns exec -d my.example.org run +lego --dns exec -d my.example.org run ``` It will then call the program `./update-dns.sh` like this: diff --git a/docs/content/dns/zz_gen_exoscale.md b/docs/content/dns/zz_gen_exoscale.md index 5392ff573..e599d6487 100644 --- a/docs/content/dns/zz_gen_exoscale.md +++ b/docs/content/dns/zz_gen_exoscale.md @@ -28,7 +28,7 @@ Here is an example bash command using the Exoscale provider: ```bash EXOSCALE_API_KEY=abcdefghijklmnopqrstuvwx \ EXOSCALE_API_SECRET=xxxxxxx \ -lego --email you@example.com --dns exoscale -d '*.example.com' -d example.com run +lego --dns exoscale -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_f5xc.md b/docs/content/dns/zz_gen_f5xc.md index 52488f1f7..0fd8fe58a 100644 --- a/docs/content/dns/zz_gen_f5xc.md +++ b/docs/content/dns/zz_gen_f5xc.md @@ -29,7 +29,7 @@ Here is an example bash command using the F5 XC provider: F5XC_API_TOKEN="xxx" \ F5XC_TENANT_NAME="yyy" \ F5XC_GROUP_NAME="zzz" \ -lego --email you@example.com --dns f5xc -d '*.example.com' -d example.com run +lego --dns f5xc -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_freemyip.md b/docs/content/dns/zz_gen_freemyip.md index d89e17c27..215f8eb84 100644 --- a/docs/content/dns/zz_gen_freemyip.md +++ b/docs/content/dns/zz_gen_freemyip.md @@ -27,7 +27,7 @@ Here is an example bash command using the freemyip.com provider: ```bash FREEMYIP_TOKEN=xxxxxx \ -lego --email you@example.com --dns freemyip -d '*.example.com' -d example.com run +lego --dns freemyip -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_gandi.md b/docs/content/dns/zz_gen_gandi.md index 961ed6873..b02d97819 100644 --- a/docs/content/dns/zz_gen_gandi.md +++ b/docs/content/dns/zz_gen_gandi.md @@ -27,7 +27,7 @@ Here is an example bash command using the Gandi provider: ```bash GANDI_API_KEY=abcdefghijklmnopqrstuvwx \ -lego --email you@example.com --dns gandi -d '*.example.com' -d example.com run +lego --dns gandi -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_gandiv5.md b/docs/content/dns/zz_gen_gandiv5.md index 773bd3b08..78824abbe 100644 --- a/docs/content/dns/zz_gen_gandiv5.md +++ b/docs/content/dns/zz_gen_gandiv5.md @@ -27,7 +27,7 @@ Here is an example bash command using the Gandi Live DNS (v5) provider: ```bash GANDIV5_PERSONAL_ACCESS_TOKEN=abcdefghijklmnopqrstuvwx \ -lego --email you@example.com --dns gandiv5 -d '*.example.com' -d example.com run +lego --dns gandiv5 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_gcloud.md b/docs/content/dns/zz_gen_gcloud.md index ff228a1c8..64acc1d1e 100644 --- a/docs/content/dns/zz_gen_gcloud.md +++ b/docs/content/dns/zz_gen_gcloud.md @@ -29,18 +29,18 @@ Here is an example bash command using the Google Cloud provider: # Using a service account file GCE_PROJECT="gc-project-id" \ GCE_SERVICE_ACCOUNT_FILE="/path/to/svc/account/file.json" \ -lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run +lego --dns gcloud -d '*.example.com' -d example.com run # Using default credentials with impersonation GCE_PROJECT="gc-project-id" \ GCE_IMPERSONATE_SERVICE_ACCOUNT="target-sa@gc-project-id.iam.gserviceaccount.com" \ -lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run +lego --dns gcloud -d '*.example.com' -d example.com run # Using service account key with impersonation GCE_PROJECT="gc-project-id" \ GCE_SERVICE_ACCOUNT_FILE="/path/to/svc/account/file.json" \ GCE_IMPERSONATE_SERVICE_ACCOUNT="target-sa@gc-project-id.iam.gserviceaccount.com" \ -lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run +lego --dns gcloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_gcore.md b/docs/content/dns/zz_gen_gcore.md index f2a17c3fb..21a7ee9b1 100644 --- a/docs/content/dns/zz_gen_gcore.md +++ b/docs/content/dns/zz_gen_gcore.md @@ -27,7 +27,7 @@ Here is an example bash command using the G-Core provider: ```bash GCORE_PERMANENT_API_TOKEN=xxxxx \ -lego --email you@example.com --dns gcore -d '*.example.com' -d example.com run +lego --dns gcore -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_gigahostno.md b/docs/content/dns/zz_gen_gigahostno.md index afb7c64c9..a59ffc401 100644 --- a/docs/content/dns/zz_gen_gigahostno.md +++ b/docs/content/dns/zz_gen_gigahostno.md @@ -28,7 +28,7 @@ Here is an example bash command using the Gigahost.no provider: ```bash GIGAHOSTNO_USERNAME="xxxxxxxxxxxxxxxxxxxxx" \ GIGAHOSTNO_PASSWORD="yyyyyyyyyyyyyyyyyyyyy" \ -lego --email you@example.com --dns gigahostno -d '*.example.com' -d example.com run +lego --dns gigahostno -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_glesys.md b/docs/content/dns/zz_gen_glesys.md index ff43cfe9a..2d2608330 100644 --- a/docs/content/dns/zz_gen_glesys.md +++ b/docs/content/dns/zz_gen_glesys.md @@ -28,7 +28,7 @@ Here is an example bash command using the Glesys provider: ```bash GLESYS_API_USER=xxxxx \ GLESYS_API_KEY=yyyyy \ -lego --email you@example.com --dns glesys -d '*.example.com' -d example.com run +lego --dns glesys -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_godaddy.md b/docs/content/dns/zz_gen_godaddy.md index c5392a878..bc51cd69b 100644 --- a/docs/content/dns/zz_gen_godaddy.md +++ b/docs/content/dns/zz_gen_godaddy.md @@ -28,7 +28,7 @@ Here is an example bash command using the Go Daddy provider: ```bash GODADDY_API_KEY=xxxxxxxx \ GODADDY_API_SECRET=yyyyyyyy \ -lego --email you@example.com --dns godaddy -d '*.example.com' -d example.com run +lego --dns godaddy -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_googledomains.md b/docs/content/dns/zz_gen_googledomains.md index c6f6d0577..2421184c0 100644 --- a/docs/content/dns/zz_gen_googledomains.md +++ b/docs/content/dns/zz_gen_googledomains.md @@ -27,7 +27,7 @@ Here is an example bash command using the Google Domains provider: ```bash GOOGLE_DOMAINS_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns googledomains -d '*.example.com' -d example.com run +lego --dns googledomains -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_gravity.md b/docs/content/dns/zz_gen_gravity.md index 42d5e6128..654ad8424 100644 --- a/docs/content/dns/zz_gen_gravity.md +++ b/docs/content/dns/zz_gen_gravity.md @@ -29,7 +29,7 @@ Here is an example bash command using the Gravity provider: GRAVITY_SERVER_URL="https://example.org:1234" \ GRAVITY_USERNAME="xxxxxxxxxxxxxxxxxxxxx" \ GRAVITY_PASSWORD="yyyyyyyyyyyyyyyyyyyyy" \ -lego --email you@example.com --dns gravity -d '*.example.com' -d example.com run +lego --dns gravity -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_hetzner.md b/docs/content/dns/zz_gen_hetzner.md index 5778a64ce..4e81bd4d9 100644 --- a/docs/content/dns/zz_gen_hetzner.md +++ b/docs/content/dns/zz_gen_hetzner.md @@ -27,7 +27,7 @@ Here is an example bash command using the Hetzner provider: ```bash HETZNER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns hetzner -d '*.example.com' -d example.com run +lego --dns hetzner -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_hostingde.md b/docs/content/dns/zz_gen_hostingde.md index cc86116e1..4a66fe0f1 100644 --- a/docs/content/dns/zz_gen_hostingde.md +++ b/docs/content/dns/zz_gen_hostingde.md @@ -27,7 +27,7 @@ Here is an example bash command using the Hosting.de provider: ```bash HOSTINGDE_API_KEY=xxxxxxxx \ -lego --email you@example.com --dns hostingde -d '*.example.com' -d example.com run +lego --dns hostingde -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_hostinger.md b/docs/content/dns/zz_gen_hostinger.md index 193455f63..c05b3f003 100644 --- a/docs/content/dns/zz_gen_hostinger.md +++ b/docs/content/dns/zz_gen_hostinger.md @@ -27,7 +27,7 @@ Here is an example bash command using the Hostinger provider: ```bash HOSTINGER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns hostinger -d '*.example.com' -d example.com run +lego --dns hostinger -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_hostingnl.md b/docs/content/dns/zz_gen_hostingnl.md index 0577affd4..09cb69b47 100644 --- a/docs/content/dns/zz_gen_hostingnl.md +++ b/docs/content/dns/zz_gen_hostingnl.md @@ -27,7 +27,7 @@ Here is an example bash command using the Hosting.nl provider: ```bash HOSTINGNL_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns hostingnl -d '*.example.com' -d example.com run +lego --dns hostingnl -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_hosttech.md b/docs/content/dns/zz_gen_hosttech.md index 4f9f117ba..9435cc562 100644 --- a/docs/content/dns/zz_gen_hosttech.md +++ b/docs/content/dns/zz_gen_hosttech.md @@ -27,7 +27,7 @@ Here is an example bash command using the Hosttech provider: ```bash HOSTTECH_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns hosttech -d '*.example.com' -d example.com run +lego --dns hosttech -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_httpnet.md b/docs/content/dns/zz_gen_httpnet.md index 06883b3f8..862909697 100644 --- a/docs/content/dns/zz_gen_httpnet.md +++ b/docs/content/dns/zz_gen_httpnet.md @@ -27,7 +27,7 @@ Here is an example bash command using the http.net provider: ```bash HTTPNET_API_KEY=xxxxxxxx \ -lego --email you@example.com --dns httpnet -d '*.example.com' -d example.com run +lego --dns httpnet -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_httpreq.md b/docs/content/dns/zz_gen_httpreq.md index 9c6476802..7f6a8d576 100644 --- a/docs/content/dns/zz_gen_httpreq.md +++ b/docs/content/dns/zz_gen_httpreq.md @@ -27,7 +27,7 @@ Here is an example bash command using the HTTP request provider: ```bash HTTPREQ_ENDPOINT=http://my.server.com:9090 \ -lego --email you@example.com --dns httpreq -d '*.example.com' -d example.com run +lego --dns httpreq -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_huaweicloud.md b/docs/content/dns/zz_gen_huaweicloud.md index 9a37a8878..46d121265 100644 --- a/docs/content/dns/zz_gen_huaweicloud.md +++ b/docs/content/dns/zz_gen_huaweicloud.md @@ -29,7 +29,7 @@ Here is an example bash command using the Huawei Cloud provider: HUAWEICLOUD_ACCESS_KEY_ID=your-access-key-id \ HUAWEICLOUD_SECRET_ACCESS_KEY=your-secret-access-key \ HUAWEICLOUD_REGION=cn-south-1 \ -lego --email you@example.com --dns huaweicloud -d '*.example.com' -d example.com run +lego --dns huaweicloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_hurricane.md b/docs/content/dns/zz_gen_hurricane.md index da78630d4..0c195d19c 100644 --- a/docs/content/dns/zz_gen_hurricane.md +++ b/docs/content/dns/zz_gen_hurricane.md @@ -27,10 +27,10 @@ Here is an example bash command using the Hurricane Electric DNS provider: ```bash HURRICANE_TOKENS=example.org:token \ -lego --email you@example.com --dns hurricane -d '*.example.com' -d example.com run +lego --dns hurricane -d '*.example.com' -d example.com run HURRICANE_TOKENS=my.example.org:token1,demo.example.org:token2 \ -lego --email you@example.com --dns hurricane -d my.example.org -d demo.example.org +lego --dns hurricane -d my.example.org -d demo.example.org ``` diff --git a/docs/content/dns/zz_gen_hyperone.md b/docs/content/dns/zz_gen_hyperone.md index 83dfdb111..bc496f7bc 100644 --- a/docs/content/dns/zz_gen_hyperone.md +++ b/docs/content/dns/zz_gen_hyperone.md @@ -26,7 +26,7 @@ Configuration for [HyperOne](https://www.hyperone.com). Here is an example bash command using the HyperOne provider: ```bash -lego --email you@example.com --dns hyperone -d '*.example.com' -d example.com run +lego --dns hyperone -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ibmcloud.md b/docs/content/dns/zz_gen_ibmcloud.md index 94997b703..c5a48d2ad 100644 --- a/docs/content/dns/zz_gen_ibmcloud.md +++ b/docs/content/dns/zz_gen_ibmcloud.md @@ -28,7 +28,7 @@ Here is an example bash command using the IBM Cloud (SoftLayer) provider: ```bash SOFTLAYER_USERNAME=xxxxx \ SOFTLAYER_API_KEY=yyyyy \ -lego --email you@example.com --dns ibmcloud -d '*.example.com' -d example.com run +lego --dns ibmcloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_iij.md b/docs/content/dns/zz_gen_iij.md index 8c73f58a5..c7acfe3a0 100644 --- a/docs/content/dns/zz_gen_iij.md +++ b/docs/content/dns/zz_gen_iij.md @@ -29,7 +29,7 @@ Here is an example bash command using the Internet Initiative Japan provider: IIJ_API_ACCESS_KEY=xxxxxxxx \ IIJ_API_SECRET_KEY=yyyyyy \ IIJ_DO_SERVICE_CODE=zzzzzz \ -lego --email you@example.com --dns iij -d '*.example.com' -d example.com run +lego --dns iij -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_iijdpf.md b/docs/content/dns/zz_gen_iijdpf.md index 7c694fc32..12e126f49 100644 --- a/docs/content/dns/zz_gen_iijdpf.md +++ b/docs/content/dns/zz_gen_iijdpf.md @@ -28,7 +28,7 @@ Here is an example bash command using the IIJ DNS Platform Service provider: ```bash IIJ_DPF_API_TOKEN=xxxxxxxx \ IIJ_DPF_DPM_SERVICE_CODE=yyyyyy \ -lego --email you@example.com --dns iijdpf -d '*.example.com' -d example.com run +lego --dns iijdpf -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_infoblox.md b/docs/content/dns/zz_gen_infoblox.md index 2d07628f3..74b80b2d1 100644 --- a/docs/content/dns/zz_gen_infoblox.md +++ b/docs/content/dns/zz_gen_infoblox.md @@ -29,7 +29,7 @@ Here is an example bash command using the Infoblox provider: INFOBLOX_USERNAME=api-user-529 \ INFOBLOX_PASSWORD=b9841238feb177a84330febba8a83208921177bffe733 \ INFOBLOX_HOST=infoblox.example.org -lego --email you@example.com --dns infoblox -d '*.example.com' -d example.com run +lego --dns infoblox -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_infomaniak.md b/docs/content/dns/zz_gen_infomaniak.md index be02d8ee8..7254241b1 100644 --- a/docs/content/dns/zz_gen_infomaniak.md +++ b/docs/content/dns/zz_gen_infomaniak.md @@ -27,7 +27,7 @@ Here is an example bash command using the Infomaniak provider: ```bash INFOMANIAK_ACCESS_TOKEN=1234567898765432 \ -lego --email you@example.com --dns infomaniak -d '*.example.com' -d example.com run +lego --dns infomaniak -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_internetbs.md b/docs/content/dns/zz_gen_internetbs.md index e98fbf4b9..f0d9df3c1 100644 --- a/docs/content/dns/zz_gen_internetbs.md +++ b/docs/content/dns/zz_gen_internetbs.md @@ -28,7 +28,7 @@ Here is an example bash command using the Internet.bs provider: ```bash INTERNET_BS_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxx \ INTERNET_BS_PASSWORD=yyyyyyyyyyyyyyyyyyyyyyyyyy \ -lego --email you@example.com --dns internetbs -d '*.example.com' -d example.com run +lego --dns internetbs -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_inwx.md b/docs/content/dns/zz_gen_inwx.md index a46ff061e..3e7d999e9 100644 --- a/docs/content/dns/zz_gen_inwx.md +++ b/docs/content/dns/zz_gen_inwx.md @@ -28,13 +28,13 @@ Here is an example bash command using the INWX provider: ```bash INWX_USERNAME=xxxxxxxxxx \ INWX_PASSWORD=yyyyyyyyyy \ -lego --email you@example.com --dns inwx -d '*.example.com' -d example.com run +lego --dns inwx -d '*.example.com' -d example.com run # 2FA INWX_USERNAME=xxxxxxxxxx \ INWX_PASSWORD=yyyyyyyyyy \ INWX_SHARED_SECRET=zzzzzzzzzz \ -lego --email you@example.com --dns inwx -d '*.example.com' -d example.com run +lego --dns inwx -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ionos.md b/docs/content/dns/zz_gen_ionos.md index 60a2ede03..78bd3ffb1 100644 --- a/docs/content/dns/zz_gen_ionos.md +++ b/docs/content/dns/zz_gen_ionos.md @@ -27,7 +27,7 @@ Here is an example bash command using the Ionos provider: ```bash IONOS_API_KEY=xxxxxxxx \ -lego --email you@example.com --dns ionos -d '*.example.com' -d example.com run +lego --dns ionos -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ionoscloud.md b/docs/content/dns/zz_gen_ionoscloud.md index 9d33a95e5..6007670a7 100644 --- a/docs/content/dns/zz_gen_ionoscloud.md +++ b/docs/content/dns/zz_gen_ionoscloud.md @@ -27,7 +27,7 @@ Here is an example bash command using the Ionos Cloud provider: ```bash IONOSCLOUD_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns ionoscloud -d '*.example.com' -d example.com run +lego --dns ionoscloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ipv64.md b/docs/content/dns/zz_gen_ipv64.md index 21327caaf..00a0292a6 100644 --- a/docs/content/dns/zz_gen_ipv64.md +++ b/docs/content/dns/zz_gen_ipv64.md @@ -27,7 +27,7 @@ Here is an example bash command using the IPv64 provider: ```bash IPV64_API_KEY=xxxxxx \ -lego --email you@example.com --dns ipv64 -d '*.example.com' -d example.com run +lego --dns ipv64 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ispconfig.md b/docs/content/dns/zz_gen_ispconfig.md index bd3b375da..e56f1f0b1 100644 --- a/docs/content/dns/zz_gen_ispconfig.md +++ b/docs/content/dns/zz_gen_ispconfig.md @@ -29,7 +29,7 @@ Here is an example bash command using the ISPConfig 3 provider: ISPCONFIG_SERVER_URL="https://example.com:8080/remote/json.php" \ ISPCONFIG_USERNAME="xxx" \ ISPCONFIG_PASSWORD="yyy" \ -lego --email you@example.com --dns ispconfig -d '*.example.com' -d example.com run +lego --dns ispconfig -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ispconfigddns.md b/docs/content/dns/zz_gen_ispconfigddns.md index c59bddda4..3d1dd83c3 100644 --- a/docs/content/dns/zz_gen_ispconfigddns.md +++ b/docs/content/dns/zz_gen_ispconfigddns.md @@ -28,7 +28,7 @@ Here is an example bash command using the ISPConfig 3 - Dynamic DNS (DDNS) Modul ```bash ISPCONFIG_DDNS_SERVER_URL="https://panel.example.com:8080" \ ISPCONFIG_DDNS_TOKEN=xxxxxx \ -lego --email you@example.com --dns ispconfigddns -d '*.example.com' -d example.com run +lego --dns ispconfigddns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_iwantmyname.md b/docs/content/dns/zz_gen_iwantmyname.md index cbdb29cb3..4638e1379 100644 --- a/docs/content/dns/zz_gen_iwantmyname.md +++ b/docs/content/dns/zz_gen_iwantmyname.md @@ -30,7 +30,7 @@ Here is an example bash command using the iwantmyname (Deprecated) provider: ```bash IWANTMYNAME_USERNAME=xxxxxxxx \ IWANTMYNAME_PASSWORD=xxxxxxxx \ -lego --email you@example.com --dns iwantmyname -d '*.example.com' -d example.com run +lego --dns iwantmyname -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_joker.md b/docs/content/dns/zz_gen_joker.md index c8d55b2f7..a5ecd47de 100644 --- a/docs/content/dns/zz_gen_joker.md +++ b/docs/content/dns/zz_gen_joker.md @@ -30,17 +30,17 @@ Here is an example bash command using the Joker provider: JOKER_API_MODE=SVC \ JOKER_USERNAME= \ JOKER_PASSWORD= \ -lego --email you@example.com --dns joker -d '*.example.com' -d example.com run +lego --dns joker -d '*.example.com' -d example.com run # DMAPI JOKER_API_MODE=DMAPI \ JOKER_USERNAME= \ JOKER_PASSWORD= \ -lego --email you@example.com --dns joker -d '*.example.com' -d example.com run +lego --dns joker -d '*.example.com' -d example.com run ## or JOKER_API_MODE=DMAPI \ JOKER_API_KEY= \ -lego --email you@example.com --dns joker -d '*.example.com' -d example.com run +lego --dns joker -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_keyhelp.md b/docs/content/dns/zz_gen_keyhelp.md index 2886a0a8e..e39d3ce82 100644 --- a/docs/content/dns/zz_gen_keyhelp.md +++ b/docs/content/dns/zz_gen_keyhelp.md @@ -28,7 +28,7 @@ Here is an example bash command using the KeyHelp provider: ```bash KEYHELP_BASE_URL="https://keyhelp.example.com" \ KEYHELP_API_KEY="xxx" \ -lego --email you@example.com --dns keyhelp -d '*.example.com' -d example.com run +lego --dns keyhelp -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_liara.md b/docs/content/dns/zz_gen_liara.md index 2c3d59ae0..8a6ddbd99 100644 --- a/docs/content/dns/zz_gen_liara.md +++ b/docs/content/dns/zz_gen_liara.md @@ -27,7 +27,7 @@ Here is an example bash command using the Liara provider: ```bash LIARA_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns liara -d '*.example.com' -d example.com run +lego --dns liara -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_limacity.md b/docs/content/dns/zz_gen_limacity.md index 2a01814e5..29bc6e0a7 100644 --- a/docs/content/dns/zz_gen_limacity.md +++ b/docs/content/dns/zz_gen_limacity.md @@ -27,7 +27,7 @@ Here is an example bash command using the Lima-City provider: ```bash LIMACITY_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns limacity -d '*.example.com' -d example.com run +lego --dns limacity -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_linode.md b/docs/content/dns/zz_gen_linode.md index 8c8487541..e41ba7cd9 100644 --- a/docs/content/dns/zz_gen_linode.md +++ b/docs/content/dns/zz_gen_linode.md @@ -27,7 +27,7 @@ Here is an example bash command using the Linode (v4) provider: ```bash LINODE_TOKEN=xxxxx \ -lego --email you@example.com --dns linode -d '*.example.com' -d example.com run +lego --dns linode -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_liquidweb.md b/docs/content/dns/zz_gen_liquidweb.md index 9d8fe8c9c..bd2ce63b6 100644 --- a/docs/content/dns/zz_gen_liquidweb.md +++ b/docs/content/dns/zz_gen_liquidweb.md @@ -28,7 +28,7 @@ Here is an example bash command using the Liquid Web provider: ```bash LWAPI_USERNAME=someuser \ LWAPI_PASSWORD=somepass \ -lego --email you@example.com --dns liquidweb -d '*.example.com' -d example.com run +lego --dns liquidweb -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_loopia.md b/docs/content/dns/zz_gen_loopia.md index 3951de8e1..bb3120c00 100644 --- a/docs/content/dns/zz_gen_loopia.md +++ b/docs/content/dns/zz_gen_loopia.md @@ -28,7 +28,7 @@ Here is an example bash command using the Loopia provider: ```bash LOOPIA_API_USER=xxxxxxxx \ LOOPIA_API_PASSWORD=yyyyyyyy \ -lego --email you@example.com --dns loopia -d '*.example.com' -d example.com run +lego --dns loopia -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_luadns.md b/docs/content/dns/zz_gen_luadns.md index c987cc9bf..8bf718ba3 100644 --- a/docs/content/dns/zz_gen_luadns.md +++ b/docs/content/dns/zz_gen_luadns.md @@ -28,7 +28,7 @@ Here is an example bash command using the LuaDNS provider: ```bash LUADNS_API_USERNAME=youremail \ LUADNS_API_TOKEN=xxxxxxxx \ -lego --email you@example.com --dns luadns -d '*.example.com' -d example.com run +lego --dns luadns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_mailinabox.md b/docs/content/dns/zz_gen_mailinabox.md index 3ffed1cc7..62a6bdb57 100644 --- a/docs/content/dns/zz_gen_mailinabox.md +++ b/docs/content/dns/zz_gen_mailinabox.md @@ -29,7 +29,7 @@ Here is an example bash command using the Mail-in-a-Box provider: MAILINABOX_EMAIL=user@example.com \ MAILINABOX_PASSWORD=yyyy \ MAILINABOX_BASE_URL=https://box.example.com \ -lego --email you@example.com --dns mailinabox -d '*.example.com' -d example.com run +lego --dns mailinabox -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_manageengine.md b/docs/content/dns/zz_gen_manageengine.md index 32b3a3aeb..a39db8208 100644 --- a/docs/content/dns/zz_gen_manageengine.md +++ b/docs/content/dns/zz_gen_manageengine.md @@ -28,7 +28,7 @@ Here is an example bash command using the ManageEngine CloudDNS provider: ```bash MANAGEENGINE_CLIENT_ID="xxx" \ MANAGEENGINE_CLIENT_SECRET="yyy" \ -lego --email you@example.com --dns manageengine -d '*.example.com' -d example.com run +lego --dns manageengine -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_manual.md b/docs/content/dns/zz_gen_manual.md index 0300d8400..056726c74 100644 --- a/docs/content/dns/zz_gen_manual.md +++ b/docs/content/dns/zz_gen_manual.md @@ -25,7 +25,7 @@ Solving the DNS-01 challenge using CLI prompt. Here is an example bash command using the Manual provider: ```bash -lego --email you@example.com --dns manual -d '*.example.com' -d example.com run +lego --dns manual -d '*.example.com' -d example.com run ``` @@ -36,7 +36,7 @@ lego --email you@example.com --dns manual -d '*.example.com' -d example.com run To start using the CLI prompt "provider", start lego with `--dns manual`: ```console -$ lego --email "you@example.com" --domains="example.com" --dns "manual" run +$ lego --dns manual -d example.com run ``` What follows are a few log print-outs, interspersed with some prompts, asking for you to do perform some actions: diff --git a/docs/content/dns/zz_gen_metaname.md b/docs/content/dns/zz_gen_metaname.md index a90d0170b..156cf15eb 100644 --- a/docs/content/dns/zz_gen_metaname.md +++ b/docs/content/dns/zz_gen_metaname.md @@ -28,7 +28,7 @@ Here is an example bash command using the Metaname provider: ```bash METANAME_ACCOUNT_REFERENCE=xxxx \ METANAME_API_KEY=yyyyyyy \ -lego --email you@example.com --dns metaname -d '*.example.com' -d example.com run +lego --dns metaname -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_metaregistrar.md b/docs/content/dns/zz_gen_metaregistrar.md index 63cc2bebc..22de046e2 100644 --- a/docs/content/dns/zz_gen_metaregistrar.md +++ b/docs/content/dns/zz_gen_metaregistrar.md @@ -27,7 +27,7 @@ Here is an example bash command using the Metaregistrar provider: ```bash METAREGISTRAR_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns metaregistrar -d '*.example.com' -d example.com run +lego --dns metaregistrar -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_mijnhost.md b/docs/content/dns/zz_gen_mijnhost.md index 42abc6558..3d8f71aff 100644 --- a/docs/content/dns/zz_gen_mijnhost.md +++ b/docs/content/dns/zz_gen_mijnhost.md @@ -27,7 +27,7 @@ Here is an example bash command using the mijn.host provider: ```bash MIJNHOST_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns mijnhost -d '*.example.com' -d example.com run +lego --dns mijnhost -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_mittwald.md b/docs/content/dns/zz_gen_mittwald.md index 943397ee9..7714ef54f 100644 --- a/docs/content/dns/zz_gen_mittwald.md +++ b/docs/content/dns/zz_gen_mittwald.md @@ -27,7 +27,7 @@ Here is an example bash command using the Mittwald provider: ```bash MITTWALD_TOKEN=my-token \ -lego --email you@example.com --dns mittwald -d '*.example.com' -d example.com run +lego --dns mittwald -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_myaddr.md b/docs/content/dns/zz_gen_myaddr.md index 277a0bf06..4a52a058b 100644 --- a/docs/content/dns/zz_gen_myaddr.md +++ b/docs/content/dns/zz_gen_myaddr.md @@ -27,7 +27,7 @@ Here is an example bash command using the myaddr.{tools,dev,io} provider: ```bash MYADDR_PRIVATE_KEYS_MAPPING="example:123,test:456" \ -lego --email you@example.com --dns myaddr -d '*.example.com' -d example.com run +lego --dns myaddr -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_mydnsjp.md b/docs/content/dns/zz_gen_mydnsjp.md index 5b29266db..0a49404bb 100644 --- a/docs/content/dns/zz_gen_mydnsjp.md +++ b/docs/content/dns/zz_gen_mydnsjp.md @@ -28,7 +28,7 @@ Here is an example bash command using the MyDNS.jp provider: ```bash MYDNSJP_MASTER_ID=xxxxx \ MYDNSJP_PASSWORD=xxxxx \ -lego --email you@example.com --dns mydnsjp -d '*.example.com' -d example.com run +lego --dns mydnsjp -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_mythicbeasts.md b/docs/content/dns/zz_gen_mythicbeasts.md index 37feebf8c..70e38d249 100644 --- a/docs/content/dns/zz_gen_mythicbeasts.md +++ b/docs/content/dns/zz_gen_mythicbeasts.md @@ -28,7 +28,7 @@ Here is an example bash command using the MythicBeasts provider: ```bash MYTHICBEASTS_USERNAME=myuser \ MYTHICBEASTS_PASSWORD=mypass \ -lego --email you@example.com --dns mythicbeasts -d '*.example.com' -d example.com run +lego --dns mythicbeasts -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_namecheap.md b/docs/content/dns/zz_gen_namecheap.md index 706651660..9d7143d84 100644 --- a/docs/content/dns/zz_gen_namecheap.md +++ b/docs/content/dns/zz_gen_namecheap.md @@ -33,7 +33,7 @@ Here is an example bash command using the Namecheap provider: ```bash NAMECHEAP_API_USER=user \ NAMECHEAP_API_KEY=key \ -lego --email you@example.com --dns namecheap -d '*.example.com' -d example.com run +lego --dns namecheap -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_namedotcom.md b/docs/content/dns/zz_gen_namedotcom.md index 36a423faa..2860ff0ae 100644 --- a/docs/content/dns/zz_gen_namedotcom.md +++ b/docs/content/dns/zz_gen_namedotcom.md @@ -28,7 +28,7 @@ Here is an example bash command using the Name.com provider: ```bash NAMECOM_USERNAME=foo.bar \ NAMECOM_API_TOKEN=a379a6f6eeafb9a55e378c118034e2751e682fab \ -lego --email you@example.com --dns namedotcom -d '*.example.com' -d example.com run +lego --dns namedotcom -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_namesilo.md b/docs/content/dns/zz_gen_namesilo.md index 397a1a3ca..207a1603f 100644 --- a/docs/content/dns/zz_gen_namesilo.md +++ b/docs/content/dns/zz_gen_namesilo.md @@ -27,7 +27,7 @@ Here is an example bash command using the Namesilo provider: ```bash NAMESILO_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \ -lego --email you@example.com --dns namesilo -d '*.example.com' -d example.com run +lego --dns namesilo -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_nearlyfreespeech.md b/docs/content/dns/zz_gen_nearlyfreespeech.md index 86f6152f9..31402d2d2 100644 --- a/docs/content/dns/zz_gen_nearlyfreespeech.md +++ b/docs/content/dns/zz_gen_nearlyfreespeech.md @@ -28,7 +28,7 @@ Here is an example bash command using the NearlyFreeSpeech.NET provider: ```bash NEARLYFREESPEECH_API_KEY=xxxxxx \ NEARLYFREESPEECH_LOGIN=xxxx \ -lego --email you@example.com --dns nearlyfreespeech -d '*.example.com' -d example.com run +lego --dns nearlyfreespeech -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_neodigit.md b/docs/content/dns/zz_gen_neodigit.md index 70dfb6343..aefeef4bf 100644 --- a/docs/content/dns/zz_gen_neodigit.md +++ b/docs/content/dns/zz_gen_neodigit.md @@ -27,7 +27,7 @@ Here is an example bash command using the Neodigit provider: ```bash NEODIGIT_TOKEN=xxxxxx \ -lego --email you@example.com --dns neodigit -d '*.example.com' -d example.com run +lego --dns neodigit -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_netcup.md b/docs/content/dns/zz_gen_netcup.md index 337baf59d..29def3285 100644 --- a/docs/content/dns/zz_gen_netcup.md +++ b/docs/content/dns/zz_gen_netcup.md @@ -29,7 +29,7 @@ Here is an example bash command using the Netcup provider: NETCUP_CUSTOMER_NUMBER=xxxx \ NETCUP_API_KEY=yyyy \ NETCUP_API_PASSWORD=zzzz \ -lego --email you@example.com --dns netcup -d '*.example.com' -d example.com run +lego --dns netcup -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_netlify.md b/docs/content/dns/zz_gen_netlify.md index b08f650f0..76651d9ef 100644 --- a/docs/content/dns/zz_gen_netlify.md +++ b/docs/content/dns/zz_gen_netlify.md @@ -27,7 +27,7 @@ Here is an example bash command using the Netlify provider: ```bash NETLIFY_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns netlify -d '*.example.com' -d example.com run +lego --dns netlify -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_nicmanager.md b/docs/content/dns/zz_gen_nicmanager.md index 0b6e1b2cb..a29d72120 100644 --- a/docs/content/dns/zz_gen_nicmanager.md +++ b/docs/content/dns/zz_gen_nicmanager.md @@ -34,7 +34,7 @@ NICMANAGER_API_PASSWORD = "password" \ # Optionally, if your account has TOTP enabled, set the secret here NICMANAGER_API_OTP = "long-secret" \ -lego --email you@example.com --dns nicmanager -d '*.example.com' -d example.com run +lego --dns nicmanager -d '*.example.com' -d example.com run ## Login using account name + username @@ -45,7 +45,7 @@ NICMANAGER_API_PASSWORD = "password" \ # Optionally, if your account has TOTP enabled, set the secret here NICMANAGER_API_OTP = "long-secret" \ -lego --email you@example.com --dns nicmanager -d '*.example.com' -d example.com run +lego --dns nicmanager -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_nicru.md b/docs/content/dns/zz_gen_nicru.md index d55477a32..3ac8d99cf 100644 --- a/docs/content/dns/zz_gen_nicru.md +++ b/docs/content/dns/zz_gen_nicru.md @@ -30,7 +30,7 @@ NICRU_USER="" \ NICRU_PASSWORD="" \ NICRU_SERVICE_ID="" \ NICRU_SECRET="" \ -lego --dns nicru --domains "*.example.com" --email you@example.com run +lego --dns nicru -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_nifcloud.md b/docs/content/dns/zz_gen_nifcloud.md index 9b9929ce2..66f38223b 100644 --- a/docs/content/dns/zz_gen_nifcloud.md +++ b/docs/content/dns/zz_gen_nifcloud.md @@ -28,7 +28,7 @@ Here is an example bash command using the NIFCloud provider: ```bash NIFCLOUD_ACCESS_KEY_ID=xxxx \ NIFCLOUD_SECRET_ACCESS_KEY=yyyy \ -lego --email you@example.com --dns nifcloud -d '*.example.com' -d example.com run +lego --dns nifcloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_njalla.md b/docs/content/dns/zz_gen_njalla.md index cf268041c..9a312df8b 100644 --- a/docs/content/dns/zz_gen_njalla.md +++ b/docs/content/dns/zz_gen_njalla.md @@ -27,7 +27,7 @@ Here is an example bash command using the Njalla provider: ```bash NJALLA_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns njalla -d '*.example.com' -d example.com run +lego --dns njalla -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_nodion.md b/docs/content/dns/zz_gen_nodion.md index c11759e8e..8d61eb834 100644 --- a/docs/content/dns/zz_gen_nodion.md +++ b/docs/content/dns/zz_gen_nodion.md @@ -27,7 +27,7 @@ Here is an example bash command using the Nodion provider: ```bash NODION_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns nodion -d '*.example.com' -d example.com run +lego --dns nodion -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ns1.md b/docs/content/dns/zz_gen_ns1.md index 547a51c1c..b2262169d 100644 --- a/docs/content/dns/zz_gen_ns1.md +++ b/docs/content/dns/zz_gen_ns1.md @@ -27,7 +27,7 @@ Here is an example bash command using the NS1 provider: ```bash NS1_API_KEY=xxxx \ -lego --email you@example.com --dns ns1 -d '*.example.com' -d example.com run +lego --dns ns1 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_octenium.md b/docs/content/dns/zz_gen_octenium.md index 874c4e780..f25da4f44 100644 --- a/docs/content/dns/zz_gen_octenium.md +++ b/docs/content/dns/zz_gen_octenium.md @@ -27,7 +27,7 @@ Here is an example bash command using the Octenium provider: ```bash OCTENIUM_API_KEY="xxx" \ -lego --email you@example.com --dns octenium -d '*.example.com' -d example.com run +lego --dns octenium -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_oraclecloud.md b/docs/content/dns/zz_gen_oraclecloud.md index c43c24b21..b7192f380 100644 --- a/docs/content/dns/zz_gen_oraclecloud.md +++ b/docs/content/dns/zz_gen_oraclecloud.md @@ -34,13 +34,13 @@ OCI_USER_OCID="ocid1.user.oc1..secret" \ OCI_FINGERPRINT="00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00" \ OCI_REGION="us-phoenix-1" \ OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ -lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com run +lego --dns oraclecloud -d '*.example.com' -d example.com run # Using Instance Principal authentication (when running on OCI compute instances): # https://docs.oracle.com/en-us/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm OCI_AUTH_TYPE="instance_principal" \ OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ -lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com run +lego --dns oraclecloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_otc.md b/docs/content/dns/zz_gen_otc.md index 4f3679fa2..9da69c694 100644 --- a/docs/content/dns/zz_gen_otc.md +++ b/docs/content/dns/zz_gen_otc.md @@ -30,7 +30,7 @@ OTC_DOMAIN_NAME=domain_name \ OTC_USER_NAME=user_name \ OTC_PASSWORD=password \ OTC_PROJECT_NAME=project_name \ -lego --email you@example.com --dns otc -d '*.example.com' -d example.com run +lego --dns otc -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ovh.md b/docs/content/dns/zz_gen_ovh.md index 7abc01b92..aaafded85 100644 --- a/docs/content/dns/zz_gen_ovh.md +++ b/docs/content/dns/zz_gen_ovh.md @@ -32,20 +32,20 @@ OVH_APPLICATION_KEY=1234567898765432 \ OVH_APPLICATION_SECRET=b9841238feb177a84330febba8a832089 \ OVH_CONSUMER_KEY=256vfsd347245sdfg \ OVH_ENDPOINT=ovh-eu \ -lego --email you@example.com --dns ovh -d '*.example.com' -d example.com run +lego --dns ovh -d '*.example.com' -d example.com run # Or Access Token: OVH_ACCESS_TOKEN=xxx \ OVH_ENDPOINT=ovh-eu \ -lego --email you@example.com --dns ovh -d '*.example.com' -d example.com run +lego --dns ovh -d '*.example.com' -d example.com run # Or OAuth2: OVH_CLIENT_ID=yyy \ OVH_CLIENT_SECRET=xxx \ OVH_ENDPOINT=ovh-eu \ -lego --email you@example.com --dns ovh -d '*.example.com' -d example.com run +lego --dns ovh -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_pdns.md b/docs/content/dns/zz_gen_pdns.md index 34a22cf84..7c2a8c663 100644 --- a/docs/content/dns/zz_gen_pdns.md +++ b/docs/content/dns/zz_gen_pdns.md @@ -28,7 +28,7 @@ Here is an example bash command using the PowerDNS provider: ```bash PDNS_API_URL=http://pdns-server:80/ \ PDNS_API_KEY=xxxx \ -lego --email you@example.com --dns pdns -d '*.example.com' -d example.com run +lego --dns pdns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_plesk.md b/docs/content/dns/zz_gen_plesk.md index b18b2656a..73ec9a55d 100644 --- a/docs/content/dns/zz_gen_plesk.md +++ b/docs/content/dns/zz_gen_plesk.md @@ -29,7 +29,7 @@ Here is an example bash command using the plesk.com provider: PLESK_SERVER_BASE_URL="https://plesk.myserver.com:8443" \ PLESK_USERNAME=xxxxxx \ PLESK_PASSWORD=yyyyyy \ -lego --email you@example.com --dns plesk -d '*.example.com' -d example.com run +lego --dns plesk -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_porkbun.md b/docs/content/dns/zz_gen_porkbun.md index 9fd230d0d..f54e6f688 100644 --- a/docs/content/dns/zz_gen_porkbun.md +++ b/docs/content/dns/zz_gen_porkbun.md @@ -28,7 +28,7 @@ Here is an example bash command using the Porkbun provider: ```bash PORKBUN_SECRET_API_KEY=xxxxxx \ PORKBUN_API_KEY=yyyyyy \ -lego --email you@example.com --dns porkbun -d '*.example.com' -d example.com run +lego --dns porkbun -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_rackspace.md b/docs/content/dns/zz_gen_rackspace.md index 6dcf6b2b2..b9a2ab710 100644 --- a/docs/content/dns/zz_gen_rackspace.md +++ b/docs/content/dns/zz_gen_rackspace.md @@ -28,7 +28,7 @@ Here is an example bash command using the Rackspace provider: ```bash RACKSPACE_USER=xxxx \ RACKSPACE_API_KEY=yyyy \ -lego --email you@example.com --dns rackspace -d '*.example.com' -d example.com run +lego --dns rackspace -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_rainyun.md b/docs/content/dns/zz_gen_rainyun.md index 74ced9f54..680eb845a 100644 --- a/docs/content/dns/zz_gen_rainyun.md +++ b/docs/content/dns/zz_gen_rainyun.md @@ -27,7 +27,7 @@ Here is an example bash command using the Rain Yun/雨云 provider: ```bash RAINYUN_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns rainyun -d '*.example.com' -d example.com run +lego --dns rainyun -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_rcodezero.md b/docs/content/dns/zz_gen_rcodezero.md index 98eaea9ca..a544df420 100644 --- a/docs/content/dns/zz_gen_rcodezero.md +++ b/docs/content/dns/zz_gen_rcodezero.md @@ -27,7 +27,7 @@ Here is an example bash command using the RcodeZero provider: ```bash RCODEZERO_API_TOKEN= \ -lego --email you@example.com --dns rcodezero -d '*.example.com' -d example.com run +lego --dns rcodezero -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_regfish.md b/docs/content/dns/zz_gen_regfish.md index 149338e5e..357ce0764 100644 --- a/docs/content/dns/zz_gen_regfish.md +++ b/docs/content/dns/zz_gen_regfish.md @@ -27,7 +27,7 @@ Here is an example bash command using the Regfish provider: ```bash REGFISH_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns regfish -d '*.example.com' -d example.com run +lego --dns regfish -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_regru.md b/docs/content/dns/zz_gen_regru.md index 1d0e0053d..eaf163a13 100644 --- a/docs/content/dns/zz_gen_regru.md +++ b/docs/content/dns/zz_gen_regru.md @@ -28,7 +28,7 @@ Here is an example bash command using the reg.ru provider: ```bash REGRU_USERNAME=xxxxxx \ REGRU_PASSWORD=yyyyyy \ -lego --email you@example.com --dns regru -d '*.example.com' -d example.com run +lego --dns regru -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_rfc2136.md b/docs/content/dns/zz_gen_rfc2136.md index ffdbc4b54..1b1d43dd5 100644 --- a/docs/content/dns/zz_gen_rfc2136.md +++ b/docs/content/dns/zz_gen_rfc2136.md @@ -30,7 +30,7 @@ RFC2136_NAMESERVER=127.0.0.1 \ RFC2136_TSIG_KEY=example.com \ RFC2136_TSIG_ALGORITHM=hmac-sha256. \ RFC2136_TSIG_SECRET=YWJjZGVmZGdoaWprbG1ub3BxcnN0dXZ3eHl6MTIzNDU= \ -lego --email you@example.com --dns rfc2136 -d '*.example.com' -d example.com run +lego --dns rfc2136 -d '*.example.com' -d example.com run ## --- @@ -38,7 +38,7 @@ keyname=example.com; keyfile=example.com.key; tsig-keygen $keyname > $keyfile RFC2136_NAMESERVER=127.0.0.1 \ RFC2136_TSIG_FILE="$keyfile" \ -lego --email you@example.com --dns rfc2136 -d '*.example.com' -d example.com run +lego --dns rfc2136 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_rimuhosting.md b/docs/content/dns/zz_gen_rimuhosting.md index 2a703dec7..acb829e93 100644 --- a/docs/content/dns/zz_gen_rimuhosting.md +++ b/docs/content/dns/zz_gen_rimuhosting.md @@ -27,7 +27,7 @@ Here is an example bash command using the RimuHosting provider: ```bash RIMUHOSTING_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns rimuhosting -d '*.example.com' -d example.com run +lego --dns rimuhosting -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_route53.md b/docs/content/dns/zz_gen_route53.md index a0967a57e..59e489d6a 100644 --- a/docs/content/dns/zz_gen_route53.md +++ b/docs/content/dns/zz_gen_route53.md @@ -30,7 +30,7 @@ AWS_ACCESS_KEY_ID=your_key_id \ AWS_SECRET_ACCESS_KEY=your_secret_access_key \ AWS_REGION=aws-region \ AWS_HOSTED_ZONE_ID=your_hosted_zone_id \ -lego --email you@example.com --dns route53 -d '*.example.com' -d example.com run +lego --dns route53 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_safedns.md b/docs/content/dns/zz_gen_safedns.md index 2a9e179f5..e040a8a9f 100644 --- a/docs/content/dns/zz_gen_safedns.md +++ b/docs/content/dns/zz_gen_safedns.md @@ -27,7 +27,7 @@ Here is an example bash command using the UKFast SafeDNS provider: ```bash SAFEDNS_AUTH_TOKEN=xxxxxx \ -lego --email you@example.com --dns safedns -d '*.example.com' -d example.com run +lego --dns safedns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_sakuracloud.md b/docs/content/dns/zz_gen_sakuracloud.md index e08e73e70..b43f83ef4 100644 --- a/docs/content/dns/zz_gen_sakuracloud.md +++ b/docs/content/dns/zz_gen_sakuracloud.md @@ -28,7 +28,7 @@ Here is an example bash command using the Sakura Cloud provider: ```bash SAKURACLOUD_ACCESS_TOKEN=xxxxx \ SAKURACLOUD_ACCESS_TOKEN_SECRET=yyyyy \ -lego --email you@example.com --dns sakuracloud -d '*.example.com' -d example.com run +lego --dns sakuracloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_scaleway.md b/docs/content/dns/zz_gen_scaleway.md index 2f6af9d8a..4033a9bd6 100644 --- a/docs/content/dns/zz_gen_scaleway.md +++ b/docs/content/dns/zz_gen_scaleway.md @@ -27,7 +27,7 @@ Here is an example bash command using the Scaleway provider: ```bash SCW_SECRET_KEY=xxxxxxx-xxxxx-xxxx-xxx-xxxxxx \ -lego --email you@example.com --dns scaleway -d '*.example.com' -d example.com run +lego --dns scaleway -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_selectel.md b/docs/content/dns/zz_gen_selectel.md index 33dc859bb..d994d6633 100644 --- a/docs/content/dns/zz_gen_selectel.md +++ b/docs/content/dns/zz_gen_selectel.md @@ -27,7 +27,7 @@ Here is an example bash command using the Selectel provider: ```bash SELECTEL_API_TOKEN=xxxxx \ -lego --email you@example.com --dns selectel -d '*.example.com' -d example.com run +lego --dns selectel -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_selectelv2.md b/docs/content/dns/zz_gen_selectelv2.md index 933ca201f..0873d810c 100644 --- a/docs/content/dns/zz_gen_selectelv2.md +++ b/docs/content/dns/zz_gen_selectelv2.md @@ -30,7 +30,7 @@ SELECTELV2_USERNAME=trex \ SELECTELV2_PASSWORD=xxxxx \ SELECTELV2_ACCOUNT_ID=1234567 \ SELECTELV2_PROJECT_ID=111a11111aaa11aa1a11aaa11111aa1a \ -lego --email you@example.com --dns selectelv2 -d '*.example.com' -d example.com run +lego --dns selectelv2 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_selfhostde.md b/docs/content/dns/zz_gen_selfhostde.md index 12df0c10d..363f782e0 100644 --- a/docs/content/dns/zz_gen_selfhostde.md +++ b/docs/content/dns/zz_gen_selfhostde.md @@ -29,7 +29,7 @@ Here is an example bash command using the SelfHost.(de|eu) provider: SELFHOSTDE_USERNAME=xxx \ SELFHOSTDE_PASSWORD=yyy \ SELFHOSTDE_RECORDS_MAPPING=my.example.com:123 \ -lego --email you@example.com --dns selfhostde -d '*.example.com' -d example.com run +lego --dns selfhostde -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_servercow.md b/docs/content/dns/zz_gen_servercow.md index 3851325d1..7d00a6306 100644 --- a/docs/content/dns/zz_gen_servercow.md +++ b/docs/content/dns/zz_gen_servercow.md @@ -28,7 +28,7 @@ Here is an example bash command using the Servercow provider: ```bash SERVERCOW_USERNAME=xxxxxxxx \ SERVERCOW_PASSWORD=xxxxxxxx \ -lego --email you@example.com --dns servercow -d '*.example.com' -d example.com run +lego --dns servercow -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_shellrent.md b/docs/content/dns/zz_gen_shellrent.md index 6c1365b7e..cbbc172e2 100644 --- a/docs/content/dns/zz_gen_shellrent.md +++ b/docs/content/dns/zz_gen_shellrent.md @@ -28,7 +28,7 @@ Here is an example bash command using the Shellrent provider: ```bash SHELLRENT_USERNAME=xxxx \ SHELLRENT_TOKEN=yyyy \ -lego --email you@example.com --dns shellrent -d '*.example.com' -d example.com run +lego --dns shellrent -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_simply.md b/docs/content/dns/zz_gen_simply.md index 32df66f05..edfa14380 100644 --- a/docs/content/dns/zz_gen_simply.md +++ b/docs/content/dns/zz_gen_simply.md @@ -28,7 +28,7 @@ Here is an example bash command using the Simply.com provider: ```bash SIMPLY_ACCOUNT_NAME=xxxxxx \ SIMPLY_API_KEY=yyyyyy \ -lego --email you@example.com --dns simply -d '*.example.com' -d example.com run +lego --dns simply -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_sonic.md b/docs/content/dns/zz_gen_sonic.md index f56a23151..20729bc1a 100644 --- a/docs/content/dns/zz_gen_sonic.md +++ b/docs/content/dns/zz_gen_sonic.md @@ -28,7 +28,7 @@ Here is an example bash command using the Sonic provider: ```bash SONIC_USER_ID=12345 \ SONIC_API_KEY=4d6fbf2f9ab0fa11697470918d37625851fc0c51 \ -lego --email you@example.com --dns sonic -d '*.example.com' -d example.com run +lego --dns sonic -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_spaceship.md b/docs/content/dns/zz_gen_spaceship.md index 4594fe217..9f3b51e43 100644 --- a/docs/content/dns/zz_gen_spaceship.md +++ b/docs/content/dns/zz_gen_spaceship.md @@ -28,7 +28,7 @@ Here is an example bash command using the Spaceship provider: ```bash SPACESHIP_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ SPACESHIP_API_SECRET="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns spaceship -d '*.example.com' -d example.com run +lego --dns spaceship -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_stackpath.md b/docs/content/dns/zz_gen_stackpath.md index ce0a02eac..b881176f4 100644 --- a/docs/content/dns/zz_gen_stackpath.md +++ b/docs/content/dns/zz_gen_stackpath.md @@ -29,7 +29,7 @@ Here is an example bash command using the Stackpath provider: STACKPATH_CLIENT_ID=xxxxx \ STACKPATH_CLIENT_SECRET=yyyyy \ STACKPATH_STACK_ID=zzzzz \ -lego --email you@example.com --dns stackpath -d '*.example.com' -d example.com run +lego --dns stackpath -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_syse.md b/docs/content/dns/zz_gen_syse.md index 1d9d957d5..a1a952bc5 100644 --- a/docs/content/dns/zz_gen_syse.md +++ b/docs/content/dns/zz_gen_syse.md @@ -27,10 +27,10 @@ Here is an example bash command using the Syse provider: ```bash SYSE_CREDENTIALS=example.com:password \ -lego --email you@example.com --dns syse -d '*.example.com' -d example.com run +lego --dns syse -d '*.example.com' -d example.com run SYSE_CREDENTIALS=example.org:password1,example.com:password2 \ -lego --email you@example.com --dns syse -d '*.example.org' -d example.org -d '*.example.com' -d example.com +lego --dns syse -d '*.example.org' -d example.org -d '*.example.com' -d example.com ``` diff --git a/docs/content/dns/zz_gen_technitium.md b/docs/content/dns/zz_gen_technitium.md index 80f7c6a1f..ff7f2e6ed 100644 --- a/docs/content/dns/zz_gen_technitium.md +++ b/docs/content/dns/zz_gen_technitium.md @@ -28,7 +28,7 @@ Here is an example bash command using the Technitium provider: ```bash TECHNITIUM_SERVER_BASE_URL="https://localhost:5380" \ TECHNITIUM_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns technitium -d '*.example.com' -d example.com run +lego --dns technitium -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_tencentcloud.md b/docs/content/dns/zz_gen_tencentcloud.md index ef1e6cdf8..178ffcf43 100644 --- a/docs/content/dns/zz_gen_tencentcloud.md +++ b/docs/content/dns/zz_gen_tencentcloud.md @@ -28,7 +28,7 @@ Here is an example bash command using the Tencent Cloud DNS provider: ```bash TENCENTCLOUD_SECRET_ID=abcdefghijklmnopqrstuvwx \ TENCENTCLOUD_SECRET_KEY=your-secret-key \ -lego --email you@example.com --dns tencentcloud -d '*.example.com' -d example.com run +lego --dns tencentcloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_timewebcloud.md b/docs/content/dns/zz_gen_timewebcloud.md index af218ddce..83d5b831b 100644 --- a/docs/content/dns/zz_gen_timewebcloud.md +++ b/docs/content/dns/zz_gen_timewebcloud.md @@ -27,7 +27,7 @@ Here is an example bash command using the Timeweb Cloud provider: ```bash TIMEWEBCLOUD_AUTH_TOKEN=xxxxxx \ -lego --email you@example.com --dns timewebcloud -d '*.example.com' -d example.com run +lego --dns timewebcloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_transip.md b/docs/content/dns/zz_gen_transip.md index 769fbc734..a66a25879 100644 --- a/docs/content/dns/zz_gen_transip.md +++ b/docs/content/dns/zz_gen_transip.md @@ -28,7 +28,7 @@ Here is an example bash command using the TransIP provider: ```bash TRANSIP_ACCOUNT_NAME = "Account name" \ TRANSIP_PRIVATE_KEY_PATH = "transip.key" \ -lego --email you@example.com --dns transip -d '*.example.com' -d example.com run +lego --dns transip -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_ultradns.md b/docs/content/dns/zz_gen_ultradns.md index 8e0fa9b20..d6d89c77b 100644 --- a/docs/content/dns/zz_gen_ultradns.md +++ b/docs/content/dns/zz_gen_ultradns.md @@ -28,7 +28,7 @@ Here is an example bash command using the Ultradns provider: ```bash ULTRADNS_USERNAME=username \ ULTRADNS_PASSWORD=password \ -lego --email you@example.com --dns ultradns -d '*.example.com' -d example.com run +lego --dns ultradns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_uniteddomains.md b/docs/content/dns/zz_gen_uniteddomains.md index 7f94dd09f..e837644d5 100644 --- a/docs/content/dns/zz_gen_uniteddomains.md +++ b/docs/content/dns/zz_gen_uniteddomains.md @@ -27,7 +27,7 @@ Here is an example bash command using the United-Domains provider: ```bash UNITEDDOMAINS_API_KEY=xxxxxxxx \ -lego --email you@example.com --dns uniteddomains -d '*.example.com' -d example.com run +lego --dns uniteddomains -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_variomedia.md b/docs/content/dns/zz_gen_variomedia.md index 282ec9da3..f9771c867 100644 --- a/docs/content/dns/zz_gen_variomedia.md +++ b/docs/content/dns/zz_gen_variomedia.md @@ -27,7 +27,7 @@ Here is an example bash command using the Variomedia provider: ```bash VARIOMEDIA_API_TOKEN=xxxx \ -lego --email you@example.com --dns variomedia -d '*.example.com' -d example.com run +lego --dns variomedia -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_vercel.md b/docs/content/dns/zz_gen_vercel.md index d9e24eee3..71f2eeed5 100644 --- a/docs/content/dns/zz_gen_vercel.md +++ b/docs/content/dns/zz_gen_vercel.md @@ -27,7 +27,7 @@ Here is an example bash command using the Vercel provider: ```bash VERCEL_API_TOKEN=xxxxxx \ -lego --email you@example.com --dns vercel -d '*.example.com' -d example.com run +lego --dns vercel -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_versio.md b/docs/content/dns/zz_gen_versio.md index 0e2edfa1e..5d2cc0118 100644 --- a/docs/content/dns/zz_gen_versio.md +++ b/docs/content/dns/zz_gen_versio.md @@ -28,7 +28,7 @@ Here is an example bash command using the Versio.[nl|eu|uk] provider: ```bash VERSIO_USERNAME= \ VERSIO_PASSWORD= \ -lego --email you@example.com --dns versio -d '*.example.com' -d example.com run +lego --dns versio -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_vinyldns.md b/docs/content/dns/zz_gen_vinyldns.md index 666bc39c4..3280d6f0a 100644 --- a/docs/content/dns/zz_gen_vinyldns.md +++ b/docs/content/dns/zz_gen_vinyldns.md @@ -29,7 +29,7 @@ Here is an example bash command using the VinylDNS provider: VINYLDNS_ACCESS_KEY=xxxxxx \ VINYLDNS_SECRET_KEY=yyyyy \ VINYLDNS_HOST=https://api.vinyldns.example.org:9443 \ -lego --email you@example.com --dns vinyldns -d '*.example.com' -d example.com run +lego --dns vinyldns -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_virtualname.md b/docs/content/dns/zz_gen_virtualname.md index afba24ad0..a00e5105f 100644 --- a/docs/content/dns/zz_gen_virtualname.md +++ b/docs/content/dns/zz_gen_virtualname.md @@ -27,7 +27,7 @@ Here is an example bash command using the Virtualname provider: ```bash VIRTUALNAME_TOKEN=xxxxxx \ -lego --email you@example.com --dns virtualname -d '*.example.com' -d example.com run +lego --dns virtualname -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_vkcloud.md b/docs/content/dns/zz_gen_vkcloud.md index eede62cf5..76fd557a5 100644 --- a/docs/content/dns/zz_gen_vkcloud.md +++ b/docs/content/dns/zz_gen_vkcloud.md @@ -29,7 +29,7 @@ Here is an example bash command using the VK Cloud provider: VK_CLOUD_PROJECT_ID="" \ VK_CLOUD_USERNAME="" \ VK_CLOUD_PASSWORD="" \ -lego --email you@example.com --dns vkcloud -d '*.example.com' -d example.com run +lego --dns vkcloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_volcengine.md b/docs/content/dns/zz_gen_volcengine.md index 9d3c92d0d..587ce1e74 100644 --- a/docs/content/dns/zz_gen_volcengine.md +++ b/docs/content/dns/zz_gen_volcengine.md @@ -28,7 +28,7 @@ Here is an example bash command using the Volcano Engine/火山引擎 provider: ```bash VOLC_ACCESSKEY=xxx \ VOLC_SECRETKEY=yyy \ -lego --email you@example.com --dns volcengine -d '*.example.com' -d example.com run +lego --dns volcengine -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_vscale.md b/docs/content/dns/zz_gen_vscale.md index 660542d61..c33e2f7b5 100644 --- a/docs/content/dns/zz_gen_vscale.md +++ b/docs/content/dns/zz_gen_vscale.md @@ -27,7 +27,7 @@ Here is an example bash command using the Vscale provider: ```bash VSCALE_API_TOKEN=xxxxx \ -lego --email you@example.com --dns vscale -d '*.example.com' -d example.com run +lego --dns vscale -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_vultr.md b/docs/content/dns/zz_gen_vultr.md index a3807c1a1..4160fbcf3 100644 --- a/docs/content/dns/zz_gen_vultr.md +++ b/docs/content/dns/zz_gen_vultr.md @@ -27,7 +27,7 @@ Here is an example bash command using the Vultr provider: ```bash VULTR_API_KEY=xxxxx \ -lego --email you@example.com --dns vultr -d '*.example.com' -d example.com run +lego --dns vultr -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_webnames.md b/docs/content/dns/zz_gen_webnames.md index 4945775a5..cad02c287 100644 --- a/docs/content/dns/zz_gen_webnames.md +++ b/docs/content/dns/zz_gen_webnames.md @@ -27,7 +27,7 @@ Here is an example bash command using the webnames.ru provider: ```bash WEBNAMESRU_API_KEY=xxxxxx \ -lego --email you@example.com --dns webnamesru -d '*.example.com' -d example.com run +lego --dns webnamesru -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_webnamesca.md b/docs/content/dns/zz_gen_webnamesca.md index 41a33cb82..4a7d3794f 100644 --- a/docs/content/dns/zz_gen_webnamesca.md +++ b/docs/content/dns/zz_gen_webnamesca.md @@ -28,7 +28,7 @@ Here is an example bash command using the webnames.ca provider: ```bash WEBNAMESCA_API_USER="xxx" \ WEBNAMESCA_API_KEY="yyy" \ -lego --email you@example.com --dns webnamesca -d '*.example.com' -d example.com run +lego --dns webnamesca -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_websupport.md b/docs/content/dns/zz_gen_websupport.md index 5fe44a860..67ae394d7 100644 --- a/docs/content/dns/zz_gen_websupport.md +++ b/docs/content/dns/zz_gen_websupport.md @@ -28,7 +28,7 @@ Here is an example bash command using the Websupport provider: ```bash WEBSUPPORT_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ WEBSUPPORT_SECRET="yyyyyyyyyyyyyyyyyyyyy" \ -lego --email you@example.com --dns websupport -d '*.example.com' -d example.com run +lego --dns websupport -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_wedos.md b/docs/content/dns/zz_gen_wedos.md index 8fe6ba00d..16139f4d4 100644 --- a/docs/content/dns/zz_gen_wedos.md +++ b/docs/content/dns/zz_gen_wedos.md @@ -28,7 +28,7 @@ Here is an example bash command using the WEDOS provider: ```bash WEDOS_USERNAME=xxxxxxxx \ WEDOS_WAPI_PASSWORD=xxxxxxxx \ -lego --email you@example.com --dns wedos -d '*.example.com' -d example.com run +lego --dns wedos -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_westcn.md b/docs/content/dns/zz_gen_westcn.md index 434e5b601..a5523b955 100644 --- a/docs/content/dns/zz_gen_westcn.md +++ b/docs/content/dns/zz_gen_westcn.md @@ -28,7 +28,7 @@ Here is an example bash command using the West.cn/西部数码 provider: ```bash WESTCN_USERNAME="xxx" \ WESTCN_PASSWORD="yyy" \ -lego --email you@example.com --dns westcn -d '*.example.com' -d example.com run +lego --dns westcn -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_yandex.md b/docs/content/dns/zz_gen_yandex.md index 6100c02fe..4a1cf1f99 100644 --- a/docs/content/dns/zz_gen_yandex.md +++ b/docs/content/dns/zz_gen_yandex.md @@ -27,7 +27,7 @@ Here is an example bash command using the Yandex PDD provider: ```bash YANDEX_PDD_TOKEN= \ -lego --email you@example.com --dns yandex -d '*.example.com' -d example.com run +lego --dns yandex -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_yandex360.md b/docs/content/dns/zz_gen_yandex360.md index 66b90e049..d831fdfc2 100644 --- a/docs/content/dns/zz_gen_yandex360.md +++ b/docs/content/dns/zz_gen_yandex360.md @@ -28,7 +28,7 @@ Here is an example bash command using the Yandex 360 provider: ```bash YANDEX360_OAUTH_TOKEN= \ YANDEX360_ORG_ID= \ -lego --email you@example.com --dns yandex360 -d '*.example.com' -d example.com run +lego --dns yandex360 -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_yandexcloud.md b/docs/content/dns/zz_gen_yandexcloud.md index f5aeba09d..0564e93d2 100644 --- a/docs/content/dns/zz_gen_yandexcloud.md +++ b/docs/content/dns/zz_gen_yandexcloud.md @@ -28,7 +28,7 @@ Here is an example bash command using the Yandex Cloud provider: ```bash YANDEX_CLOUD_IAM_TOKEN= \ YANDEX_CLOUD_FOLDER_ID= \ -lego --email you@example.com --dns yandexcloud -d '*.example.com' -d example.com run +lego --dns yandexcloud -d '*.example.com' -d example.com run # --- @@ -41,7 +41,7 @@ YANDEX_CLOUD_IAM_TOKEN=$(echo '{ \ "private_key": "-----BEGIN PRIVATE KEY----------END PRIVATE KEY-----" \ }' | base64) \ YANDEX_CLOUD_FOLDER_ID= \ -lego --email you@example.com --dns yandexcloud -d '*.example.com' -d example.com run +lego --dns yandexcloud -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_zoneedit.md b/docs/content/dns/zz_gen_zoneedit.md index e259a2a04..c7f88b3fe 100644 --- a/docs/content/dns/zz_gen_zoneedit.md +++ b/docs/content/dns/zz_gen_zoneedit.md @@ -28,7 +28,7 @@ Here is an example bash command using the ZoneEdit provider: ```bash ZONEEDIT_USER="xxxxxxxxxxxxxxxxxxxxx" \ ZONEEDIT_AUTH_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns zoneedit -d '*.example.com' -d example.com run +lego --dns zoneedit -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_zoneee.md b/docs/content/dns/zz_gen_zoneee.md index cfc6be692..65678a3dc 100644 --- a/docs/content/dns/zz_gen_zoneee.md +++ b/docs/content/dns/zz_gen_zoneee.md @@ -28,7 +28,7 @@ Here is an example bash command using the Zone.ee provider: ```bash ZONEEE_API_USER=xxxxx \ ZONEEE_API_KEY=yyyyy \ -lego --email you@example.com --dns zoneee -d '*.example.com' -d example.com run +lego --dns zoneee -d '*.example.com' -d example.com run ``` diff --git a/docs/content/dns/zz_gen_zonomi.md b/docs/content/dns/zz_gen_zonomi.md index 1e90a7285..fd8757f82 100644 --- a/docs/content/dns/zz_gen_zonomi.md +++ b/docs/content/dns/zz_gen_zonomi.md @@ -27,7 +27,7 @@ Here is an example bash command using the Zonomi provider: ```bash ZONOMI_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns zonomi -d '*.example.com' -d example.com run +lego --dns zonomi -d '*.example.com' -d example.com run ``` diff --git a/providers/dns/acmedns/acmedns.toml b/providers/dns/acmedns/acmedns.toml index 6d68a013d..e491569b0 100644 --- a/providers/dns/acmedns/acmedns.toml +++ b/providers/dns/acmedns/acmedns.toml @@ -8,13 +8,13 @@ Since = "v1.1.0" Example = ''' ACME_DNS_API_BASE=http://10.0.0.8:4443 \ ACME_DNS_STORAGE_PATH=/root/.lego-acme-dns-accounts.json \ -lego --email you@example.com --dns "acme-dns" -d '*.example.com' -d example.com run +lego --dns "acme-dns" -d '*.example.com' -d example.com run # or ACME_DNS_API_BASE=http://10.0.0.8:4443 \ ACME_DNS_STORAGE_BASE_URL=http://10.10.10.10:80 \ -lego --email you@example.com --dns "acme-dns" -d '*.example.com' -d example.com run +lego --dns "acme-dns" -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/active24/active24.toml b/providers/dns/active24/active24.toml index 6a54d4695..b0eaabab8 100644 --- a/providers/dns/active24/active24.toml +++ b/providers/dns/active24/active24.toml @@ -7,7 +7,7 @@ Since = "v4.23.0" Example = ''' ACTIVE24_API_KEY="xxx" \ ACTIVE24_SECRET="yyy" \ -lego --email you@example.com --dns active24 -d '*.example.com' -d example.com run +lego --dns active24 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/alidns/alidns.toml b/providers/dns/alidns/alidns.toml index 49a9aeeab..9a93bd24f 100644 --- a/providers/dns/alidns/alidns.toml +++ b/providers/dns/alidns/alidns.toml @@ -7,13 +7,13 @@ Since = "v1.1.0" Example = ''' # Setup using instance RAM role ALICLOUD_RAM_ROLE=lego \ -lego --email you@example.com --dns alidns -d '*.example.com' -d example.com run +lego --dns alidns -d '*.example.com' -d example.com run # Or, using credentials ALICLOUD_ACCESS_KEY=abcdefghijklmnopqrstuvwx \ ALICLOUD_SECRET_KEY=your-secret-key \ ALICLOUD_SECURITY_TOKEN=your-sts-token \ -lego --email you@example.com --dns alidns - -d '*.example.com' -d example.com run +lego --dns alidns - -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/aliesa/aliesa.toml b/providers/dns/aliesa/aliesa.toml index d0f6cdb91..5e7345e40 100644 --- a/providers/dns/aliesa/aliesa.toml +++ b/providers/dns/aliesa/aliesa.toml @@ -7,13 +7,13 @@ Since = "v4.29.0" Example = ''' # Setup using instance RAM role ALIESA_RAM_ROLE=lego \ -lego --email you@example.com --dns aliesa -d '*.example.com' -d example.com run +lego --dns aliesa -d '*.example.com' -d example.com run # Or, using credentials ALIESA_ACCESS_KEY=abcdefghijklmnopqrstuvwx \ ALIESA_SECRET_KEY=your-secret-key \ ALIESA_SECURITY_TOKEN=your-sts-token \ -lego --email you@example.com --dns aliesa - -d '*.example.com' -d example.com run +lego --dns aliesa - -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/allinkl/allinkl.toml b/providers/dns/allinkl/allinkl.toml index d9c937ee1..774f8fb9f 100644 --- a/providers/dns/allinkl/allinkl.toml +++ b/providers/dns/allinkl/allinkl.toml @@ -7,7 +7,7 @@ Since = "v4.5.0" Example = ''' ALL_INKL_LOGIN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ ALL_INKL_PASSWORD=yyyyyyyyyyyyyyyyyyyyyyyyyy \ -lego --email you@example.com --dns allinkl -d '*.example.com' -d example.com run +lego --dns allinkl -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/alwaysdata/alwaysdata.toml b/providers/dns/alwaysdata/alwaysdata.toml index 96d8d9616..d00c6f032 100644 --- a/providers/dns/alwaysdata/alwaysdata.toml +++ b/providers/dns/alwaysdata/alwaysdata.toml @@ -6,7 +6,7 @@ Since = "v4.31.0" Example = ''' ALWAYSDATA_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns alwaysdata -d '*.example.com' -d example.com run +lego --dns alwaysdata -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/anexia/anexia.toml b/providers/dns/anexia/anexia.toml index 4fad8ea48..332f0b8b1 100644 --- a/providers/dns/anexia/anexia.toml +++ b/providers/dns/anexia/anexia.toml @@ -6,7 +6,7 @@ Since = "v4.28.0" Example = ''' ANEXIA_TOKEN=xxx \ -lego --email you@example.com --dns anexia -d '*.example.com' -d example.com run +lego --dns anexia -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/arvancloud/arvancloud.toml b/providers/dns/arvancloud/arvancloud.toml index e94452a8b..aa5cafb51 100644 --- a/providers/dns/arvancloud/arvancloud.toml +++ b/providers/dns/arvancloud/arvancloud.toml @@ -6,7 +6,7 @@ Since = "v3.8.0" Example = ''' ARVANCLOUD_API_KEY="Apikey xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \ -lego --email you@example.com --dns arvancloud -d '*.example.com' -d example.com run +lego --dns arvancloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/auroradns/auroradns.toml b/providers/dns/auroradns/auroradns.toml index e000e015e..59b5e7ab1 100644 --- a/providers/dns/auroradns/auroradns.toml +++ b/providers/dns/auroradns/auroradns.toml @@ -7,7 +7,7 @@ Since = "v0.4.0" Example = ''' AURORA_API_KEY=xxxxx \ AURORA_SECRET=yyyyyy \ -lego --email you@example.com --dns auroradns -d '*.example.com' -d example.com run +lego --dns auroradns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/autodns/autodns.toml b/providers/dns/autodns/autodns.toml index 78015e431..2798d4cee 100644 --- a/providers/dns/autodns/autodns.toml +++ b/providers/dns/autodns/autodns.toml @@ -7,7 +7,7 @@ Since = "v3.2.0" Example = ''' AUTODNS_API_USER=username \ AUTODNS_API_PASSWORD=supersecretpassword \ -lego --email you@example.com --dns autodns -d '*.example.com' -d example.com run +lego --dns autodns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/axelname/axelname.toml b/providers/dns/axelname/axelname.toml index ee348d5d8..1e2ad6e72 100644 --- a/providers/dns/axelname/axelname.toml +++ b/providers/dns/axelname/axelname.toml @@ -7,7 +7,7 @@ Since = "v4.23.0" Example = ''' AXELNAME_NICKNAME="yyy" \ AXELNAME_TOKEN="xxx" \ -lego --email you@example.com --dns axelname -d '*.example.com' -d example.com run +lego --dns axelname -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/azion/azion.toml b/providers/dns/azion/azion.toml index eacfe74a6..52df20ab5 100644 --- a/providers/dns/azion/azion.toml +++ b/providers/dns/azion/azion.toml @@ -6,7 +6,7 @@ URL = "https://www.azion.com/en/products/edge-dns/" Example = ''' AZION_PERSONAL_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns azion -d '*.example.com' -d example.com run +lego --dns azion -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/azuredns/azuredns.toml b/providers/dns/azuredns/azuredns.toml index 6c1e1ccff..7c800ce7e 100644 --- a/providers/dns/azuredns/azuredns.toml +++ b/providers/dns/azuredns/azuredns.toml @@ -10,32 +10,32 @@ Example = ''' AZURE_CLIENT_ID= \ AZURE_TENANT_ID= \ AZURE_CLIENT_SECRET= \ -lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run +lego --dns azuredns -d '*.example.com' -d example.com run ### Using client certificate AZURE_CLIENT_ID= \ AZURE_TENANT_ID= \ AZURE_CLIENT_CERTIFICATE_PATH= \ -lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run +lego --dns azuredns -d '*.example.com' -d example.com run ### Using Azure CLI az login \ -lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run +lego --dns azuredns -d '*.example.com' -d example.com run ### Using Managed Identity (Azure VM) AZURE_TENANT_ID= \ AZURE_RESOURCE_GROUP= \ -lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run +lego --dns azuredns -d '*.example.com' -d example.com run ### Using Managed Identity (Azure Arc) AZURE_TENANT_ID= \ IMDS_ENDPOINT=http://localhost:40342 \ IDENTITY_ENDPOINT=http://localhost:40342/metadata/identity/oauth2/token \ -lego --email you@example.com --dns azuredns -d '*.example.com' -d example.com run +lego --dns azuredns -d '*.example.com' -d example.com run ''' diff --git a/providers/dns/baiducloud/baiducloud.toml b/providers/dns/baiducloud/baiducloud.toml index 8422eafd5..54f1f6312 100644 --- a/providers/dns/baiducloud/baiducloud.toml +++ b/providers/dns/baiducloud/baiducloud.toml @@ -7,7 +7,7 @@ Since = "v4.23.0" Example = ''' BAIDUCLOUD_ACCESS_KEY_ID="xxx" \ BAIDUCLOUD_SECRET_ACCESS_KEY="yyy" \ -lego --email you@example.com --dns baiducloud -d '*.example.com' -d example.com run +lego --dns baiducloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/beget/beget.toml b/providers/dns/beget/beget.toml index 3cef2f38c..4ed26d850 100644 --- a/providers/dns/beget/beget.toml +++ b/providers/dns/beget/beget.toml @@ -7,7 +7,7 @@ Since = "v4.27.0" Example = ''' BEGET_USERNAME=xxxxxx \ BEGET_PASSWORD=yyyyyy \ -lego --email you@example.com --dns beget -d '*.example.com' -d example.com run +lego --dns beget -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/binarylane/binarylane.toml b/providers/dns/binarylane/binarylane.toml index 5038fc3e6..8b382f3b2 100644 --- a/providers/dns/binarylane/binarylane.toml +++ b/providers/dns/binarylane/binarylane.toml @@ -6,7 +6,7 @@ Since = "v4.26.0" Example = ''' BINARYLANE_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns binarylane -d '*.example.com' -d example.com run +lego --dns binarylane -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/bindman/bindman.toml b/providers/dns/bindman/bindman.toml index 5c69e18ff..768601588 100644 --- a/providers/dns/bindman/bindman.toml +++ b/providers/dns/bindman/bindman.toml @@ -6,7 +6,7 @@ Since = "v2.6.0" Example = ''' BINDMAN_MANAGER_ADDRESS= \ -lego --email you@example.com --dns bindman -d '*.example.com' -d example.com run +lego --dns bindman -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/bluecat/bluecat.toml b/providers/dns/bluecat/bluecat.toml index a01a5918d..15df6ed34 100644 --- a/providers/dns/bluecat/bluecat.toml +++ b/providers/dns/bluecat/bluecat.toml @@ -11,7 +11,7 @@ BLUECAT_USER_NAME=myusername \ BLUECAT_CONFIG_NAME=myconfig \ BLUECAT_SERVER_URL=https://bam.example.com \ BLUECAT_TTL=30 \ -lego --email you@example.com --dns bluecat -d '*.example.com' -d example.com run +lego --dns bluecat -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/bookmyname/bookmyname.toml b/providers/dns/bookmyname/bookmyname.toml index 5111c4fbd..76fcb85e7 100644 --- a/providers/dns/bookmyname/bookmyname.toml +++ b/providers/dns/bookmyname/bookmyname.toml @@ -7,7 +7,7 @@ Since = "v4.23.0" Example = ''' BOOKMYNAME_USERNAME="xxx" \ BOOKMYNAME_PASSWORD="yyy" \ -lego --email you@example.com --dns bookmyname -d '*.example.com' -d example.com run +lego --dns bookmyname -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/brandit/brandit.toml b/providers/dns/brandit/brandit.toml index 32d15c15c..4c43e27a9 100644 --- a/providers/dns/brandit/brandit.toml +++ b/providers/dns/brandit/brandit.toml @@ -12,7 +12,7 @@ Since = "v4.11.0" Example = ''' BRANDIT_API_KEY=xxxxxxxxxxxxxxxxxxxxx \ BRANDIT_API_USERNAME=yyyyyyyyyyyyyyyyyyyy \ -lego --email you@example.com --dns brandit -d '*.example.com' -d example.com run +lego --dns brandit -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/bunny/bunny.toml b/providers/dns/bunny/bunny.toml index cbe22d6db..758c4f202 100644 --- a/providers/dns/bunny/bunny.toml +++ b/providers/dns/bunny/bunny.toml @@ -6,7 +6,7 @@ Since = "v4.11.0" Example = ''' BUNNY_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ -lego --email you@example.com --dns bunny -d '*.example.com' -d example.com run +lego --dns bunny -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/checkdomain/checkdomain.toml b/providers/dns/checkdomain/checkdomain.toml index c3ac14e36..0b93058ba 100644 --- a/providers/dns/checkdomain/checkdomain.toml +++ b/providers/dns/checkdomain/checkdomain.toml @@ -6,7 +6,7 @@ Since = "v3.3.0" Example = ''' CHECKDOMAIN_TOKEN=yoursecrettoken \ -lego --email you@example.com --dns checkdomain -d '*.example.com' -d example.com run +lego --dns checkdomain -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/civo/civo.toml b/providers/dns/civo/civo.toml index 9458f01c3..b525712c8 100644 --- a/providers/dns/civo/civo.toml +++ b/providers/dns/civo/civo.toml @@ -6,7 +6,7 @@ Since = "v4.9.0" Example = ''' CIVO_TOKEN=xxxxxx \ -lego --email you@example.com --dns civo -d '*.example.com' -d example.com run +lego --dns civo -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/clouddns/clouddns.toml b/providers/dns/clouddns/clouddns.toml index 154d4da67..6f516e834 100644 --- a/providers/dns/clouddns/clouddns.toml +++ b/providers/dns/clouddns/clouddns.toml @@ -8,7 +8,7 @@ Example = ''' CLOUDDNS_CLIENT_ID=bLsdFAks23429841238feb177a572aX \ CLOUDDNS_EMAIL=you@example.com \ CLOUDDNS_PASSWORD=b9841238feb177a84330f \ -lego --email you@example.com --dns clouddns -d '*.example.com' -d example.com run +lego --dns clouddns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/cloudflare/cloudflare.toml b/providers/dns/cloudflare/cloudflare.toml index caf132bb4..c46130fe6 100644 --- a/providers/dns/cloudflare/cloudflare.toml +++ b/providers/dns/cloudflare/cloudflare.toml @@ -7,12 +7,12 @@ Since = "v0.3.0" Example = ''' CLOUDFLARE_EMAIL=you@example.com \ CLOUDFLARE_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \ -lego --email you@example.com --dns cloudflare -d '*.example.com' -d example.com run +lego --dns cloudflare -d '*.example.com' -d example.com run # or CLOUDFLARE_DNS_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \ -lego --email you@example.com --dns cloudflare -d '*.example.com' -d example.com run +lego --dns cloudflare -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/cloudns/cloudns.toml b/providers/dns/cloudns/cloudns.toml index dd191f06a..ad52ef5b1 100644 --- a/providers/dns/cloudns/cloudns.toml +++ b/providers/dns/cloudns/cloudns.toml @@ -7,7 +7,7 @@ Since = "v2.3.0" Example = ''' CLOUDNS_AUTH_ID=xxxx \ CLOUDNS_AUTH_PASSWORD=yyyy \ -lego --email you@example.com --dns cloudns -d '*.example.com' -d example.com run +lego --dns cloudns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/cloudru/cloudru.toml b/providers/dns/cloudru/cloudru.toml index a6563a3df..b74098a72 100644 --- a/providers/dns/cloudru/cloudru.toml +++ b/providers/dns/cloudru/cloudru.toml @@ -8,7 +8,7 @@ Example = ''' CLOUDRU_SERVICE_INSTANCE_ID=ppp \ CLOUDRU_KEY_ID=xxx \ CLOUDRU_SECRET=yyy \ -lego --email you@example.com --dns cloudru -d '*.example.com' -d example.com run +lego --dns cloudru -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/cloudxns/cloudxns.toml b/providers/dns/cloudxns/cloudxns.toml index e87a741df..32eae8beb 100644 --- a/providers/dns/cloudxns/cloudxns.toml +++ b/providers/dns/cloudxns/cloudxns.toml @@ -9,7 +9,7 @@ Since = "v0.5.0" Example = ''' CLOUDXNS_API_KEY=xxxx \ CLOUDXNS_SECRET_KEY=yyyy \ -lego --email you@example.com --dns cloudxns -d '*.example.com' -d example.com run +lego --dns cloudxns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/conoha/conoha.toml b/providers/dns/conoha/conoha.toml index 8bd83247e..be90acb0d 100644 --- a/providers/dns/conoha/conoha.toml +++ b/providers/dns/conoha/conoha.toml @@ -8,7 +8,7 @@ Example = ''' CONOHA_TENANT_ID=487727e3921d44e3bfe7ebb337bf085e \ CONOHA_API_USERNAME=xxxx \ CONOHA_API_PASSWORD=yyyy \ -lego --email you@example.com --dns conoha -d '*.example.com' -d example.com run +lego --dns conoha -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/conohav3/conohav3.toml b/providers/dns/conohav3/conohav3.toml index 7608e6742..e2c80259d 100644 --- a/providers/dns/conohav3/conohav3.toml +++ b/providers/dns/conohav3/conohav3.toml @@ -8,7 +8,7 @@ Example = ''' CONOHAV3_TENANT_ID=487727e3921d44e3bfe7ebb337bf085e \ CONOHAV3_API_USER_ID=xxxx \ CONOHAV3_API_PASSWORD=yyyy \ -lego --email you@example.com --dns conohav3 -d '*.example.com' -d example.com run +lego --dns conohav3 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/constellix/constellix.toml b/providers/dns/constellix/constellix.toml index c4ae0a194..171a0de99 100644 --- a/providers/dns/constellix/constellix.toml +++ b/providers/dns/constellix/constellix.toml @@ -7,7 +7,7 @@ Since = "v3.4.0" Example = ''' CONSTELLIX_API_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ CONSTELLIX_SECRET_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \ -lego --email you@example.com --dns constellix -d '*.example.com' -d example.com run +lego --dns constellix -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/corenetworks/corenetworks.toml b/providers/dns/corenetworks/corenetworks.toml index 8546d8723..09840bb1b 100644 --- a/providers/dns/corenetworks/corenetworks.toml +++ b/providers/dns/corenetworks/corenetworks.toml @@ -7,7 +7,7 @@ Since = "v4.20.0" Example = ''' CORENETWORKS_LOGIN="xxxx" \ CORENETWORKS_PASSWORD="yyyy" \ -lego --email you@example.com --dns corenetworks -d '*.example.com' -d example.com run +lego --dns corenetworks -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/cpanel/cpanel.toml b/providers/dns/cpanel/cpanel.toml index faed2abe2..b64adf0cf 100644 --- a/providers/dns/cpanel/cpanel.toml +++ b/providers/dns/cpanel/cpanel.toml @@ -10,7 +10,7 @@ Example = ''' CPANEL_USERNAME="yyyy" \ CPANEL_TOKEN="xxxx" \ CPANEL_BASE_URL="https://example.com:2083" \ -lego --email you@example.com --dns cpanel -d '*.example.com' -d example.com run +lego --dns cpanel -d '*.example.com' -d example.com run ## WHM @@ -18,7 +18,7 @@ CPANEL_MODE=whm \ CPANEL_USERNAME="yyyy" \ CPANEL_TOKEN="xxxx" \ CPANEL_BASE_URL="https://example.com:2087" \ -lego --email you@example.com --dns cpanel -d '*.example.com' -d example.com run +lego --dns cpanel -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/derak/derak.toml b/providers/dns/derak/derak.toml index 45d7e1fcf..72f49883a 100644 --- a/providers/dns/derak/derak.toml +++ b/providers/dns/derak/derak.toml @@ -6,7 +6,7 @@ Since = "v4.12.0" Example = ''' DERAK_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns derak -d '*.example.com' -d example.com run +lego --dns derak -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/desec/desec.toml b/providers/dns/desec/desec.toml index a79b38cd3..f7e66ae07 100644 --- a/providers/dns/desec/desec.toml +++ b/providers/dns/desec/desec.toml @@ -6,7 +6,7 @@ Since = "v3.7.0" Example = ''' DESEC_TOKEN=x-xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns desec -d '*.example.com' -d example.com run +lego --dns desec -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/designate/designate.toml b/providers/dns/designate/designate.toml index 3ea6260a6..a36034f64 100644 --- a/providers/dns/designate/designate.toml +++ b/providers/dns/designate/designate.toml @@ -7,7 +7,7 @@ Since = "v2.2.0" Example = ''' # With a `clouds.yaml` OS_CLOUD=my_openstack \ -lego --email you@example.com --dns designate -d '*.example.com' -d example.com run +lego --dns designate -d '*.example.com' -d example.com run # or @@ -16,7 +16,7 @@ OS_REGION_NAME=RegionOne \ OS_PROJECT_ID=23d4522a987d4ab529f722a007c27846 OS_USERNAME=myuser \ OS_PASSWORD=passw0rd \ -lego --email you@example.com --dns designate -d '*.example.com' -d example.com run +lego --dns designate -d '*.example.com' -d example.com run # or @@ -25,7 +25,7 @@ OS_REGION_NAME=RegionOne \ OS_AUTH_TYPE=v3applicationcredential \ OS_APPLICATION_CREDENTIAL_ID=imn74uq0or7dyzz20dwo1ytls4me8dry \ OS_APPLICATION_CREDENTIAL_SECRET=68FuSPSdQqkFQYH5X1OoriEIJOwyLtQ8QSqXZOc9XxFK1A9tzZT6He2PfPw0OMja \ -lego --email you@example.com --dns designate -d '*.example.com' -d example.com run +lego --dns designate -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/digitalocean/digitalocean.toml b/providers/dns/digitalocean/digitalocean.toml index b30d986f2..8f9107c26 100644 --- a/providers/dns/digitalocean/digitalocean.toml +++ b/providers/dns/digitalocean/digitalocean.toml @@ -6,7 +6,7 @@ Since = "v0.3.0" Example = ''' DO_AUTH_TOKEN=xxxxxx \ -lego --email you@example.com --dns digitalocean -d '*.example.com' -d example.com run +lego --dns digitalocean -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/directadmin/directadmin.toml b/providers/dns/directadmin/directadmin.toml index bd1c9316a..294eaca1c 100644 --- a/providers/dns/directadmin/directadmin.toml +++ b/providers/dns/directadmin/directadmin.toml @@ -8,7 +8,7 @@ Example = ''' DIRECTADMIN_API_URL="http://example.com:2222" \ DIRECTADMIN_USERNAME=xxxx \ DIRECTADMIN_PASSWORD=yyy \ -lego --email you@example.com --dns directadmin -d '*.example.com' -d example.com run +lego --dns directadmin -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/dnshomede/dnshomede.toml b/providers/dns/dnshomede/dnshomede.toml index bc52bb6dd..9c3b65277 100644 --- a/providers/dns/dnshomede/dnshomede.toml +++ b/providers/dns/dnshomede/dnshomede.toml @@ -6,10 +6,10 @@ Since = "v4.10.0" Example = ''' DNSHOMEDE_CREDENTIALS=example.org:password \ -lego --email you@example.com --dns dnshomede -d '*.example.com' -d example.com run +lego --dns dnshomede -d '*.example.com' -d example.com run DNSHOMEDE_CREDENTIALS=my.example.org:password1,demo.example.org:password2 \ -lego --email you@example.com --dns dnshomede -d my.example.org -d demo.example.org +lego --dns dnshomede -d my.example.org -d demo.example.org ''' [Configuration] diff --git a/providers/dns/dnsimple/dnsimple.toml b/providers/dns/dnsimple/dnsimple.toml index dcf999136..158fb7011 100644 --- a/providers/dns/dnsimple/dnsimple.toml +++ b/providers/dns/dnsimple/dnsimple.toml @@ -6,7 +6,7 @@ Since = "v0.3.0" Example = ''' DNSIMPLE_OAUTH_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \ -lego --email you@example.com --dns dnsimple -d '*.example.com' -d example.com run +lego --dns dnsimple -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/dnsmadeeasy/dnsmadeeasy.toml b/providers/dns/dnsmadeeasy/dnsmadeeasy.toml index 11a5f85ac..d71ab5303 100644 --- a/providers/dns/dnsmadeeasy/dnsmadeeasy.toml +++ b/providers/dns/dnsmadeeasy/dnsmadeeasy.toml @@ -7,7 +7,7 @@ Since = "v0.4.0" Example = ''' DNSMADEEASY_API_KEY=xxxxxx \ DNSMADEEASY_API_SECRET=yyyyy \ -lego --email you@example.com --dns dnsmadeeasy -d '*.example.com' -d example.com run +lego --dns dnsmadeeasy -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/dnspod/dnspod.toml b/providers/dns/dnspod/dnspod.toml index a0bf50e31..162685d76 100644 --- a/providers/dns/dnspod/dnspod.toml +++ b/providers/dns/dnspod/dnspod.toml @@ -8,7 +8,7 @@ Since = "v0.4.0" Example = ''' DNSPOD_API_KEY=xxxxxx \ -lego --email you@example.com --dns dnspod -d '*.example.com' -d example.com run +lego --dns dnspod -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/dode/dode.toml b/providers/dns/dode/dode.toml index a96e9ee43..eb629bb3e 100644 --- a/providers/dns/dode/dode.toml +++ b/providers/dns/dode/dode.toml @@ -6,7 +6,7 @@ Since = "v2.4.0" Example = ''' DODE_TOKEN=xxxxxx \ -lego --email you@example.com --dns dode -d '*.example.com' -d example.com run +lego --dns dode -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/domeneshop/domeneshop.toml b/providers/dns/domeneshop/domeneshop.toml index a8d2a1064..b74af598e 100644 --- a/providers/dns/domeneshop/domeneshop.toml +++ b/providers/dns/domeneshop/domeneshop.toml @@ -8,7 +8,7 @@ Since = "v4.3.0" Example = ''' DOMENESHOP_API_TOKEN= \ DOMENESHOP_API_SECRET= \ -lego --email example@example.com --dns domeneshop -d '*.example.com' -d example.com run +lego --dns domeneshop -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/dreamhost/dreamhost.toml b/providers/dns/dreamhost/dreamhost.toml index 4345e9ece..c3a9db360 100644 --- a/providers/dns/dreamhost/dreamhost.toml +++ b/providers/dns/dreamhost/dreamhost.toml @@ -6,7 +6,7 @@ Since = "v1.1.0" Example = ''' DREAMHOST_API_KEY="YOURAPIKEY" \ -lego --email you@example.com --dns dreamhost -d '*.example.com' -d example.com run +lego --dns dreamhost -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/duckdns/duckdns.toml b/providers/dns/duckdns/duckdns.toml index 9c0b3a6be..6866da57c 100644 --- a/providers/dns/duckdns/duckdns.toml +++ b/providers/dns/duckdns/duckdns.toml @@ -6,7 +6,7 @@ Since = "v0.5.0" Example = ''' DUCKDNS_TOKEN=xxxxxx \ -lego --email you@example.com --dns duckdns -d '*.example.com' -d example.com run +lego --dns duckdns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/dyn/dyn.toml b/providers/dns/dyn/dyn.toml index 4b0d3e652..c4b3563e0 100644 --- a/providers/dns/dyn/dyn.toml +++ b/providers/dns/dyn/dyn.toml @@ -8,7 +8,7 @@ Example = ''' DYN_CUSTOMER_NAME=xxxxxx \ DYN_USER_NAME=yyyyy \ DYN_PASSWORD=zzzz \ -lego --email you@example.com --dns dyn -d '*.example.com' -d example.com run +lego --dns dyn -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/dyndnsfree/dyndnsfree.toml b/providers/dns/dyndnsfree/dyndnsfree.toml index dd354fb33..e64bb0080 100644 --- a/providers/dns/dyndnsfree/dyndnsfree.toml +++ b/providers/dns/dyndnsfree/dyndnsfree.toml @@ -7,7 +7,7 @@ Since = "v4.23.0" Example = ''' DYNDNSFREE_USERNAME="xxx" \ DYNDNSFREE_PASSWORD="yyy" \ -lego --email you@example.com --dns dyndnsfree -d '*.example.com' -d example.com run +lego --dns dyndnsfree -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/dynu/dynu.toml b/providers/dns/dynu/dynu.toml index ba59034dd..ae2367087 100644 --- a/providers/dns/dynu/dynu.toml +++ b/providers/dns/dynu/dynu.toml @@ -6,7 +6,7 @@ Since = "v3.5.0" Example = ''' DYNU_API_KEY=1234567890abcdefghijklmnopqrstuvwxyz \ -lego --email you@example.com --dns dynu -d '*.example.com' -d example.com run +lego --dns dynu -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/easydns/easydns.toml b/providers/dns/easydns/easydns.toml index 71521bbd6..307c86a09 100644 --- a/providers/dns/easydns/easydns.toml +++ b/providers/dns/easydns/easydns.toml @@ -7,7 +7,7 @@ Since = "v2.6.0" Example = ''' EASYDNS_TOKEN=xxx \ EASYDNS_KEY=yyy \ -lego --email you@example.com --dns easydns -d '*.example.com' -d example.com run +lego --dns easydns -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/edgecenter/edgecenter.toml b/providers/dns/edgecenter/edgecenter.toml index 0cd4b0cb6..1c9e9b2a9 100644 --- a/providers/dns/edgecenter/edgecenter.toml +++ b/providers/dns/edgecenter/edgecenter.toml @@ -6,7 +6,7 @@ Since = "v4.29.0" Example = ''' EDGECENTER_PERMANENT_API_TOKEN=xxxxx \ -lego --email you@example.com --dns edgecenter -d '*.example.com' -d example.com run +lego --dns edgecenter -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/edgedns/edgedns.toml b/providers/dns/edgedns/edgedns.toml index d40d5cc03..7c7c5b3aa 100644 --- a/providers/dns/edgedns/edgedns.toml +++ b/providers/dns/edgedns/edgedns.toml @@ -12,7 +12,7 @@ AKAMAI_CLIENT_SECRET=abcdefghijklmnopqrstuvwxyz1234567890ABCDEFG= \ AKAMAI_CLIENT_TOKEN=akab-mnbvcxzlkjhgfdsapoiuytrewq1234567 \ AKAMAI_HOST=akab-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.luna.akamaiapis.net \ AKAMAI_ACCESS_TOKEN=akab-1234567890qwerty-asdfghjklzxcvtnu \ -lego --email you@example.com --dns edgedns -d '*.example.com' -d example.com run +lego --dns edgedns -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/edgeone/edgeone.toml b/providers/dns/edgeone/edgeone.toml index a33af75b2..05b8bc516 100644 --- a/providers/dns/edgeone/edgeone.toml +++ b/providers/dns/edgeone/edgeone.toml @@ -7,7 +7,7 @@ Since = "v4.26.0" Example = ''' EDGEONE_SECRET_ID=abcdefghijklmnopqrstuvwx \ EDGEONE_SECRET_KEY=your-secret-key \ -lego --email you@example.com --dns edgeone -d '*.example.com' -d example.com run +lego --dns edgeone -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/efficientip/efficientip.toml b/providers/dns/efficientip/efficientip.toml index 565c9575b..6e1874319 100644 --- a/providers/dns/efficientip/efficientip.toml +++ b/providers/dns/efficientip/efficientip.toml @@ -9,7 +9,7 @@ EFFICIENTIP_USERNAME="user" \ EFFICIENTIP_PASSWORD="secret" \ EFFICIENTIP_HOSTNAME="ipam.example.org" \ EFFICIENTIP_DNS_NAME="dns.smart" \ -lego --email you@example.com --dns efficientip -d '*.example.com' -d example.com run +lego --dns efficientip -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/epik/epik.toml b/providers/dns/epik/epik.toml index 7b4688609..faf453581 100644 --- a/providers/dns/epik/epik.toml +++ b/providers/dns/epik/epik.toml @@ -6,7 +6,7 @@ Since = "v4.5.0" Example = ''' EPIK_SIGNATURE=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns epik -d '*.example.com' -d example.com run +lego --dns epik -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/exec/exec.toml b/providers/dns/exec/exec.toml index 4c8d70b1c..2f9c77c67 100644 --- a/providers/dns/exec/exec.toml +++ b/providers/dns/exec/exec.toml @@ -6,7 +6,7 @@ Since = "v0.5.0" Example = ''' EXEC_PATH=/the/path/to/myscript.sh \ -lego --email you@example.com --dns exec -d '*.example.com' -d example.com run +lego --dns exec -d '*.example.com' -d example.com run ''' Additional = ''' @@ -39,7 +39,7 @@ For example, requesting a certificate for the domain 'my.example.org' can be ach ```bash EXEC_PATH=./update-dns.sh \ -lego --email you@example.com --dns exec --d my.example.org run +lego --dns exec --d my.example.org run ``` It will then call the program './update-dns.sh' with like this: @@ -59,7 +59,7 @@ If you want to use the raw domain, token, and keyAuth values with your program, ```bash EXEC_MODE=RAW \ EXEC_PATH=./update-dns.sh \ -lego --email you@example.com --dns exec -d my.example.org run +lego --dns exec -d my.example.org run ``` It will then call the program `./update-dns.sh` like this: diff --git a/providers/dns/exoscale/exoscale.toml b/providers/dns/exoscale/exoscale.toml index 82c005d26..bcc912b07 100644 --- a/providers/dns/exoscale/exoscale.toml +++ b/providers/dns/exoscale/exoscale.toml @@ -7,7 +7,7 @@ Since = "v0.4.0" Example = ''' EXOSCALE_API_KEY=abcdefghijklmnopqrstuvwx \ EXOSCALE_API_SECRET=xxxxxxx \ -lego --email you@example.com --dns exoscale -d '*.example.com' -d example.com run +lego --dns exoscale -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/f5xc/f5xc.toml b/providers/dns/f5xc/f5xc.toml index f5a843c97..6be604ddd 100644 --- a/providers/dns/f5xc/f5xc.toml +++ b/providers/dns/f5xc/f5xc.toml @@ -8,7 +8,7 @@ Example = ''' F5XC_API_TOKEN="xxx" \ F5XC_TENANT_NAME="yyy" \ F5XC_GROUP_NAME="zzz" \ -lego --email you@example.com --dns f5xc -d '*.example.com' -d example.com run +lego --dns f5xc -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/freemyip/freemyip.toml b/providers/dns/freemyip/freemyip.toml index 4821e2a9c..adbf9e213 100644 --- a/providers/dns/freemyip/freemyip.toml +++ b/providers/dns/freemyip/freemyip.toml @@ -6,7 +6,7 @@ Since = "v4.5.0" Example = ''' FREEMYIP_TOKEN=xxxxxx \ -lego --email you@example.com --dns freemyip -d '*.example.com' -d example.com run +lego --dns freemyip -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/gandi/gandi.toml b/providers/dns/gandi/gandi.toml index 96d5233be..23d7de5db 100644 --- a/providers/dns/gandi/gandi.toml +++ b/providers/dns/gandi/gandi.toml @@ -6,7 +6,7 @@ Since = "v0.3.0" Example = ''' GANDI_API_KEY=abcdefghijklmnopqrstuvwx \ -lego --email you@example.com --dns gandi -d '*.example.com' -d example.com run +lego --dns gandi -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/gandiv5/gandiv5.toml b/providers/dns/gandiv5/gandiv5.toml index 246b03524..31568e89b 100644 --- a/providers/dns/gandiv5/gandiv5.toml +++ b/providers/dns/gandiv5/gandiv5.toml @@ -6,7 +6,7 @@ Since = "v0.5.0" Example = ''' GANDIV5_PERSONAL_ACCESS_TOKEN=abcdefghijklmnopqrstuvwx \ -lego --email you@example.com --dns gandiv5 -d '*.example.com' -d example.com run +lego --dns gandiv5 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/gcloud/gcloud.toml b/providers/dns/gcloud/gcloud.toml index 471e2e9d1..63d22bed3 100644 --- a/providers/dns/gcloud/gcloud.toml +++ b/providers/dns/gcloud/gcloud.toml @@ -8,18 +8,18 @@ Example = ''' # Using a service account file GCE_PROJECT="gc-project-id" \ GCE_SERVICE_ACCOUNT_FILE="/path/to/svc/account/file.json" \ -lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run +lego --dns gcloud -d '*.example.com' -d example.com run # Using default credentials with impersonation GCE_PROJECT="gc-project-id" \ GCE_IMPERSONATE_SERVICE_ACCOUNT="target-sa@gc-project-id.iam.gserviceaccount.com" \ -lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run +lego --dns gcloud -d '*.example.com' -d example.com run # Using service account key with impersonation GCE_PROJECT="gc-project-id" \ GCE_SERVICE_ACCOUNT_FILE="/path/to/svc/account/file.json" \ GCE_IMPERSONATE_SERVICE_ACCOUNT="target-sa@gc-project-id.iam.gserviceaccount.com" \ -lego --email you@example.com --dns gcloud -d '*.example.com' -d example.com run +lego --dns gcloud -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/gcore/gcore.toml b/providers/dns/gcore/gcore.toml index 986455e80..983c35f8a 100644 --- a/providers/dns/gcore/gcore.toml +++ b/providers/dns/gcore/gcore.toml @@ -6,7 +6,7 @@ Since = "v4.5.0" Example = ''' GCORE_PERMANENT_API_TOKEN=xxxxx \ -lego --email you@example.com --dns gcore -d '*.example.com' -d example.com run +lego --dns gcore -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/gigahostno/gigahostno.toml b/providers/dns/gigahostno/gigahostno.toml index 689b96569..b8d3fad2b 100644 --- a/providers/dns/gigahostno/gigahostno.toml +++ b/providers/dns/gigahostno/gigahostno.toml @@ -7,7 +7,7 @@ Since = "v4.29.0" Example = ''' GIGAHOSTNO_USERNAME="xxxxxxxxxxxxxxxxxxxxx" \ GIGAHOSTNO_PASSWORD="yyyyyyyyyyyyyyyyyyyyy" \ -lego --email you@example.com --dns gigahostno -d '*.example.com' -d example.com run +lego --dns gigahostno -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/glesys/glesys.toml b/providers/dns/glesys/glesys.toml index 1bdd43c2b..c0e2613b8 100644 --- a/providers/dns/glesys/glesys.toml +++ b/providers/dns/glesys/glesys.toml @@ -7,7 +7,7 @@ Since = "v0.5.0" Example = ''' GLESYS_API_USER=xxxxx \ GLESYS_API_KEY=yyyyy \ -lego --email you@example.com --dns glesys -d '*.example.com' -d example.com run +lego --dns glesys -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/godaddy/godaddy.toml b/providers/dns/godaddy/godaddy.toml index acf0bf404..b906605b3 100644 --- a/providers/dns/godaddy/godaddy.toml +++ b/providers/dns/godaddy/godaddy.toml @@ -7,7 +7,7 @@ Since = "v0.5.0" Example = ''' GODADDY_API_KEY=xxxxxxxx \ GODADDY_API_SECRET=yyyyyyyy \ -lego --email you@example.com --dns godaddy -d '*.example.com' -d example.com run +lego --dns godaddy -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/googledomains/googledomains.toml b/providers/dns/googledomains/googledomains.toml index 1ac7e5e54..52330795d 100644 --- a/providers/dns/googledomains/googledomains.toml +++ b/providers/dns/googledomains/googledomains.toml @@ -8,7 +8,7 @@ Since = "v4.11.0" Example = ''' GOOGLE_DOMAINS_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns googledomains -d '*.example.com' -d example.com run +lego --dns googledomains -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/gravity/gravity.toml b/providers/dns/gravity/gravity.toml index 6010e26e1..87a303839 100644 --- a/providers/dns/gravity/gravity.toml +++ b/providers/dns/gravity/gravity.toml @@ -8,7 +8,7 @@ Example = ''' GRAVITY_SERVER_URL="https://example.org:1234" \ GRAVITY_USERNAME="xxxxxxxxxxxxxxxxxxxxx" \ GRAVITY_PASSWORD="yyyyyyyyyyyyyyyyyyyyy" \ -lego --email you@example.com --dns gravity -d '*.example.com' -d example.com run +lego --dns gravity -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/hetzner/hetzner.toml b/providers/dns/hetzner/hetzner.toml index ee1f9a970..40d4cea72 100644 --- a/providers/dns/hetzner/hetzner.toml +++ b/providers/dns/hetzner/hetzner.toml @@ -6,7 +6,7 @@ Since = "v3.7.0" Example = ''' HETZNER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns hetzner -d '*.example.com' -d example.com run +lego --dns hetzner -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/hostingde/hostingde.toml b/providers/dns/hostingde/hostingde.toml index 569e8a781..502a7fe9e 100644 --- a/providers/dns/hostingde/hostingde.toml +++ b/providers/dns/hostingde/hostingde.toml @@ -6,7 +6,7 @@ Since = "v1.1.0" Example = ''' HOSTINGDE_API_KEY=xxxxxxxx \ -lego --email you@example.com --dns hostingde -d '*.example.com' -d example.com run +lego --dns hostingde -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/hostinger/hostinger.toml b/providers/dns/hostinger/hostinger.toml index f49e447ed..a6f152e73 100644 --- a/providers/dns/hostinger/hostinger.toml +++ b/providers/dns/hostinger/hostinger.toml @@ -6,7 +6,7 @@ Since = "v4.27.0" Example = ''' HOSTINGER_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns hostinger -d '*.example.com' -d example.com run +lego --dns hostinger -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/hostingnl/hostingnl.toml b/providers/dns/hostingnl/hostingnl.toml index a26c07ab2..943264ed3 100644 --- a/providers/dns/hostingnl/hostingnl.toml +++ b/providers/dns/hostingnl/hostingnl.toml @@ -6,7 +6,7 @@ Since = "v4.30.0" Example = ''' HOSTINGNL_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns hostingnl -d '*.example.com' -d example.com run +lego --dns hostingnl -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/hosttech/hosttech.toml b/providers/dns/hosttech/hosttech.toml index 5d7555499..52c01fd31 100644 --- a/providers/dns/hosttech/hosttech.toml +++ b/providers/dns/hosttech/hosttech.toml @@ -6,7 +6,7 @@ Since = "v4.5.0" Example = ''' HOSTTECH_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns hosttech -d '*.example.com' -d example.com run +lego --dns hosttech -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/httpnet/httpnet.toml b/providers/dns/httpnet/httpnet.toml index 204f5bc54..3dd581204 100644 --- a/providers/dns/httpnet/httpnet.toml +++ b/providers/dns/httpnet/httpnet.toml @@ -6,7 +6,7 @@ Since = "v4.15.0" Example = ''' HTTPNET_API_KEY=xxxxxxxx \ -lego --email you@example.com --dns httpnet -d '*.example.com' -d example.com run +lego --dns httpnet -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/httpreq/httpreq.toml b/providers/dns/httpreq/httpreq.toml index 6c3f8719b..d64d61a6c 100644 --- a/providers/dns/httpreq/httpreq.toml +++ b/providers/dns/httpreq/httpreq.toml @@ -6,7 +6,7 @@ Since = "v2.0.0" Example = ''' HTTPREQ_ENDPOINT=http://my.server.com:9090 \ -lego --email you@example.com --dns httpreq -d '*.example.com' -d example.com run +lego --dns httpreq -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/huaweicloud/huaweicloud.toml b/providers/dns/huaweicloud/huaweicloud.toml index f7991dfae..e8d417c11 100644 --- a/providers/dns/huaweicloud/huaweicloud.toml +++ b/providers/dns/huaweicloud/huaweicloud.toml @@ -8,7 +8,7 @@ Example = ''' HUAWEICLOUD_ACCESS_KEY_ID=your-access-key-id \ HUAWEICLOUD_SECRET_ACCESS_KEY=your-secret-access-key \ HUAWEICLOUD_REGION=cn-south-1 \ -lego --email you@example.com --dns huaweicloud -d '*.example.com' -d example.com run +lego --dns huaweicloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/hurricane/hurricane.toml b/providers/dns/hurricane/hurricane.toml index 033c73984..10b370e4f 100644 --- a/providers/dns/hurricane/hurricane.toml +++ b/providers/dns/hurricane/hurricane.toml @@ -6,10 +6,10 @@ Since = "v4.3.0" Example = ''' HURRICANE_TOKENS=example.org:token \ -lego --email you@example.com --dns hurricane -d '*.example.com' -d example.com run +lego --dns hurricane -d '*.example.com' -d example.com run HURRICANE_TOKENS=my.example.org:token1,demo.example.org:token2 \ -lego --email you@example.com --dns hurricane -d my.example.org -d demo.example.org +lego --dns hurricane -d my.example.org -d demo.example.org ''' Additional = """ diff --git a/providers/dns/hyperone/hyperone.toml b/providers/dns/hyperone/hyperone.toml index 0f23976c4..88814356f 100644 --- a/providers/dns/hyperone/hyperone.toml +++ b/providers/dns/hyperone/hyperone.toml @@ -5,7 +5,7 @@ Code = "hyperone" Since = "v3.9.0" Example = ''' -lego --email you@example.com --dns hyperone -d '*.example.com' -d example.com run +lego --dns hyperone -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/ibmcloud/ibmcloud.toml b/providers/dns/ibmcloud/ibmcloud.toml index 2a6c12f82..01088f09b 100644 --- a/providers/dns/ibmcloud/ibmcloud.toml +++ b/providers/dns/ibmcloud/ibmcloud.toml @@ -7,7 +7,7 @@ Since = "v4.5.0" Example = ''' SOFTLAYER_USERNAME=xxxxx \ SOFTLAYER_API_KEY=yyyyy \ -lego --email you@example.com --dns ibmcloud -d '*.example.com' -d example.com run +lego --dns ibmcloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/iij/iij.toml b/providers/dns/iij/iij.toml index 8dbf5ba1a..95355200a 100644 --- a/providers/dns/iij/iij.toml +++ b/providers/dns/iij/iij.toml @@ -8,7 +8,7 @@ Example = ''' IIJ_API_ACCESS_KEY=xxxxxxxx \ IIJ_API_SECRET_KEY=yyyyyy \ IIJ_DO_SERVICE_CODE=zzzzzz \ -lego --email you@example.com --dns iij -d '*.example.com' -d example.com run +lego --dns iij -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/iijdpf/iijdpf.toml b/providers/dns/iijdpf/iijdpf.toml index 4aaa9ca37..650285f95 100644 --- a/providers/dns/iijdpf/iijdpf.toml +++ b/providers/dns/iijdpf/iijdpf.toml @@ -7,7 +7,7 @@ Since = "v4.7.0" Example = ''' IIJ_DPF_API_TOKEN=xxxxxxxx \ IIJ_DPF_DPM_SERVICE_CODE=yyyyyy \ -lego --email you@example.com --dns iijdpf -d '*.example.com' -d example.com run +lego --dns iijdpf -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/infoblox/infoblox.toml b/providers/dns/infoblox/infoblox.toml index 3c2632042..0e6972d3a 100644 --- a/providers/dns/infoblox/infoblox.toml +++ b/providers/dns/infoblox/infoblox.toml @@ -8,7 +8,7 @@ Example = ''' INFOBLOX_USERNAME=api-user-529 \ INFOBLOX_PASSWORD=b9841238feb177a84330febba8a83208921177bffe733 \ INFOBLOX_HOST=infoblox.example.org -lego --email you@example.com --dns infoblox -d '*.example.com' -d example.com run +lego --dns infoblox -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/infomaniak/infomaniak.toml b/providers/dns/infomaniak/infomaniak.toml index 283838053..d924e3a26 100644 --- a/providers/dns/infomaniak/infomaniak.toml +++ b/providers/dns/infomaniak/infomaniak.toml @@ -6,7 +6,7 @@ Since = "v4.1.0" Example = ''' INFOMANIAK_ACCESS_TOKEN=1234567898765432 \ -lego --email you@example.com --dns infomaniak -d '*.example.com' -d example.com run +lego --dns infomaniak -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/internetbs/internetbs.toml b/providers/dns/internetbs/internetbs.toml index d25418f22..f22850253 100644 --- a/providers/dns/internetbs/internetbs.toml +++ b/providers/dns/internetbs/internetbs.toml @@ -7,7 +7,7 @@ Since = "v4.5.0" Example = ''' INTERNET_BS_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxx \ INTERNET_BS_PASSWORD=yyyyyyyyyyyyyyyyyyyyyyyyyy \ -lego --email you@example.com --dns internetbs -d '*.example.com' -d example.com run +lego --dns internetbs -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/inwx/inwx.toml b/providers/dns/inwx/inwx.toml index aeab5a242..da4c6d959 100644 --- a/providers/dns/inwx/inwx.toml +++ b/providers/dns/inwx/inwx.toml @@ -7,13 +7,13 @@ Since = "v2.0.0" Example = ''' INWX_USERNAME=xxxxxxxxxx \ INWX_PASSWORD=yyyyyyyyyy \ -lego --email you@example.com --dns inwx -d '*.example.com' -d example.com run +lego --dns inwx -d '*.example.com' -d example.com run # 2FA INWX_USERNAME=xxxxxxxxxx \ INWX_PASSWORD=yyyyyyyyyy \ INWX_SHARED_SECRET=zzzzzzzzzz \ -lego --email you@example.com --dns inwx -d '*.example.com' -d example.com run +lego --dns inwx -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/ionos/ionos.toml b/providers/dns/ionos/ionos.toml index 0c905273f..a2c9518fb 100644 --- a/providers/dns/ionos/ionos.toml +++ b/providers/dns/ionos/ionos.toml @@ -6,7 +6,7 @@ Since = "v4.2.0" Example = ''' IONOS_API_KEY=xxxxxxxx \ -lego --email you@example.com --dns ionos -d '*.example.com' -d example.com run +lego --dns ionos -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/ionoscloud/ionoscloud.toml b/providers/dns/ionoscloud/ionoscloud.toml index a8fedce6c..6e1d080e4 100644 --- a/providers/dns/ionoscloud/ionoscloud.toml +++ b/providers/dns/ionoscloud/ionoscloud.toml @@ -6,7 +6,7 @@ Since = "v4.30.0" Example = ''' IONOSCLOUD_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns ionoscloud -d '*.example.com' -d example.com run +lego --dns ionoscloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/ipv64/ipv64.toml b/providers/dns/ipv64/ipv64.toml index fba210bdb..aa1720c9e 100644 --- a/providers/dns/ipv64/ipv64.toml +++ b/providers/dns/ipv64/ipv64.toml @@ -6,7 +6,7 @@ Since = "v4.13.0" Example = ''' IPV64_API_KEY=xxxxxx \ -lego --email you@example.com --dns ipv64 -d '*.example.com' -d example.com run +lego --dns ipv64 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/ispconfig/ispconfig.toml b/providers/dns/ispconfig/ispconfig.toml index 399544742..4defd5509 100644 --- a/providers/dns/ispconfig/ispconfig.toml +++ b/providers/dns/ispconfig/ispconfig.toml @@ -8,7 +8,7 @@ Example = ''' ISPCONFIG_SERVER_URL="https://example.com:8080/remote/json.php" \ ISPCONFIG_USERNAME="xxx" \ ISPCONFIG_PASSWORD="yyy" \ -lego --email you@example.com --dns ispconfig -d '*.example.com' -d example.com run +lego --dns ispconfig -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/ispconfigddns/ispconfigddns.toml b/providers/dns/ispconfigddns/ispconfigddns.toml index 84e82904f..158ee9fbd 100644 --- a/providers/dns/ispconfigddns/ispconfigddns.toml +++ b/providers/dns/ispconfigddns/ispconfigddns.toml @@ -7,7 +7,7 @@ Since = "v4.31.0" Example = ''' ISPCONFIG_DDNS_SERVER_URL="https://panel.example.com:8080" \ ISPCONFIG_DDNS_TOKEN=xxxxxx \ -lego --email you@example.com --dns ispconfigddns -d '*.example.com' -d example.com run +lego --dns ispconfigddns -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/iwantmyname/iwantmyname.toml b/providers/dns/iwantmyname/iwantmyname.toml index a138dee9e..a82c2b749 100644 --- a/providers/dns/iwantmyname/iwantmyname.toml +++ b/providers/dns/iwantmyname/iwantmyname.toml @@ -11,7 +11,7 @@ Since = "v4.7.0" Example = ''' IWANTMYNAME_USERNAME=xxxxxxxx \ IWANTMYNAME_PASSWORD=xxxxxxxx \ -lego --email you@example.com --dns iwantmyname -d '*.example.com' -d example.com run +lego --dns iwantmyname -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/joker/joker.toml b/providers/dns/joker/joker.toml index 35713df18..20e481a6d 100644 --- a/providers/dns/joker/joker.toml +++ b/providers/dns/joker/joker.toml @@ -9,17 +9,17 @@ Example = ''' JOKER_API_MODE=SVC \ JOKER_USERNAME= \ JOKER_PASSWORD= \ -lego --email you@example.com --dns joker -d '*.example.com' -d example.com run +lego --dns joker -d '*.example.com' -d example.com run # DMAPI JOKER_API_MODE=DMAPI \ JOKER_USERNAME= \ JOKER_PASSWORD= \ -lego --email you@example.com --dns joker -d '*.example.com' -d example.com run +lego --dns joker -d '*.example.com' -d example.com run ## or JOKER_API_MODE=DMAPI \ JOKER_API_KEY= \ -lego --email you@example.com --dns joker -d '*.example.com' -d example.com run +lego --dns joker -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/keyhelp/keyhelp.toml b/providers/dns/keyhelp/keyhelp.toml index d6f84e34e..e622794ca 100644 --- a/providers/dns/keyhelp/keyhelp.toml +++ b/providers/dns/keyhelp/keyhelp.toml @@ -7,7 +7,7 @@ Since = "v4.26.0" Example = ''' KEYHELP_BASE_URL="https://keyhelp.example.com" \ KEYHELP_API_KEY="xxx" \ -lego --email you@example.com --dns keyhelp -d '*.example.com' -d example.com run +lego --dns keyhelp -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/liara/liara.toml b/providers/dns/liara/liara.toml index 1259999a2..4ed53ec75 100644 --- a/providers/dns/liara/liara.toml +++ b/providers/dns/liara/liara.toml @@ -6,7 +6,7 @@ Since = "v4.10.0" Example = ''' LIARA_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns liara -d '*.example.com' -d example.com run +lego --dns liara -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/limacity/limacity.toml b/providers/dns/limacity/limacity.toml index b9b9f0018..d236577d0 100644 --- a/providers/dns/limacity/limacity.toml +++ b/providers/dns/limacity/limacity.toml @@ -6,7 +6,7 @@ Since = "v4.18.0" Example = ''' LIMACITY_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns limacity -d '*.example.com' -d example.com run +lego --dns limacity -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/linode/linode.toml b/providers/dns/linode/linode.toml index f046d3f9b..9ea30b92b 100644 --- a/providers/dns/linode/linode.toml +++ b/providers/dns/linode/linode.toml @@ -7,7 +7,7 @@ Since = "v1.1.0" Example = ''' LINODE_TOKEN=xxxxx \ -lego --email you@example.com --dns linode -d '*.example.com' -d example.com run +lego --dns linode -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/liquidweb/liquidweb.toml b/providers/dns/liquidweb/liquidweb.toml index 22789f41e..386b99cab 100644 --- a/providers/dns/liquidweb/liquidweb.toml +++ b/providers/dns/liquidweb/liquidweb.toml @@ -7,7 +7,7 @@ Since = "v3.1.0" Example = ''' LWAPI_USERNAME=someuser \ LWAPI_PASSWORD=somepass \ -lego --email you@example.com --dns liquidweb -d '*.example.com' -d example.com run +lego --dns liquidweb -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/loopia/loopia.toml b/providers/dns/loopia/loopia.toml index 4a127ec55..a201852c9 100644 --- a/providers/dns/loopia/loopia.toml +++ b/providers/dns/loopia/loopia.toml @@ -7,7 +7,7 @@ Since = "v4.2.0" Example = ''' LOOPIA_API_USER=xxxxxxxx \ LOOPIA_API_PASSWORD=yyyyyyyy \ -lego --email you@example.com --dns loopia -d '*.example.com' -d example.com run +lego --dns loopia -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/luadns/luadns.toml b/providers/dns/luadns/luadns.toml index c80929c21..e56fac0b6 100644 --- a/providers/dns/luadns/luadns.toml +++ b/providers/dns/luadns/luadns.toml @@ -7,7 +7,7 @@ Since = "v3.7.0" Example = ''' LUADNS_API_USERNAME=youremail \ LUADNS_API_TOKEN=xxxxxxxx \ -lego --email you@example.com --dns luadns -d '*.example.com' -d example.com run +lego --dns luadns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/mailinabox/mailinabox.toml b/providers/dns/mailinabox/mailinabox.toml index e0072ebdd..74d8aabbc 100644 --- a/providers/dns/mailinabox/mailinabox.toml +++ b/providers/dns/mailinabox/mailinabox.toml @@ -8,7 +8,7 @@ Example = ''' MAILINABOX_EMAIL=user@example.com \ MAILINABOX_PASSWORD=yyyy \ MAILINABOX_BASE_URL=https://box.example.com \ -lego --email you@example.com --dns mailinabox -d '*.example.com' -d example.com run +lego --dns mailinabox -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/manageengine/manageengine.toml b/providers/dns/manageengine/manageengine.toml index 7708fa74f..43a782841 100644 --- a/providers/dns/manageengine/manageengine.toml +++ b/providers/dns/manageengine/manageengine.toml @@ -7,7 +7,7 @@ Since = "v4.21.0" Example = ''' MANAGEENGINE_CLIENT_ID="xxx" \ MANAGEENGINE_CLIENT_SECRET="yyy" \ -lego --email you@example.com --dns manageengine -d '*.example.com' -d example.com run +lego --dns manageengine -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/manual/manual.toml b/providers/dns/manual/manual.toml index 88acf4750..aca67536d 100644 --- a/providers/dns/manual/manual.toml +++ b/providers/dns/manual/manual.toml @@ -4,7 +4,7 @@ Code = "manual" Since = "v0.3.0" Example = ''' -lego --email you@example.com --dns manual -d '*.example.com' -d example.com run +lego --dns manual -d '*.example.com' -d example.com run ''' Additional = ''' @@ -13,7 +13,7 @@ Additional = ''' To start using the CLI prompt "provider", start lego with `--dns manual`: ```console -$ lego --email "you@example.com" --domains="example.com" --dns "manual" run +$ lego --dns manual -d example.com run ``` What follows are a few log print-outs, interspersed with some prompts, asking for you to do perform some actions: diff --git a/providers/dns/metaname/metaname.toml b/providers/dns/metaname/metaname.toml index 4a147d043..654dcaed0 100644 --- a/providers/dns/metaname/metaname.toml +++ b/providers/dns/metaname/metaname.toml @@ -7,7 +7,7 @@ Since = "v4.13.0" Example = ''' METANAME_ACCOUNT_REFERENCE=xxxx \ METANAME_API_KEY=yyyyyyy \ -lego --email you@example.com --dns metaname -d '*.example.com' -d example.com run +lego --dns metaname -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/metaregistrar/metaregistrar.toml b/providers/dns/metaregistrar/metaregistrar.toml index 952c7ea61..e505e0ce2 100644 --- a/providers/dns/metaregistrar/metaregistrar.toml +++ b/providers/dns/metaregistrar/metaregistrar.toml @@ -6,7 +6,7 @@ Since = "v4.23.0" Example = ''' METAREGISTRAR_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns metaregistrar -d '*.example.com' -d example.com run +lego --dns metaregistrar -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/mijnhost/mijnhost.toml b/providers/dns/mijnhost/mijnhost.toml index 00152e132..416fdde53 100644 --- a/providers/dns/mijnhost/mijnhost.toml +++ b/providers/dns/mijnhost/mijnhost.toml @@ -6,7 +6,7 @@ Since = "v4.18.0" Example = ''' MIJNHOST_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns mijnhost -d '*.example.com' -d example.com run +lego --dns mijnhost -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/mittwald/mittwald.toml b/providers/dns/mittwald/mittwald.toml index 937b9c172..36a9f6c16 100644 --- a/providers/dns/mittwald/mittwald.toml +++ b/providers/dns/mittwald/mittwald.toml @@ -6,7 +6,7 @@ Since = "v1.48.0" Example = ''' MITTWALD_TOKEN=my-token \ -lego --email you@example.com --dns mittwald -d '*.example.com' -d example.com run +lego --dns mittwald -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/myaddr/myaddr.toml b/providers/dns/myaddr/myaddr.toml index 5ff306526..2f5fe6c1f 100644 --- a/providers/dns/myaddr/myaddr.toml +++ b/providers/dns/myaddr/myaddr.toml @@ -6,7 +6,7 @@ Since = "v4.22.0" Example = ''' MYADDR_PRIVATE_KEYS_MAPPING="example:123,test:456" \ -lego --email you@example.com --dns myaddr -d '*.example.com' -d example.com run +lego --dns myaddr -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/mydnsjp/mydnsjp.toml b/providers/dns/mydnsjp/mydnsjp.toml index ab842e37f..eb9e73acc 100644 --- a/providers/dns/mydnsjp/mydnsjp.toml +++ b/providers/dns/mydnsjp/mydnsjp.toml @@ -7,7 +7,7 @@ Since = "v1.2.0" Example = ''' MYDNSJP_MASTER_ID=xxxxx \ MYDNSJP_PASSWORD=xxxxx \ -lego --email you@example.com --dns mydnsjp -d '*.example.com' -d example.com run +lego --dns mydnsjp -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/mythicbeasts/mythicbeasts.toml b/providers/dns/mythicbeasts/mythicbeasts.toml index 011abba1f..cada3041d 100644 --- a/providers/dns/mythicbeasts/mythicbeasts.toml +++ b/providers/dns/mythicbeasts/mythicbeasts.toml @@ -7,7 +7,7 @@ Since = "v0.3.7" Example = ''' MYTHICBEASTS_USERNAME=myuser \ MYTHICBEASTS_PASSWORD=mypass \ -lego --email you@example.com --dns mythicbeasts -d '*.example.com' -d example.com run +lego --dns mythicbeasts -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/namecheap/namecheap.toml b/providers/dns/namecheap/namecheap.toml index 3a5be870c..b0f92a1bd 100644 --- a/providers/dns/namecheap/namecheap.toml +++ b/providers/dns/namecheap/namecheap.toml @@ -14,7 +14,7 @@ More information in the section [Enabling API Access](https://www.namecheap.com/ Example = ''' NAMECHEAP_API_USER=user \ NAMECHEAP_API_KEY=key \ -lego --email you@example.com --dns namecheap -d '*.example.com' -d example.com run +lego --dns namecheap -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/namedotcom/namedotcom.toml b/providers/dns/namedotcom/namedotcom.toml index e6de796d1..3651c424b 100644 --- a/providers/dns/namedotcom/namedotcom.toml +++ b/providers/dns/namedotcom/namedotcom.toml @@ -7,7 +7,7 @@ Since = "v0.5.0" Example = ''' NAMECOM_USERNAME=foo.bar \ NAMECOM_API_TOKEN=a379a6f6eeafb9a55e378c118034e2751e682fab \ -lego --email you@example.com --dns namedotcom -d '*.example.com' -d example.com run +lego --dns namedotcom -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/namesilo/namesilo.toml b/providers/dns/namesilo/namesilo.toml index bab7905bf..113ddb5c5 100644 --- a/providers/dns/namesilo/namesilo.toml +++ b/providers/dns/namesilo/namesilo.toml @@ -6,7 +6,7 @@ Since = "v2.7.0" Example = ''' NAMESILO_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \ -lego --email you@example.com --dns namesilo -d '*.example.com' -d example.com run +lego --dns namesilo -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/nearlyfreespeech/nearlyfreespeech.toml b/providers/dns/nearlyfreespeech/nearlyfreespeech.toml index 80d4fd6bc..3a1e25942 100644 --- a/providers/dns/nearlyfreespeech/nearlyfreespeech.toml +++ b/providers/dns/nearlyfreespeech/nearlyfreespeech.toml @@ -7,7 +7,7 @@ Since = "v4.8.0" Example = ''' NEARLYFREESPEECH_API_KEY=xxxxxx \ NEARLYFREESPEECH_LOGIN=xxxx \ -lego --email you@example.com --dns nearlyfreespeech -d '*.example.com' -d example.com run +lego --dns nearlyfreespeech -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/neodigit/neodigit.toml b/providers/dns/neodigit/neodigit.toml index b391a6512..91b3cfb07 100644 --- a/providers/dns/neodigit/neodigit.toml +++ b/providers/dns/neodigit/neodigit.toml @@ -6,7 +6,7 @@ Since = "v4.30.0" Example = ''' NEODIGIT_TOKEN=xxxxxx \ -lego --email you@example.com --dns neodigit -d '*.example.com' -d example.com run +lego --dns neodigit -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/netcup/netcup.toml b/providers/dns/netcup/netcup.toml index 0df09b0df..4ef8688c6 100644 --- a/providers/dns/netcup/netcup.toml +++ b/providers/dns/netcup/netcup.toml @@ -8,7 +8,7 @@ Example = ''' NETCUP_CUSTOMER_NUMBER=xxxx \ NETCUP_API_KEY=yyyy \ NETCUP_API_PASSWORD=zzzz \ -lego --email you@example.com --dns netcup -d '*.example.com' -d example.com run +lego --dns netcup -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/netlify/netlify.toml b/providers/dns/netlify/netlify.toml index c5cb670f9..9d3c0f6b5 100644 --- a/providers/dns/netlify/netlify.toml +++ b/providers/dns/netlify/netlify.toml @@ -6,7 +6,7 @@ Since = "v3.7.0" Example = ''' NETLIFY_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns netlify -d '*.example.com' -d example.com run +lego --dns netlify -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/nicmanager/nicmanager.toml b/providers/dns/nicmanager/nicmanager.toml index 7fdf296c4..d5921de5a 100644 --- a/providers/dns/nicmanager/nicmanager.toml +++ b/providers/dns/nicmanager/nicmanager.toml @@ -13,7 +13,7 @@ NICMANAGER_API_PASSWORD = "password" \ # Optionally, if your account has TOTP enabled, set the secret here NICMANAGER_API_OTP = "long-secret" \ -lego --email you@example.com --dns nicmanager -d '*.example.com' -d example.com run +lego --dns nicmanager -d '*.example.com' -d example.com run ## Login using account name + username @@ -24,7 +24,7 @@ NICMANAGER_API_PASSWORD = "password" \ # Optionally, if your account has TOTP enabled, set the secret here NICMANAGER_API_OTP = "long-secret" \ -lego --email you@example.com --dns nicmanager -d '*.example.com' -d example.com run +lego --dns nicmanager -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/nicru/nicru.toml b/providers/dns/nicru/nicru.toml index 6bffe74a5..f955511a2 100644 --- a/providers/dns/nicru/nicru.toml +++ b/providers/dns/nicru/nicru.toml @@ -9,7 +9,7 @@ NICRU_USER="" \ NICRU_PASSWORD="" \ NICRU_SERVICE_ID="" \ NICRU_SECRET="" \ -lego --dns nicru --domains "*.example.com" --email you@example.com run +lego --dns nicru -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/nifcloud/nifcloud.toml b/providers/dns/nifcloud/nifcloud.toml index b692bb9d3..3c43b1dc0 100644 --- a/providers/dns/nifcloud/nifcloud.toml +++ b/providers/dns/nifcloud/nifcloud.toml @@ -7,7 +7,7 @@ Since = "v1.1.0" Example = ''' NIFCLOUD_ACCESS_KEY_ID=xxxx \ NIFCLOUD_SECRET_ACCESS_KEY=yyyy \ -lego --email you@example.com --dns nifcloud -d '*.example.com' -d example.com run +lego --dns nifcloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/njalla/njalla.toml b/providers/dns/njalla/njalla.toml index ef1fe158e..ff4750b7d 100644 --- a/providers/dns/njalla/njalla.toml +++ b/providers/dns/njalla/njalla.toml @@ -6,7 +6,7 @@ Since = "v4.3.0" Example = ''' NJALLA_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns njalla -d '*.example.com' -d example.com run +lego --dns njalla -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/nodion/nodion.toml b/providers/dns/nodion/nodion.toml index 0888f96c3..c9db46e61 100644 --- a/providers/dns/nodion/nodion.toml +++ b/providers/dns/nodion/nodion.toml @@ -6,7 +6,7 @@ Since = "v4.11.0" Example = ''' NODION_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns nodion -d '*.example.com' -d example.com run +lego --dns nodion -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/ns1/ns1.toml b/providers/dns/ns1/ns1.toml index 2a6b10deb..829663bf5 100644 --- a/providers/dns/ns1/ns1.toml +++ b/providers/dns/ns1/ns1.toml @@ -6,7 +6,7 @@ Since = "v0.4.0" Example = ''' NS1_API_KEY=xxxx \ -lego --email you@example.com --dns ns1 -d '*.example.com' -d example.com run +lego --dns ns1 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/octenium/octenium.toml b/providers/dns/octenium/octenium.toml index 5084526fd..e3c9d894f 100644 --- a/providers/dns/octenium/octenium.toml +++ b/providers/dns/octenium/octenium.toml @@ -6,7 +6,7 @@ Since = "v4.27.0" Example = ''' OCTENIUM_API_KEY="xxx" \ -lego --email you@example.com --dns octenium -d '*.example.com' -d example.com run +lego --dns octenium -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/oraclecloud/oraclecloud.toml b/providers/dns/oraclecloud/oraclecloud.toml index f13cb1e1e..f6155052e 100644 --- a/providers/dns/oraclecloud/oraclecloud.toml +++ b/providers/dns/oraclecloud/oraclecloud.toml @@ -13,13 +13,13 @@ OCI_USER_OCID="ocid1.user.oc1..secret" \ OCI_FINGERPRINT="00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00" \ OCI_REGION="us-phoenix-1" \ OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ -lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com run +lego --dns oraclecloud -d '*.example.com' -d example.com run # Using Instance Principal authentication (when running on OCI compute instances): # https://docs.oracle.com/en-us/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm OCI_AUTH_TYPE="instance_principal" \ OCI_COMPARTMENT_OCID="ocid1.tenancy.oc1..secret" \ -lego --email you@example.com --dns oraclecloud -d '*.example.com' -d example.com run +lego --dns oraclecloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/otc/otc.toml b/providers/dns/otc/otc.toml index 91f9f5455..e63077fda 100644 --- a/providers/dns/otc/otc.toml +++ b/providers/dns/otc/otc.toml @@ -9,7 +9,7 @@ OTC_DOMAIN_NAME=domain_name \ OTC_USER_NAME=user_name \ OTC_PASSWORD=password \ OTC_PROJECT_NAME=project_name \ -lego --email you@example.com --dns otc -d '*.example.com' -d example.com run +lego --dns otc -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/ovh/ovh.toml b/providers/dns/ovh/ovh.toml index 95162185b..abf22bd7a 100644 --- a/providers/dns/ovh/ovh.toml +++ b/providers/dns/ovh/ovh.toml @@ -11,20 +11,20 @@ OVH_APPLICATION_KEY=1234567898765432 \ OVH_APPLICATION_SECRET=b9841238feb177a84330febba8a832089 \ OVH_CONSUMER_KEY=256vfsd347245sdfg \ OVH_ENDPOINT=ovh-eu \ -lego --email you@example.com --dns ovh -d '*.example.com' -d example.com run +lego --dns ovh -d '*.example.com' -d example.com run # Or Access Token: OVH_ACCESS_TOKEN=xxx \ OVH_ENDPOINT=ovh-eu \ -lego --email you@example.com --dns ovh -d '*.example.com' -d example.com run +lego --dns ovh -d '*.example.com' -d example.com run # Or OAuth2: OVH_CLIENT_ID=yyy \ OVH_CLIENT_SECRET=xxx \ OVH_ENDPOINT=ovh-eu \ -lego --email you@example.com --dns ovh -d '*.example.com' -d example.com run +lego --dns ovh -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/pdns/pdns.toml b/providers/dns/pdns/pdns.toml index 53b5547b9..a83d80922 100644 --- a/providers/dns/pdns/pdns.toml +++ b/providers/dns/pdns/pdns.toml @@ -7,7 +7,7 @@ Since = "v0.4.0" Example = ''' PDNS_API_URL=http://pdns-server:80/ \ PDNS_API_KEY=xxxx \ -lego --email you@example.com --dns pdns -d '*.example.com' -d example.com run +lego --dns pdns -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/plesk/plesk.toml b/providers/dns/plesk/plesk.toml index 5fb4ce073..0ef89d6b7 100644 --- a/providers/dns/plesk/plesk.toml +++ b/providers/dns/plesk/plesk.toml @@ -8,7 +8,7 @@ Example = ''' PLESK_SERVER_BASE_URL="https://plesk.myserver.com:8443" \ PLESK_USERNAME=xxxxxx \ PLESK_PASSWORD=yyyyyy \ -lego --email you@example.com --dns plesk -d '*.example.com' -d example.com run +lego --dns plesk -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/porkbun/porkbun.toml b/providers/dns/porkbun/porkbun.toml index d7ed3aedc..9ae036da6 100644 --- a/providers/dns/porkbun/porkbun.toml +++ b/providers/dns/porkbun/porkbun.toml @@ -8,7 +8,7 @@ Since = "v4.4.0" Example = ''' PORKBUN_SECRET_API_KEY=xxxxxx \ PORKBUN_API_KEY=yyyyyy \ -lego --email you@example.com --dns porkbun -d '*.example.com' -d example.com run +lego --dns porkbun -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/rackspace/rackspace.toml b/providers/dns/rackspace/rackspace.toml index 7ca2c3b7a..0a4a80ffc 100644 --- a/providers/dns/rackspace/rackspace.toml +++ b/providers/dns/rackspace/rackspace.toml @@ -7,7 +7,7 @@ Since = "v0.4.0" Example = ''' RACKSPACE_USER=xxxx \ RACKSPACE_API_KEY=yyyy \ -lego --email you@example.com --dns rackspace -d '*.example.com' -d example.com run +lego --dns rackspace -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/rainyun/rainyun.toml b/providers/dns/rainyun/rainyun.toml index cca16cffe..fe2b3c07d 100644 --- a/providers/dns/rainyun/rainyun.toml +++ b/providers/dns/rainyun/rainyun.toml @@ -6,7 +6,7 @@ Since = "v4.21.0" Example = ''' RAINYUN_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns rainyun -d '*.example.com' -d example.com run +lego --dns rainyun -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/rcodezero/rcodezero.toml b/providers/dns/rcodezero/rcodezero.toml index bba5588da..c2a4a1e7b 100644 --- a/providers/dns/rcodezero/rcodezero.toml +++ b/providers/dns/rcodezero/rcodezero.toml @@ -6,7 +6,7 @@ Since = "v4.13" Example = ''' RCODEZERO_API_TOKEN= \ -lego --email you@example.com --dns rcodezero -d '*.example.com' -d example.com run +lego --dns rcodezero -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/regfish/regfish.toml b/providers/dns/regfish/regfish.toml index 9869ed96e..fbaacbde4 100644 --- a/providers/dns/regfish/regfish.toml +++ b/providers/dns/regfish/regfish.toml @@ -6,7 +6,7 @@ Since = "v4.20.0" Example = ''' REGFISH_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns regfish -d '*.example.com' -d example.com run +lego --dns regfish -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/regru/regru.toml b/providers/dns/regru/regru.toml index 2ccf3a58f..728bb2bf7 100644 --- a/providers/dns/regru/regru.toml +++ b/providers/dns/regru/regru.toml @@ -7,7 +7,7 @@ Since = "v3.5.0" Example = ''' REGRU_USERNAME=xxxxxx \ REGRU_PASSWORD=yyyyyy \ -lego --email you@example.com --dns regru -d '*.example.com' -d example.com run +lego --dns regru -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/rfc2136/rfc2136.toml b/providers/dns/rfc2136/rfc2136.toml index 9243440a4..6b5bbe599 100644 --- a/providers/dns/rfc2136/rfc2136.toml +++ b/providers/dns/rfc2136/rfc2136.toml @@ -9,7 +9,7 @@ RFC2136_NAMESERVER=127.0.0.1 \ RFC2136_TSIG_KEY=example.com \ RFC2136_TSIG_ALGORITHM=hmac-sha256. \ RFC2136_TSIG_SECRET=YWJjZGVmZGdoaWprbG1ub3BxcnN0dXZ3eHl6MTIzNDU= \ -lego --email you@example.com --dns rfc2136 -d '*.example.com' -d example.com run +lego --dns rfc2136 -d '*.example.com' -d example.com run ## --- @@ -17,7 +17,7 @@ keyname=example.com; keyfile=example.com.key; tsig-keygen $keyname > $keyfile RFC2136_NAMESERVER=127.0.0.1 \ RFC2136_TSIG_FILE="$keyfile" \ -lego --email you@example.com --dns rfc2136 -d '*.example.com' -d example.com run +lego --dns rfc2136 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/rimuhosting/rimuhosting.toml b/providers/dns/rimuhosting/rimuhosting.toml index 0a4f983e2..c1994e2cc 100644 --- a/providers/dns/rimuhosting/rimuhosting.toml +++ b/providers/dns/rimuhosting/rimuhosting.toml @@ -6,7 +6,7 @@ Since = "v0.3.5" Example = ''' RIMUHOSTING_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns rimuhosting -d '*.example.com' -d example.com run +lego --dns rimuhosting -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/route53/route53.toml b/providers/dns/route53/route53.toml index 9e3b049a6..607d9ef31 100644 --- a/providers/dns/route53/route53.toml +++ b/providers/dns/route53/route53.toml @@ -9,7 +9,7 @@ AWS_ACCESS_KEY_ID=your_key_id \ AWS_SECRET_ACCESS_KEY=your_secret_access_key \ AWS_REGION=aws-region \ AWS_HOSTED_ZONE_ID=your_hosted_zone_id \ -lego --email you@example.com --dns route53 -d '*.example.com' -d example.com run +lego --dns route53 -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/safedns/safedns.toml b/providers/dns/safedns/safedns.toml index dcc7bc90e..188db66a4 100644 --- a/providers/dns/safedns/safedns.toml +++ b/providers/dns/safedns/safedns.toml @@ -6,7 +6,7 @@ Since = "v4.6.0" Example = ''' SAFEDNS_AUTH_TOKEN=xxxxxx \ -lego --email you@example.com --dns safedns -d '*.example.com' -d example.com run +lego --dns safedns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/sakuracloud/sakuracloud.toml b/providers/dns/sakuracloud/sakuracloud.toml index f754e0c89..a197cd27c 100644 --- a/providers/dns/sakuracloud/sakuracloud.toml +++ b/providers/dns/sakuracloud/sakuracloud.toml @@ -7,7 +7,7 @@ Since = "v1.1.0" Example = ''' SAKURACLOUD_ACCESS_TOKEN=xxxxx \ SAKURACLOUD_ACCESS_TOKEN_SECRET=yyyyy \ -lego --email you@example.com --dns sakuracloud -d '*.example.com' -d example.com run +lego --dns sakuracloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/scaleway/scaleway.toml b/providers/dns/scaleway/scaleway.toml index 212cea295..8b556e8b1 100644 --- a/providers/dns/scaleway/scaleway.toml +++ b/providers/dns/scaleway/scaleway.toml @@ -6,7 +6,7 @@ Since = "v3.4.0" Example = ''' SCW_SECRET_KEY=xxxxxxx-xxxxx-xxxx-xxx-xxxxxx \ -lego --email you@example.com --dns scaleway -d '*.example.com' -d example.com run +lego --dns scaleway -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/selectel/selectel.toml b/providers/dns/selectel/selectel.toml index f9add7ea9..087c97b5b 100644 --- a/providers/dns/selectel/selectel.toml +++ b/providers/dns/selectel/selectel.toml @@ -6,7 +6,7 @@ Since = "v1.2.0" Example = ''' SELECTEL_API_TOKEN=xxxxx \ -lego --email you@example.com --dns selectel -d '*.example.com' -d example.com run +lego --dns selectel -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/selectelv2/selectelv2.toml b/providers/dns/selectelv2/selectelv2.toml index fd8dbda9f..480c7756e 100644 --- a/providers/dns/selectelv2/selectelv2.toml +++ b/providers/dns/selectelv2/selectelv2.toml @@ -9,7 +9,7 @@ SELECTELV2_USERNAME=trex \ SELECTELV2_PASSWORD=xxxxx \ SELECTELV2_ACCOUNT_ID=1234567 \ SELECTELV2_PROJECT_ID=111a11111aaa11aa1a11aaa11111aa1a \ -lego --email you@example.com --dns selectelv2 -d '*.example.com' -d example.com run +lego --dns selectelv2 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/selfhostde/selfhostde.toml b/providers/dns/selfhostde/selfhostde.toml index 619f2cae8..bd22c6c41 100644 --- a/providers/dns/selfhostde/selfhostde.toml +++ b/providers/dns/selfhostde/selfhostde.toml @@ -8,7 +8,7 @@ Example = ''' SELFHOSTDE_USERNAME=xxx \ SELFHOSTDE_PASSWORD=yyy \ SELFHOSTDE_RECORDS_MAPPING=my.example.com:123 \ -lego --email you@example.com --dns selfhostde -d '*.example.com' -d example.com run +lego --dns selfhostde -d '*.example.com' -d example.com run ''' Additional = """ diff --git a/providers/dns/servercow/servercow.toml b/providers/dns/servercow/servercow.toml index de9727163..5cbacbb88 100644 --- a/providers/dns/servercow/servercow.toml +++ b/providers/dns/servercow/servercow.toml @@ -7,7 +7,7 @@ Since = "v3.4.0" Example = ''' SERVERCOW_USERNAME=xxxxxxxx \ SERVERCOW_PASSWORD=xxxxxxxx \ -lego --email you@example.com --dns servercow -d '*.example.com' -d example.com run +lego --dns servercow -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/shellrent/shellrent.toml b/providers/dns/shellrent/shellrent.toml index 48a5b9ad9..05b6517fc 100644 --- a/providers/dns/shellrent/shellrent.toml +++ b/providers/dns/shellrent/shellrent.toml @@ -7,7 +7,7 @@ Since = "v4.16.0" Example = ''' SHELLRENT_USERNAME=xxxx \ SHELLRENT_TOKEN=yyyy \ -lego --email you@example.com --dns shellrent -d '*.example.com' -d example.com run +lego --dns shellrent -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/simply/simply.toml b/providers/dns/simply/simply.toml index c586e0db5..a838e245a 100644 --- a/providers/dns/simply/simply.toml +++ b/providers/dns/simply/simply.toml @@ -7,7 +7,7 @@ Since = "v4.4.0" Example = ''' SIMPLY_ACCOUNT_NAME=xxxxxx \ SIMPLY_API_KEY=yyyyyy \ -lego --email you@example.com --dns simply -d '*.example.com' -d example.com run +lego --dns simply -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/sonic/sonic.toml b/providers/dns/sonic/sonic.toml index 921fe4988..cb501e923 100644 --- a/providers/dns/sonic/sonic.toml +++ b/providers/dns/sonic/sonic.toml @@ -7,7 +7,7 @@ Since = "v4.4.0" Example = ''' SONIC_USER_ID=12345 \ SONIC_API_KEY=4d6fbf2f9ab0fa11697470918d37625851fc0c51 \ -lego --email you@example.com --dns sonic -d '*.example.com' -d example.com run +lego --dns sonic -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/spaceship/spaceship.toml b/providers/dns/spaceship/spaceship.toml index 645abd171..e9abcd408 100644 --- a/providers/dns/spaceship/spaceship.toml +++ b/providers/dns/spaceship/spaceship.toml @@ -7,7 +7,7 @@ Since = "v4.22.0" Example = ''' SPACESHIP_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ SPACESHIP_API_SECRET="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns spaceship -d '*.example.com' -d example.com run +lego --dns spaceship -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/stackpath/stackpath.toml b/providers/dns/stackpath/stackpath.toml index cc14cdfba..b50e7035f 100644 --- a/providers/dns/stackpath/stackpath.toml +++ b/providers/dns/stackpath/stackpath.toml @@ -8,7 +8,7 @@ Example = ''' STACKPATH_CLIENT_ID=xxxxx \ STACKPATH_CLIENT_SECRET=yyyyy \ STACKPATH_STACK_ID=zzzzz \ -lego --email you@example.com --dns stackpath -d '*.example.com' -d example.com run +lego --dns stackpath -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/syse/syse.toml b/providers/dns/syse/syse.toml index 0ae585854..b5b1fdf47 100644 --- a/providers/dns/syse/syse.toml +++ b/providers/dns/syse/syse.toml @@ -6,10 +6,10 @@ Since = "v4.30.0" Example = ''' SYSE_CREDENTIALS=example.com:password \ -lego --email you@example.com --dns syse -d '*.example.com' -d example.com run +lego --dns syse -d '*.example.com' -d example.com run SYSE_CREDENTIALS=example.org:password1,example.com:password2 \ -lego --email you@example.com --dns syse -d '*.example.org' -d example.org -d '*.example.com' -d example.com +lego --dns syse -d '*.example.org' -d example.org -d '*.example.com' -d example.com ''' [Configuration] diff --git a/providers/dns/technitium/technitium.toml b/providers/dns/technitium/technitium.toml index 13b40c304..ac1fc6466 100644 --- a/providers/dns/technitium/technitium.toml +++ b/providers/dns/technitium/technitium.toml @@ -7,7 +7,7 @@ Since = "v4.20.0" Example = ''' TECHNITIUM_SERVER_BASE_URL="https://localhost:5380" \ TECHNITIUM_API_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns technitium -d '*.example.com' -d example.com run +lego --dns technitium -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/tencentcloud/tencentcloud.toml b/providers/dns/tencentcloud/tencentcloud.toml index 7f06d9386..50f4ee9d5 100644 --- a/providers/dns/tencentcloud/tencentcloud.toml +++ b/providers/dns/tencentcloud/tencentcloud.toml @@ -7,7 +7,7 @@ Since = "v4.6.0" Example = ''' TENCENTCLOUD_SECRET_ID=abcdefghijklmnopqrstuvwx \ TENCENTCLOUD_SECRET_KEY=your-secret-key \ -lego --email you@example.com --dns tencentcloud -d '*.example.com' -d example.com run +lego --dns tencentcloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/timewebcloud/timewebcloud.toml b/providers/dns/timewebcloud/timewebcloud.toml index 8c20b37b9..c8bde636a 100644 --- a/providers/dns/timewebcloud/timewebcloud.toml +++ b/providers/dns/timewebcloud/timewebcloud.toml @@ -6,7 +6,7 @@ Since = "v4.20.0" Example = ''' TIMEWEBCLOUD_AUTH_TOKEN=xxxxxx \ -lego --email you@example.com --dns timewebcloud -d '*.example.com' -d example.com run +lego --dns timewebcloud -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/transip/transip.toml b/providers/dns/transip/transip.toml index a894cc3e3..bf7d58ee3 100644 --- a/providers/dns/transip/transip.toml +++ b/providers/dns/transip/transip.toml @@ -7,7 +7,7 @@ Since = "v2.0.0" Example = ''' TRANSIP_ACCOUNT_NAME = "Account name" \ TRANSIP_PRIVATE_KEY_PATH = "transip.key" \ -lego --email you@example.com --dns transip -d '*.example.com' -d example.com run +lego --dns transip -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/ultradns/ultradns.toml b/providers/dns/ultradns/ultradns.toml index a403a3dcf..4c3dbbe72 100644 --- a/providers/dns/ultradns/ultradns.toml +++ b/providers/dns/ultradns/ultradns.toml @@ -7,7 +7,7 @@ Since = "v4.10.0" Example = ''' ULTRADNS_USERNAME=username \ ULTRADNS_PASSWORD=password \ -lego --email you@example.com --dns ultradns -d '*.example.com' -d example.com run +lego --dns ultradns -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/uniteddomains/uniteddomains.toml b/providers/dns/uniteddomains/uniteddomains.toml index 3663cb867..fe8b9e574 100644 --- a/providers/dns/uniteddomains/uniteddomains.toml +++ b/providers/dns/uniteddomains/uniteddomains.toml @@ -6,7 +6,7 @@ Since = "v4.29.0" Example = ''' UNITEDDOMAINS_API_KEY=xxxxxxxx \ -lego --email you@example.com --dns uniteddomains -d '*.example.com' -d example.com run +lego --dns uniteddomains -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/variomedia/variomedia.toml b/providers/dns/variomedia/variomedia.toml index fe6f2797a..8390d1922 100644 --- a/providers/dns/variomedia/variomedia.toml +++ b/providers/dns/variomedia/variomedia.toml @@ -6,7 +6,7 @@ Since = "v4.8.0" Example = ''' VARIOMEDIA_API_TOKEN=xxxx \ -lego --email you@example.com --dns variomedia -d '*.example.com' -d example.com run +lego --dns variomedia -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/vercel/vercel.toml b/providers/dns/vercel/vercel.toml index 2545b9c48..4700d6d78 100644 --- a/providers/dns/vercel/vercel.toml +++ b/providers/dns/vercel/vercel.toml @@ -6,7 +6,7 @@ Since = "v4.7.0" Example = ''' VERCEL_API_TOKEN=xxxxxx \ -lego --email you@example.com --dns vercel -d '*.example.com' -d example.com run +lego --dns vercel -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/versio/versio.toml b/providers/dns/versio/versio.toml index 33f7125c8..733947095 100644 --- a/providers/dns/versio/versio.toml +++ b/providers/dns/versio/versio.toml @@ -7,7 +7,7 @@ Since = "v2.7.0" Example = ''' VERSIO_USERNAME= \ VERSIO_PASSWORD= \ -lego --email you@example.com --dns versio -d '*.example.com' -d example.com run +lego --dns versio -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/vinyldns/vinyldns.toml b/providers/dns/vinyldns/vinyldns.toml index 5789d10ab..d6dd5810e 100644 --- a/providers/dns/vinyldns/vinyldns.toml +++ b/providers/dns/vinyldns/vinyldns.toml @@ -8,7 +8,7 @@ Example = ''' VINYLDNS_ACCESS_KEY=xxxxxx \ VINYLDNS_SECRET_KEY=yyyyy \ VINYLDNS_HOST=https://api.vinyldns.example.org:9443 \ -lego --email you@example.com --dns vinyldns -d '*.example.com' -d example.com run +lego --dns vinyldns -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/virtualname/virtualname.toml b/providers/dns/virtualname/virtualname.toml index 7cc4c5344..881f09797 100644 --- a/providers/dns/virtualname/virtualname.toml +++ b/providers/dns/virtualname/virtualname.toml @@ -6,7 +6,7 @@ Since = "v4.30.0" Example = ''' VIRTUALNAME_TOKEN=xxxxxx \ -lego --email you@example.com --dns virtualname -d '*.example.com' -d example.com run +lego --dns virtualname -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/vkcloud/vkcloud.toml b/providers/dns/vkcloud/vkcloud.toml index 366039694..04f57fea3 100644 --- a/providers/dns/vkcloud/vkcloud.toml +++ b/providers/dns/vkcloud/vkcloud.toml @@ -8,7 +8,7 @@ Example = ''' VK_CLOUD_PROJECT_ID="" \ VK_CLOUD_USERNAME="" \ VK_CLOUD_PASSWORD="" \ -lego --email you@example.com --dns vkcloud -d '*.example.com' -d example.com run +lego --dns vkcloud -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/volcengine/volcengine.toml b/providers/dns/volcengine/volcengine.toml index cb7c219f7..ceedcb18a 100644 --- a/providers/dns/volcengine/volcengine.toml +++ b/providers/dns/volcengine/volcengine.toml @@ -7,7 +7,7 @@ Since = "v4.19.0" Example = ''' VOLC_ACCESSKEY=xxx \ VOLC_SECRETKEY=yyy \ -lego --email you@example.com --dns volcengine -d '*.example.com' -d example.com run +lego --dns volcengine -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/vscale/vscale.toml b/providers/dns/vscale/vscale.toml index 78b6c99e5..f7dc0d943 100644 --- a/providers/dns/vscale/vscale.toml +++ b/providers/dns/vscale/vscale.toml @@ -6,7 +6,7 @@ Since = "v2.0.0" Example = ''' VSCALE_API_TOKEN=xxxxx \ -lego --email you@example.com --dns vscale -d '*.example.com' -d example.com run +lego --dns vscale -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/vultr/vultr.toml b/providers/dns/vultr/vultr.toml index 7d31bd52b..78e878bea 100644 --- a/providers/dns/vultr/vultr.toml +++ b/providers/dns/vultr/vultr.toml @@ -6,7 +6,7 @@ Since = "v0.3.1" Example = ''' VULTR_API_KEY=xxxxx \ -lego --email you@example.com --dns vultr -d '*.example.com' -d example.com run +lego --dns vultr -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/webnames/webnames.toml b/providers/dns/webnames/webnames.toml index 04dea25c5..b038deaf5 100644 --- a/providers/dns/webnames/webnames.toml +++ b/providers/dns/webnames/webnames.toml @@ -7,7 +7,7 @@ Since = "v4.15.0" Example = ''' WEBNAMESRU_API_KEY=xxxxxx \ -lego --email you@example.com --dns webnamesru -d '*.example.com' -d example.com run +lego --dns webnamesru -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/webnamesca/webnamesca.toml b/providers/dns/webnamesca/webnamesca.toml index c7d30751b..ab68a04a0 100644 --- a/providers/dns/webnamesca/webnamesca.toml +++ b/providers/dns/webnamesca/webnamesca.toml @@ -7,7 +7,7 @@ Since = "v4.28.0" Example = ''' WEBNAMESCA_API_USER="xxx" \ WEBNAMESCA_API_KEY="yyy" \ -lego --email you@example.com --dns webnamesca -d '*.example.com' -d example.com run +lego --dns webnamesca -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/websupport/websupport.toml b/providers/dns/websupport/websupport.toml index 1f34b431b..4908f0235 100644 --- a/providers/dns/websupport/websupport.toml +++ b/providers/dns/websupport/websupport.toml @@ -7,7 +7,7 @@ Since = "v4.10.0" Example = ''' WEBSUPPORT_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ WEBSUPPORT_SECRET="yyyyyyyyyyyyyyyyyyyyy" \ -lego --email you@example.com --dns websupport -d '*.example.com' -d example.com run +lego --dns websupport -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/wedos/wedos.toml b/providers/dns/wedos/wedos.toml index 2ed351a2d..89abfc16c 100644 --- a/providers/dns/wedos/wedos.toml +++ b/providers/dns/wedos/wedos.toml @@ -7,7 +7,7 @@ Since = "v4.4.0" Example = ''' WEDOS_USERNAME=xxxxxxxx \ WEDOS_WAPI_PASSWORD=xxxxxxxx \ -lego --email you@example.com --dns wedos -d '*.example.com' -d example.com run +lego --dns wedos -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/westcn/westcn.toml b/providers/dns/westcn/westcn.toml index acf3a4e49..1b0cb0a7a 100644 --- a/providers/dns/westcn/westcn.toml +++ b/providers/dns/westcn/westcn.toml @@ -7,7 +7,7 @@ Since = "v4.21.0" Example = ''' WESTCN_USERNAME="xxx" \ WESTCN_PASSWORD="yyy" \ -lego --email you@example.com --dns westcn -d '*.example.com' -d example.com run +lego --dns westcn -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/yandex/yandex.toml b/providers/dns/yandex/yandex.toml index 78da1444d..a36df069e 100644 --- a/providers/dns/yandex/yandex.toml +++ b/providers/dns/yandex/yandex.toml @@ -7,7 +7,7 @@ Since = "v3.7.0" Example = ''' YANDEX_PDD_TOKEN= \ -lego --email you@example.com --dns yandex -d '*.example.com' -d example.com run +lego --dns yandex -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/yandex360/yandex360.toml b/providers/dns/yandex360/yandex360.toml index 69ea02a28..444b1cc38 100644 --- a/providers/dns/yandex360/yandex360.toml +++ b/providers/dns/yandex360/yandex360.toml @@ -8,7 +8,7 @@ Since = "v4.14.0" Example = ''' YANDEX360_OAUTH_TOKEN= \ YANDEX360_ORG_ID= \ -lego --email you@example.com --dns yandex360 -d '*.example.com' -d example.com run +lego --dns yandex360 -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/yandexcloud/yandexcloud.toml b/providers/dns/yandexcloud/yandexcloud.toml index a4bf3ebbb..d4b40bb1d 100644 --- a/providers/dns/yandexcloud/yandexcloud.toml +++ b/providers/dns/yandexcloud/yandexcloud.toml @@ -7,7 +7,7 @@ Since = "v4.9.0" Example = ''' YANDEX_CLOUD_IAM_TOKEN= \ YANDEX_CLOUD_FOLDER_ID= \ -lego --email you@example.com --dns yandexcloud -d '*.example.com' -d example.com run +lego --dns yandexcloud -d '*.example.com' -d example.com run # --- @@ -20,7 +20,7 @@ YANDEX_CLOUD_IAM_TOKEN=$(echo '{ \ "private_key": "-----BEGIN PRIVATE KEY----------END PRIVATE KEY-----" \ }' | base64) \ YANDEX_CLOUD_FOLDER_ID= \ -lego --email you@example.com --dns yandexcloud -d '*.example.com' -d example.com run +lego --dns yandexcloud -d '*.example.com' -d example.com run ''' Additional = ''' diff --git a/providers/dns/zoneedit/zoneedit.toml b/providers/dns/zoneedit/zoneedit.toml index d3c547c23..cdc53b33a 100644 --- a/providers/dns/zoneedit/zoneedit.toml +++ b/providers/dns/zoneedit/zoneedit.toml @@ -7,7 +7,7 @@ Since = "v4.25.0" Example = ''' ZONEEDIT_USER="xxxxxxxxxxxxxxxxxxxxx" \ ZONEEDIT_AUTH_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ -lego --email you@example.com --dns zoneedit -d '*.example.com' -d example.com run +lego --dns zoneedit -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/zoneee/zoneee.toml b/providers/dns/zoneee/zoneee.toml index 75ccabbda..ab7133180 100644 --- a/providers/dns/zoneee/zoneee.toml +++ b/providers/dns/zoneee/zoneee.toml @@ -7,7 +7,7 @@ Since = "v2.1.0" Example = ''' ZONEEE_API_USER=xxxxx \ ZONEEE_API_KEY=yyyyy \ -lego --email you@example.com --dns zoneee -d '*.example.com' -d example.com run +lego --dns zoneee -d '*.example.com' -d example.com run ''' [Configuration] diff --git a/providers/dns/zonomi/zonomi.toml b/providers/dns/zonomi/zonomi.toml index a5577999a..b91bcaac6 100644 --- a/providers/dns/zonomi/zonomi.toml +++ b/providers/dns/zonomi/zonomi.toml @@ -6,7 +6,7 @@ Since = "v3.5.0" Example = ''' ZONOMI_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -lego --email you@example.com --dns zonomi -d '*.example.com' -d example.com run +lego --dns zonomi -d '*.example.com' -d example.com run ''' [Configuration] From 2eede6d6206a06e0b16fb3de45b8fa7aa0807de3 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 1 Jan 2026 22:11:38 +0100 Subject: [PATCH 257/298] hetzner: fix compatibility with _FILE suffix (#2775) --- providers/dns/hetzner/hetzner.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/providers/dns/hetzner/hetzner.go b/providers/dns/hetzner/hetzner.go index 1b02590d6..bae985b3e 100644 --- a/providers/dns/hetzner/hetzner.go +++ b/providers/dns/hetzner/hetzner.go @@ -4,7 +4,6 @@ package hetzner import ( "errors" "net/http" - "os" "time" "github.com/go-acme/lego/v4/challenge" @@ -62,10 +61,9 @@ type DNSProvider struct { } // NewDNSProvider returns a DNSProvider instance configured for hetzner. -// Credentials must be passed in the environment variable: HETZNER_API_KEY. func NewDNSProvider() (*DNSProvider, error) { - _, foundAPIToken := os.LookupEnv(EnvAPIToken) - _, foundAPIKey := os.LookupEnv(EnvAPIKey) + foundAPIToken := env.GetOrFile(EnvAPIToken) != "" + foundAPIKey := env.GetOrFile(EnvAPIKey) != "" switch { case foundAPIToken: From 4783c128fad713c4bc0a4e50da48e0f10da79c23 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 5 Jan 2026 00:05:16 +0100 Subject: [PATCH 258/298] chore: minor changes (#2776) --- README.md | 4 +- docs/content/_index.md | 2 +- providers/dns/allinkl/allinkl.go | 8 +-- providers/dns/autodns/autodns.go | 4 +- providers/dns/liquidweb/liquidweb_test.go | 36 ++++++------- providers/dns/liquidweb/servermock_test.go | 60 +++++++++++----------- providers/dns/neodigit/neodigit.go | 4 +- providers/dns/virtualname/virtualname.go | 4 +- 8 files changed, 64 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index aff5052ca..e9a8caacc 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ So if you think that lego is worth it, please consider [donating](https://donate - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): certificates for IP addresses - Support [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html): Renewal Information (ARI) Extension - Support [draft-ietf-acme-profiles-00](https://datatracker.ietf.org/doc/draft-ietf-acme-profiles/): Profiles Extension -- Comes with about [170 DNS providers](https://go-acme.github.io/lego/dns) +- Comes with about [180 DNS providers](https://go-acme.github.io/lego/dns) - Register with CA - Obtain certificates, both from scratch or with an existing CSR - Renew certificates @@ -56,6 +56,8 @@ Documentation is hosted live at https://go-acme.github.io/lego/. Detailed documentation is available [here](https://go-acme.github.io/lego/dns). +If your DNS provider is not supported, please open an [issue](https://github.com/go-acme/lego/issues/new?assignees=&labels=enhancement%2C+new-provider&template=new_dns_provider.yml). + diff --git a/docs/content/_index.md b/docs/content/_index.md index d3787cf19..95e411afc 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -24,7 +24,7 @@ I've been maintaining it for about 10 years. - Support [RFC 8738](https://www.rfc-editor.org/rfc/rfc8738.html): issues certificates for IP addresses - Support [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html): Renewal Information (ARI) Extension - Support [draft-ietf-acme-profiles-00](https://datatracker.ietf.org/doc/draft-ietf-acme-profiles/): Profiles Extension -- Comes with about [170 DNS providers]({{% ref "dns" %}}) +- Comes with about [180 DNS providers]({{% ref "dns" %}}) - Register with CA - Obtain certificates, both from scratch or with an existing CSR - Renew certificates diff --git a/providers/dns/allinkl/allinkl.go b/providers/dns/allinkl/allinkl.go index 7e8f5ab4e..4a0aadd2b 100644 --- a/providers/dns/allinkl/allinkl.go +++ b/providers/dns/allinkl/allinkl.go @@ -130,7 +130,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { credential, err := d.identifier.Authentication(ctx, 60, true) if err != nil { - return fmt.Errorf("allinkl: %w", err) + return fmt.Errorf("allinkl: authentication: %w", err) } ctx = internal.WithContext(ctx, credential) @@ -149,7 +149,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { recordID, err := d.client.AddDNSSettings(ctx, record) if err != nil { - return fmt.Errorf("allinkl: %w", err) + return fmt.Errorf("allinkl: add DNS settings: %w", err) } d.recordIDsMu.Lock() @@ -167,7 +167,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { credential, err := d.identifier.Authentication(ctx, 60, true) if err != nil { - return fmt.Errorf("allinkl: %w", err) + return fmt.Errorf("allinkl: authentication: %w", err) } ctx = internal.WithContext(ctx, credential) @@ -183,7 +183,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { _, err = d.client.DeleteDNSSettings(ctx, recordID) if err != nil { - return fmt.Errorf("allinkl: %w", err) + return fmt.Errorf("allinkl: delete DNS settings: %w", err) } d.recordIDsMu.Lock() diff --git a/providers/dns/autodns/autodns.go b/providers/dns/autodns/autodns.go index fc8e793b6..8a9361bc0 100644 --- a/providers/dns/autodns/autodns.go +++ b/providers/dns/autodns/autodns.go @@ -130,7 +130,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { _, err := d.client.AddRecords(context.Background(), info.EffectiveFQDN, records) if err != nil { - return fmt.Errorf("autodns: %w", err) + return fmt.Errorf("autodns: add record: %w", err) } return nil @@ -149,7 +149,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { _, err := d.client.RemoveRecords(context.Background(), info.EffectiveFQDN, records) if err != nil { - return fmt.Errorf("autodns: %w", err) + return fmt.Errorf("autodns: remove record: %w", err) } return nil diff --git a/providers/dns/liquidweb/liquidweb_test.go b/providers/dns/liquidweb/liquidweb_test.go index 26dc5bdc0..a34d19037 100644 --- a/providers/dns/liquidweb/liquidweb_test.go +++ b/providers/dns/liquidweb/liquidweb_test.go @@ -27,16 +27,16 @@ func TestNewDNSProvider(t *testing.T) { { desc: "minimum-success", envVars: map[string]string{ - EnvUsername: "blars", - EnvPassword: "tacoman", + EnvUsername: "user", + EnvPassword: "secret", }, }, { desc: "set-everything", envVars: map[string]string{ - EnvURL: "https://storm.com", - EnvUsername: "blars", - EnvPassword: "tacoman", + EnvURL: "https://storm.example", + EnvUsername: "user", + EnvPassword: "secret", EnvZone: "blars.com", }, }, @@ -48,16 +48,16 @@ func TestNewDNSProvider(t *testing.T) { { desc: "missing username", envVars: map[string]string{ - EnvPassword: "tacoman", - EnvZone: "blars.com", + EnvPassword: "secret", + EnvZone: "blars.example", }, expected: "liquidweb: some credentials information are missing: LIQUID_WEB_USERNAME", }, { desc: "missing password", envVars: map[string]string{ - EnvUsername: "blars", - EnvZone: "blars.com", + EnvUsername: "user", + EnvZone: "blars.example", }, expected: "liquidweb: some credentials information are missing: LIQUID_WEB_PASSWORD", }, @@ -148,13 +148,13 @@ func TestNewDNSProviderConfig(t *testing.T) { func TestDNSProvider_Present(t *testing.T) { provider := mockProvider(t) - err := provider.Present("tacoman.com", "", "") + err := provider.Present("tacoman.example", "", "") require.NoError(t, err) } func TestDNSProvider_CleanUp(t *testing.T) { provider := mockProvider(t, network.DNSRecord{ - Name: "_acme-challenge.tacoman.com", + Name: "_acme-challenge.tacoman.example", RData: "123d==", Type: "TXT", TTL: 300, @@ -164,7 +164,7 @@ func TestDNSProvider_CleanUp(t *testing.T) { provider.recordIDs["123d=="] = 1234567 - err := provider.CleanUp("tacoman.com.", "123d==", "") + err := provider.CleanUp("tacoman.example.", "123d==", "") require.NoError(t, err) } @@ -181,7 +181,7 @@ func TestDNSProvider(t *testing.T) { }{ { desc: "expected successful", - domain: "tacoman.com", + domain: "tacoman.example", token: "123", keyAuth: "456", present: true, @@ -189,7 +189,7 @@ func TestDNSProvider(t *testing.T) { }, { desc: "other successful", - domain: "banana.com", + domain: "banana.example", token: "123", keyAuth: "456", present: true, @@ -197,16 +197,16 @@ func TestDNSProvider(t *testing.T) { }, { desc: "zone not on account", - domain: "huckleberry.com", + domain: "huckleberry.example", token: "123", keyAuth: "456", present: true, - expPresentErr: "no valid zone in account for certificate '_acme-challenge.huckleberry.com'", + expPresentErr: "no valid zone in account for certificate '_acme-challenge.huckleberry.example'", cleanup: false, }, { desc: "ssl for domain", - domain: "sundae.cherry.com", + domain: "sundae.cherry.example", token: "5847953", keyAuth: "34872934", present: true, @@ -214,7 +214,7 @@ func TestDNSProvider(t *testing.T) { }, { desc: "complicated domain", - domain: "always.money.stand.banana.com", + domain: "always.money.stand.banana.example", token: "5847953", keyAuth: "there is always money in the banana stand", present: true, diff --git a/providers/dns/liquidweb/servermock_test.go b/providers/dns/liquidweb/servermock_test.go index f211e7253..4886e17f1 100644 --- a/providers/dns/liquidweb/servermock_test.go +++ b/providers/dns/liquidweb/servermock_test.go @@ -26,14 +26,14 @@ func mockProvider(t *testing.T, initRecs ...network.DNSRecord) *DNSProvider { return servermock.NewBuilder( func(server *httptest.Server) (*DNSProvider, error) { config := NewDefaultConfig() - config.Username = "blars" - config.Password = "tacoman" + config.Username = "user" + config.Password = "secret" config.BaseURL = server.URL return NewDNSProviderConfig(config) }, servermock.CheckHeader(). - WithBasicAuth("blars", "tacoman"), + WithBasicAuth("user", "secret"), ). Route("/v1/Network/DNS/Record/delete", mockAPIDelete(recs)). Route("/v1/Network/DNS/Record/create", mockAPICreate(recs)). @@ -172,38 +172,38 @@ func makeMockZones() (map[int]network.DNSZoneList, map[string]int) { Items: []network.DNSZone{ { ID: 1, - Name: "blars.com", + Name: "blars.example", Active: 1, DelegationStatus: "CORRECT", - PrimaryNameserver: "ns.liquidweb.com", + PrimaryNameserver: "ns.example.org", }, { ID: 2, - Name: "tacoman.com", + Name: "tacoman.example", Active: 1, DelegationStatus: "CORRECT", - PrimaryNameserver: "ns.liquidweb.com", + PrimaryNameserver: "ns.example.org", }, { ID: 3, - Name: "storm.com", + Name: "storm.example", Active: 1, DelegationStatus: "CORRECT", - PrimaryNameserver: "ns.liquidweb.com", + PrimaryNameserver: "ns.example.org", }, { ID: 4, - Name: "not-apple.com", + Name: "not-apple.example", Active: 1, DelegationStatus: "BAD_NAMESERVERS", - PrimaryNameserver: "ns.liquidweb.com", + PrimaryNameserver: "ns.example.org", }, { ID: 5, Name: "example.com", Active: 1, DelegationStatus: "BAD_NAMESERVERS", - PrimaryNameserver: "ns.liquidweb.com", + PrimaryNameserver: "ns.example.org", }, }, }, @@ -211,38 +211,38 @@ func makeMockZones() (map[int]network.DNSZoneList, map[string]int) { Items: []network.DNSZone{ { ID: 6, - Name: "banana.com", + Name: "banana.example", Active: 1, DelegationStatus: "NXDOMAIN", - PrimaryNameserver: "ns.liquidweb.com", + PrimaryNameserver: "ns.example.org", }, { ID: 7, - Name: "cherry.com", + Name: "cherry.example", Active: 1, DelegationStatus: "SERVFAIL", - PrimaryNameserver: "ns.liquidweb.com", + PrimaryNameserver: "ns.example.org", }, { ID: 8, - Name: "dates.com", + Name: "dates.example", Active: 1, DelegationStatus: "SERVFAIL", - PrimaryNameserver: "ns.liquidweb.com", + PrimaryNameserver: "ns.example.org", }, { ID: 9, - Name: "eggplant.com", + Name: "eggplant.example", Active: 1, DelegationStatus: "SERVFAIL", - PrimaryNameserver: "ns.liquidweb.com", + PrimaryNameserver: "ns.example.org", }, { ID: 10, - Name: "fig.com", + Name: "fig.example", Active: 1, DelegationStatus: "UNKNOWN", - PrimaryNameserver: "ns.liquidweb.com", + PrimaryNameserver: "ns.example.org", }, }, }, @@ -250,31 +250,31 @@ func makeMockZones() (map[int]network.DNSZoneList, map[string]int) { Items: []network.DNSZone{ { ID: 11, - Name: "grapes.com", + Name: "grapes.example", Active: 1, DelegationStatus: "UNKNOWN", - PrimaryNameserver: "ns.liquidweb.com", + PrimaryNameserver: "ns.example.org", }, { ID: 12, - Name: "money.banana.com", + Name: "money.banana.example", Active: 1, DelegationStatus: "UNKNOWN", - PrimaryNameserver: "ns.liquidweb.com", + PrimaryNameserver: "ns.example.org", }, { ID: 13, - Name: "money.stand.banana.com", + Name: "money.stand.banana.example", Active: 1, DelegationStatus: "UNKNOWN", - PrimaryNameserver: "ns.liquidweb.com", + PrimaryNameserver: "ns.example.org", }, { ID: 14, - Name: "stand.banana.com", + Name: "stand.banana.example", Active: 1, DelegationStatus: "UNKNOWN", - PrimaryNameserver: "ns.liquidweb.com", + PrimaryNameserver: "ns.example.org", }, }, }, diff --git a/providers/dns/neodigit/neodigit.go b/providers/dns/neodigit/neodigit.go index eb4530479..d41846307 100644 --- a/providers/dns/neodigit/neodigit.go +++ b/providers/dns/neodigit/neodigit.go @@ -25,6 +25,8 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) +const defaultBaseURL = "https://api.neodigit.net/v1" + var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. @@ -66,7 +68,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("neodigit: the configuration of the DNS provider is nil") } - provider, err := tecnocratica.NewDNSProviderConfig(config, "") + provider, err := tecnocratica.NewDNSProviderConfig(config, defaultBaseURL) if err != nil { return nil, fmt.Errorf("neodigit: %w", err) } diff --git a/providers/dns/virtualname/virtualname.go b/providers/dns/virtualname/virtualname.go index 6b04e8169..34637d280 100644 --- a/providers/dns/virtualname/virtualname.go +++ b/providers/dns/virtualname/virtualname.go @@ -25,6 +25,8 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) +const defaultBaseURL = "https://api.virtualname.net/v1" + var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. @@ -66,7 +68,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("virtualname: the configuration of the DNS provider is nil") } - provider, err := tecnocratica.NewDNSProviderConfig(config, "https://api.virtualname.net/v1") + provider, err := tecnocratica.NewDNSProviderConfig(config, defaultBaseURL) if err != nil { return nil, fmt.Errorf("virtualname: %w", err) } From c5a259564fe8e0b183fe12fb926f5b3634497967 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 6 Jan 2026 19:06:05 +0100 Subject: [PATCH 259/298] =?UTF-8?q?Add=20DNS=20provider=20for=2035.com/?= =?UTF-8?q?=E4=B8=89=E4=BA=94=E4=BA=92=E8=81=94=20(#2779)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 90 +++++------ cmd/zz_gen_cmd_dnshelp.go | 22 +++ docs/content/dns/zz_gen_com35.md | 69 +++++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/com35/com35.go | 104 +++++++++++++ providers/dns/com35/com35.toml | 24 +++ providers/dns/com35/com35_test.go | 144 ++++++++++++++++++ .../{ => internal}/westcn/internal/client.go | 6 +- .../westcn/internal/client_test.go | 5 +- .../internal/fixtures/adddnsrecord.json | 0 .../internal/fixtures/deldnsrecord.json | 0 .../westcn/internal/fixtures/error.json | 0 .../{ => internal}/westcn/internal/types.go | 0 providers/dns/internal/westcn/provider.go | 140 +++++++++++++++++ .../dns/internal/westcn/provider_test.go | 127 +++++++++++++++ providers/dns/westcn/westcn.go | 91 ++--------- providers/dns/westcn/westcn_test.go | 6 +- providers/dns/zz_gen_dns_providers.go | 3 + 18 files changed, 698 insertions(+), 135 deletions(-) create mode 100644 docs/content/dns/zz_gen_com35.md create mode 100644 providers/dns/com35/com35.go create mode 100644 providers/dns/com35/com35.toml create mode 100644 providers/dns/com35/com35_test.go rename providers/dns/{ => internal}/westcn/internal/client.go (98%) rename providers/dns/{ => internal}/westcn/internal/client_test.go (98%) rename providers/dns/{ => internal}/westcn/internal/fixtures/adddnsrecord.json (100%) rename providers/dns/{ => internal}/westcn/internal/fixtures/deldnsrecord.json (100%) rename providers/dns/{ => internal}/westcn/internal/fixtures/error.json (100%) rename providers/dns/{ => internal}/westcn/internal/types.go (100%) create mode 100644 providers/dns/internal/westcn/provider.go create mode 100644 providers/dns/internal/westcn/provider_test.go diff --git a/README.md b/README.md index e9a8caacc..1ad4f4fb6 100644 --- a/README.md +++ b/README.md @@ -61,230 +61,230 @@ If your DNS provider is not supported, please open an [issue](https://github.com
+ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + -
35.com/三五互联 Active24 Akamai EdgeDNS Alibaba Cloud DNSAlibabaCloud ESA
AlibabaCloud ESA all-inkl Alwaysdata Amazon LightsailAmazon Route 53
Amazon Route 53 Anexia CloudDNS ArvanCloud Aurora DNSAutodns
Autodns Axelname Azion Azure (deprecated)Azure DNS
Azure DNS Baidu Cloud Beget.com Binary LaneBindman
Bindman Bluecat BookMyName Brandit (deprecated)Bunny
Bunny Checkdomain Civo Cloud.ruCloudDNS
CloudDNS Cloudflare ClouDNS CloudXNS (Deprecated)ConoHa v2
ConoHa v2 ConoHa v3 Constellix Core-NetworksCPanel/WHM
CPanel/WHM Derak Cloud deSEC.io Designate DNSaaS for OpenstackDigital Ocean
Digital Ocean DirectAdmin DNS Made Easy dnsHome.deDNSimple
DNSimple DNSPod (deprecated) Domain Offensive (do.de) DomeneshopDreamHost
DreamHost Duck DNS Dyn DynDnsFree.deDynu
Dynu EasyDNS EdgeCenter Efficient IPEpik
Epik Exoscale External program F5 XCfreemyip.com
freemyip.com G-Core Gandi Gandi Live DNS (v5)Gigahost.no
Gigahost.no Glesys Go Daddy Google CloudGoogle Domains
Google Domains Gravity Hetzner Hosting.deHosting.nl
Hosting.nl Hostinger Hosttech HTTP requesthttp.net
http.net Huawei Cloud Hurricane Electric DNS HyperOneIBM Cloud (SoftLayer)
IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox InfomaniakInternet Initiative Japan
Internet Initiative Japan Internet.bs INWX IonosIonos Cloud
Ionos Cloud IPv64 ISPConfig 3 ISPConfig 3 - Dynamic DNS (DDNS) Moduleiwantmyname (Deprecated)
iwantmyname (Deprecated) Joker Joohoi's ACME-DNS KeyHelpLiara
Liara Lima-City Linode (v4) Liquid WebLoopia
Loopia LuaDNS Mail-in-a-Box ManageEngine CloudDNSManual
Manual Metaname Metaregistrar mijn.hostMittwald
Mittwald myaddr.{tools,dev,io} MyDNS.jp MythicBeastsName.com
Name.com Namecheap Namesilo NearlyFreeSpeech.NETNeodigit
Neodigit Netcup Netlify NicmanagerNIFCloud
NIFCloud Njalla Nodion NS1Octenium
Octenium Open Telekom Cloud Oracle Cloud OVHplesk.com
plesk.com Porkbun PowerDNS RackspaceRain Yun/雨云
Rain Yun/雨云 RcodeZero reg.ru RegfishRFC2136
RFC2136 RimuHosting RU CENTER Sakura CloudScaleway
Scaleway Selectel Selectel v2 SelfHost.(de|eu)Servercow
Servercow Shellrent Simply.com SonicSpaceship
Spaceship Stackpath Syse TechnitiumTencent Cloud DNS
Tencent Cloud DNS Tencent EdgeOne Timeweb Cloud TransIPUKFast SafeDNS
UKFast SafeDNS Ultradns United-Domains VariomediaVegaDNS
VegaDNS Vercel Versio.[nl|eu|uk] VinylDNSVirtualname
Virtualname VK Cloud Volcano Engine/火山引擎 VscaleVultr
Vultr webnames.ca webnames.ru WebsupportWEDOS
WEDOS West.cn/西部数码 Yandex 360 Yandex CloudYandex PDD
Yandex PDD Zone.ee ZoneEdit Zonomi
diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 220289242..44fec8e54 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -41,6 +41,7 @@ func allDNSCodes() string { "cloudns", "cloudru", "cloudxns", + "com35", "conoha", "conohav3", "constellix", @@ -836,6 +837,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/cloudxns`) + case "com35": + // generated from: providers/dns/com35/com35.toml + ew.writeln(`Configuration for 35.com/三五互联.`) + ew.writeln(`Code: 'com35'`) + ew.writeln(`Since: 'v4.31.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "COM35_PASSWORD": API password`) + ew.writeln(` - "COM35_USERNAME": Username`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "COM35_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "COM35_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "COM35_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "COM35_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/com35`) + case "conoha": // generated from: providers/dns/conoha/conoha.toml ew.writeln(`Configuration for ConoHa v2.`) diff --git a/docs/content/dns/zz_gen_com35.md b/docs/content/dns/zz_gen_com35.md new file mode 100644 index 000000000..e2552e57c --- /dev/null +++ b/docs/content/dns/zz_gen_com35.md @@ -0,0 +1,69 @@ +--- +title: "35.com/三五互联" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: com35 +dnsprovider: + since: "v4.31.0" + code: "com35" + url: "https://www.35.cn/" +--- + + + + + + +Configuration for [35.com/三五互联](https://www.35.cn/). + + + + +- Code: `com35` +- Since: v4.31.0 + + +Here is an example bash command using the 35.com/三五互联 provider: + +```bash +COM35_USERNAME="xxx" \ +COM35_PASSWORD="yyy" \ +lego --dns com35 -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `COM35_PASSWORD` | API password | +| `COM35_USERNAME` | Username | + +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 | +|--------------------------------|-------------| +| `COM35_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `COM35_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `COM35_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `COM35_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | + +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://api.35.cn/CustomerCenter/doc/domain_v2.html) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index ab9ff31c9..2ee3e9006 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/com35/com35.go b/providers/dns/com35/com35.go new file mode 100644 index 000000000..4a9de3a18 --- /dev/null +++ b/providers/dns/com35/com35.go @@ -0,0 +1,104 @@ +// Package com35 implements a DNS provider for solving the DNS-01 challenge using 35.com/三五互联. +package com35 + +import ( + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/westcn" +) + +// Environment variables names. +const ( + envNamespace = "COM35_" + + EnvUsername = envNamespace + "USERNAME" + EnvPassword = envNamespace + "PASSWORD" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +const defaultBaseURL = "https://api.35.cn/api/v2" + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config = westcn.Config + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + TTL: env.GetOrDefaultInt(EnvTTL, 60), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + prv challenge.ProviderTimeout +} + +// NewDNSProvider returns a DNSProvider instance configured for 35.com/三五互联. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvUsername, EnvPassword) + if err != nil { + return nil, fmt.Errorf("35com: %w", err) + } + + config := NewDefaultConfig() + config.Username = values[EnvUsername] + config.Password = values[EnvPassword] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for 35.com/三五互联. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("35com: the configuration of the DNS provider is nil") + } + + provider, err := westcn.NewDNSProviderConfig(config, defaultBaseURL) + if err != nil { + return nil, fmt.Errorf("35com: %w", err) + } + + return &DNSProvider{prv: provider}, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + err := d.prv.Present(domain, token, keyAuth) + if err != nil { + return fmt.Errorf("35com: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + err := d.prv.CleanUp(domain, token, keyAuth) + if err != nil { + return fmt.Errorf("35com: %w", err) + } + + 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.prv.Timeout() +} diff --git a/providers/dns/com35/com35.toml b/providers/dns/com35/com35.toml new file mode 100644 index 000000000..386ee0043 --- /dev/null +++ b/providers/dns/com35/com35.toml @@ -0,0 +1,24 @@ +Name = "35.com/三五互联" +Description = '''''' +URL = "https://www.35.cn/" +Code = "com35" +Since = "v4.31.0" + +Example = ''' +COM35_USERNAME="xxx" \ +COM35_PASSWORD="yyy" \ +lego --dns com35 -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + COM35_USERNAME = "Username" + COM35_PASSWORD = "API password" + [Configuration.Additional] + COM35_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + COM35_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + COM35_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + COM35_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://api.35.cn/CustomerCenter/doc/domain_v2.html" diff --git a/providers/dns/com35/com35_test.go b/providers/dns/com35/com35_test.go new file mode 100644 index 000000000..78fd8f829 --- /dev/null +++ b/providers/dns/com35/com35_test.go @@ -0,0 +1,144 @@ +package com35 + +import ( + "testing" + + "github.com/go-acme/lego/v4/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: "user", + EnvPassword: "secret", + }, + }, + { + desc: "missing username", + envVars: map[string]string{ + EnvUsername: "", + EnvPassword: "secret", + }, + expected: "35com: some credentials information are missing: COM35_USERNAME", + }, + { + desc: "missing password", + envVars: map[string]string{ + EnvUsername: "user", + EnvPassword: "", + }, + expected: "35com: some credentials information are missing: COM35_PASSWORD", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "35com: some credentials information are missing: COM35_USERNAME,COM35_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 test.expected == "" { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.prv) + } 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: "user", + password: "secret", + }, + { + desc: "missing username", + password: "secret", + expected: "35com: credentials missing", + }, + { + desc: "missing password", + username: "user", + expected: "35com: credentials missing", + }, + { + desc: "missing credentials", + expected: "35com: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Username = test.username + config.Password = test.password + + p, err := NewDNSProviderConfig(config) + + if test.expected == "" { + require.NoError(t, err) + require.NotNil(t, p) + require.NotNil(t, p.prv) + } 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/westcn/internal/client.go b/providers/dns/internal/westcn/internal/client.go similarity index 98% rename from providers/dns/westcn/internal/client.go rename to providers/dns/internal/westcn/internal/client.go index bfed159ae..621c7865f 100644 --- a/providers/dns/westcn/internal/client.go +++ b/providers/dns/internal/westcn/internal/client.go @@ -30,7 +30,7 @@ type Client struct { encoder *encoding.Encoder - baseURL *url.URL + BaseURL *url.URL HTTPClient *http.Client } @@ -46,7 +46,7 @@ func NewClient(username, password string) (*Client, error) { username: username, password: password, encoder: simplifiedchinese.GBK.NewEncoder(), - baseURL: baseURL, + BaseURL: baseURL, HTTPClient: &http.Client{Timeout: 10 * time.Second}, }, nil } @@ -116,7 +116,7 @@ func (c *Client) newRequest(ctx context.Context, p, act string, form url.Values) return nil, err } - endpoint := c.baseURL.JoinPath(p, "/") + endpoint := c.BaseURL.JoinPath(p, "/") query := endpoint.Query() query.Set("act", act) diff --git a/providers/dns/westcn/internal/client_test.go b/providers/dns/internal/westcn/internal/client_test.go similarity index 98% rename from providers/dns/westcn/internal/client_test.go rename to providers/dns/internal/westcn/internal/client_test.go index f7bdac5c0..53fd6ed8f 100644 --- a/providers/dns/westcn/internal/client_test.go +++ b/providers/dns/internal/westcn/internal/client_test.go @@ -21,7 +21,7 @@ func mockBuilder() *servermock.Builder[*Client] { } client.HTTPClient = server.Client() - client.baseURL, _ = url.Parse(server.URL) + client.BaseURL, _ = url.Parse(server.URL) return client, nil }, @@ -69,7 +69,8 @@ func TestClientAddRecord_error(t *testing.T) { servermock.ResponseFromFixture("error.json"). WithHeader("Content-Type", "application/json", "Charset=gb2312"), servermock.CheckQueryParameter().Strict(). - With("act", "adddnsrecord")). + With("act", "adddnsrecord"), + ). Build(t) record := Record{ diff --git a/providers/dns/westcn/internal/fixtures/adddnsrecord.json b/providers/dns/internal/westcn/internal/fixtures/adddnsrecord.json similarity index 100% rename from providers/dns/westcn/internal/fixtures/adddnsrecord.json rename to providers/dns/internal/westcn/internal/fixtures/adddnsrecord.json diff --git a/providers/dns/westcn/internal/fixtures/deldnsrecord.json b/providers/dns/internal/westcn/internal/fixtures/deldnsrecord.json similarity index 100% rename from providers/dns/westcn/internal/fixtures/deldnsrecord.json rename to providers/dns/internal/westcn/internal/fixtures/deldnsrecord.json diff --git a/providers/dns/westcn/internal/fixtures/error.json b/providers/dns/internal/westcn/internal/fixtures/error.json similarity index 100% rename from providers/dns/westcn/internal/fixtures/error.json rename to providers/dns/internal/westcn/internal/fixtures/error.json diff --git a/providers/dns/westcn/internal/types.go b/providers/dns/internal/westcn/internal/types.go similarity index 100% rename from providers/dns/westcn/internal/types.go rename to providers/dns/internal/westcn/internal/types.go diff --git a/providers/dns/internal/westcn/provider.go b/providers/dns/internal/westcn/provider.go new file mode 100644 index 000000000..a9e6dad58 --- /dev/null +++ b/providers/dns/internal/westcn/provider.go @@ -0,0 +1,140 @@ +package westcn + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/internal/westcn/internal" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + Username string + Password string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client + + recordIDs map[string]int + recordIDsMu sync.Mutex +} + +// NewDNSProviderConfig return a DNSProvider instance configured for West.cn/西部数码. +func NewDNSProviderConfig(config *Config, baseURL string) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.Username, config.Password) + if err != nil { + return nil, fmt.Errorf("%w", err) + } + + if baseURL != "" { + client.BaseURL, err = url.Parse(baseURL) + if err != nil { + return nil, err + } + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + 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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("%w", err) + } + + record := internal.Record{ + Domain: dns01.UnFqdn(authZone), + Host: subDomain, + Type: "TXT", + Value: info.Value, + TTL: d.config.TTL, + } + + recordID, err := d.client.AddRecord(context.Background(), record) + if err != nil { + return fmt.Errorf("add record: %w", err) + } + + d.recordIDsMu.Lock() + d.recordIDs[token] = recordID + 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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("could not find zone for domain %q: %w", domain, err) + } + + // gets the record's unique ID + d.recordIDsMu.Lock() + recordID, ok := d.recordIDs[token] + d.recordIDsMu.Unlock() + + if !ok { + return fmt.Errorf("unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) + } + + err = d.client.DeleteRecord(context.Background(), dns01.UnFqdn(authZone), recordID) + if err != nil { + return fmt.Errorf("delete record: %w", err) + } + + // deletes 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/internal/westcn/provider_test.go b/providers/dns/internal/westcn/provider_test.go new file mode 100644 index 000000000..2ae0f09cb --- /dev/null +++ b/providers/dns/internal/westcn/provider_test.go @@ -0,0 +1,127 @@ +package westcn + +import ( + "net/http/httptest" + "testing" + "time" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +func TestNewDNSProviderConfig(t *testing.T) { + testCases := []struct { + desc string + username string + password string + expected string + }{ + { + desc: "success", + username: "user", + password: "secret", + }, + { + desc: "missing username", + password: "secret", + expected: "credentials missing", + }, + { + desc: "missing password", + username: "user", + expected: "credentials missing", + }, + { + desc: "missing credentials", + expected: "credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := &Config{} + config.Username = test.username + config.Password = test.password + + 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 mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := &Config{ + Username: "user", + Password: "secret", + PropagationTimeout: 10 * time.Second, + PollingInterval: 1 * time.Second, + TTL: 120, + HTTPClient: server.Client(), + } + + p, err := NewDNSProviderConfig(config, server.URL) + if err != nil { + return nil, err + } + + return p, nil + }, + servermock.CheckHeader(). + WithContentTypeFromURLEncoded()) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("POST /domain/", + servermock.ResponseFromInternal("adddnsrecord.json"). + WithHeader("Content-Type", "application/json", "Charset=gb2312"), + servermock.CheckQueryParameter().Strict(). + With("act", "adddnsrecord"), + servermock.CheckForm().UsePostForm().Strict(). + With("domain", "example.com"). + With("host", "_acme-challenge"). + With("ttl", "120"). + With("type", "TXT"). + With("value", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"). + // With("act", "adddnsrecord"). + With("username", "user"). + WithRegexp("time", `\d+`). + WithRegexp("token", `[a-z0-9]{32}`), + ). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("POST /domain/", + servermock.ResponseFromInternal("deldnsrecord.json"). + WithHeader("Content-Type", "application/json", "Charset=gb2312"), + servermock.CheckQueryParameter().Strict(). + With("act", "deldnsrecord"), + servermock.CheckForm().UsePostForm().Strict(). + With("id", "123"). + With("domain", "example.com"). + With("username", "user"). + WithRegexp("time", `\d+`). + WithRegexp("token", `[a-z0-9]{32}`), + ). + Build(t) + + provider.recordIDs["abc"] = 123 + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/westcn/westcn.go b/providers/dns/westcn/westcn.go index c641f661d..1906f9737 100644 --- a/providers/dns/westcn/westcn.go +++ b/providers/dns/westcn/westcn.go @@ -2,18 +2,14 @@ package westcn import ( - "context" "errors" "fmt" "net/http" - "sync" "time" "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" - "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" - "github.com/go-acme/lego/v4/providers/dns/westcn/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/westcn" ) // Environment variables names. @@ -29,18 +25,12 @@ const ( EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" ) +const defaultBaseURL = "https://api.west.cn/api/v2" + var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. -type Config struct { - Username string - Password string - - PropagationTimeout time.Duration - PollingInterval time.Duration - TTL int - HTTPClient *http.Client -} +type Config = westcn.Config // NewDefaultConfig returns a default configuration for the DNSProvider. func NewDefaultConfig() *Config { @@ -56,11 +46,7 @@ func NewDefaultConfig() *Config { // DNSProvider implements the challenge.Provider interface. type DNSProvider struct { - config *Config - client *internal.Client - - recordIDs map[string]int - recordIDsMu sync.Mutex + prv challenge.ProviderTimeout } // NewDNSProvider returns a DNSProvider instance configured for West.cn/西部数码. @@ -83,91 +69,36 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("westcn: the configuration of the DNS provider is nil") } - client, err := internal.NewClient(config.Username, config.Password) + provider, err := westcn.NewDNSProviderConfig(config, defaultBaseURL) if err != nil { return nil, fmt.Errorf("westcn: %w", err) } - if config.HTTPClient != nil { - client.HTTPClient = config.HTTPClient - } - - client.HTTPClient = clientdebug.Wrap(client.HTTPClient) - - return &DNSProvider{ - config: config, - client: client, - recordIDs: make(map[string]int), - }, nil + return &DNSProvider{prv: provider}, nil } // Present creates a TXT record using the specified parameters. func (d *DNSProvider) Present(domain, token, keyAuth string) error { - info := dns01.GetChallengeInfo(domain, keyAuth) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("westcn: could not find zone for domain %q: %w", domain, err) - } - - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + err := d.prv.Present(domain, token, keyAuth) if err != nil { return fmt.Errorf("westcn: %w", err) } - record := internal.Record{ - Domain: dns01.UnFqdn(authZone), - Host: subDomain, - Type: "TXT", - Value: info.Value, - TTL: d.config.TTL, - } - - recordID, err := d.client.AddRecord(context.Background(), record) - if err != nil { - return fmt.Errorf("westcn: add record: %w", err) - } - - d.recordIDsMu.Lock() - d.recordIDs[token] = recordID - 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) - - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + err := d.prv.CleanUp(domain, token, keyAuth) if err != nil { - return fmt.Errorf("westcn: could not find zone for domain %q: %w", domain, err) + return fmt.Errorf("westcn: %w", err) } - // gets the record's unique ID - d.recordIDsMu.Lock() - recordID, ok := d.recordIDs[token] - d.recordIDsMu.Unlock() - - if !ok { - return fmt.Errorf("westcn: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) - } - - err = d.client.DeleteRecord(context.Background(), dns01.UnFqdn(authZone), recordID) - if err != nil { - return fmt.Errorf("westcn: delete record: %w", err) - } - - // deletes 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 + return d.prv.Timeout() } diff --git a/providers/dns/westcn/westcn_test.go b/providers/dns/westcn/westcn_test.go index 36827fd06..a546d518e 100644 --- a/providers/dns/westcn/westcn_test.go +++ b/providers/dns/westcn/westcn_test.go @@ -60,8 +60,7 @@ func TestNewDNSProvider(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } @@ -108,8 +107,7 @@ func TestNewDNSProviderConfig(t *testing.T) { if test.expected == "" { require.NoError(t, err) require.NotNil(t, p) - require.NotNil(t, p.config) - require.NotNil(t, p.client) + require.NotNil(t, p.prv) } else { require.EqualError(t, err, test.expected) } diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index c5db54109..0d9ad26e8 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -35,6 +35,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/cloudns" "github.com/go-acme/lego/v4/providers/dns/cloudru" "github.com/go-acme/lego/v4/providers/dns/cloudxns" + "github.com/go-acme/lego/v4/providers/dns/com35" "github.com/go-acme/lego/v4/providers/dns/conoha" "github.com/go-acme/lego/v4/providers/dns/conohav3" "github.com/go-acme/lego/v4/providers/dns/constellix" @@ -248,6 +249,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return cloudru.NewDNSProvider() case "cloudxns": return cloudxns.NewDNSProvider() + case "com35": + return com35.NewDNSProvider() case "conoha": return conoha.NewDNSProvider() case "conohav3": From dd6ab7ca95c90bde76719e9c8121ec8274f3aff8 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 7 Jan 2026 18:03:32 +0100 Subject: [PATCH 260/298] Add DNS provider for JDCloud (#2782) --- README.md | 47 ++-- cmd/zz_gen_cmd_dnshelp.go | 23 ++ docs/content/dns/zz_gen_jdcloud.md | 71 +++++ docs/data/zz_cli_help.toml | 2 +- go.mod | 2 + go.sum | 4 + .../fixtures/create_record-request.json | 15 ++ .../dns/jdcloud/fixtures/create_record.json | 25 ++ .../dns/jdcloud/fixtures/delete_record.json | 9 + .../fixtures/describe_domains_page1.json | 55 ++++ .../fixtures/describe_domains_page2.json | 55 ++++ providers/dns/jdcloud/jdcloud.go | 217 ++++++++++++++++ providers/dns/jdcloud/jdcloud.toml | 27 ++ providers/dns/jdcloud/jdcloud_test.go | 242 ++++++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 15 files changed, 775 insertions(+), 22 deletions(-) create mode 100644 docs/content/dns/zz_gen_jdcloud.md create mode 100644 providers/dns/jdcloud/fixtures/create_record-request.json create mode 100644 providers/dns/jdcloud/fixtures/create_record.json create mode 100644 providers/dns/jdcloud/fixtures/delete_record.json create mode 100644 providers/dns/jdcloud/fixtures/describe_domains_page1.json create mode 100644 providers/dns/jdcloud/fixtures/describe_domains_page2.json create mode 100644 providers/dns/jdcloud/jdcloud.go create mode 100644 providers/dns/jdcloud/jdcloud.toml create mode 100644 providers/dns/jdcloud/jdcloud_test.go diff --git a/README.md b/README.md index 1ad4f4fb6..c02347e23 100644 --- a/README.md +++ b/README.md @@ -177,114 +177,119 @@ If your DNS provider is not supported, please open an [issue](https://github.com ISPConfig 3 - Dynamic DNS (DDNS) Module iwantmyname (Deprecated) + JD Cloud Joker Joohoi's ACME-DNS - KeyHelp + KeyHelp Liara Lima-City Linode (v4) - Liquid Web + Liquid Web Loopia LuaDNS Mail-in-a-Box - ManageEngine CloudDNS + ManageEngine CloudDNS Manual Metaname Metaregistrar - mijn.host + mijn.host Mittwald myaddr.{tools,dev,io} MyDNS.jp - MythicBeasts + MythicBeasts Name.com Namecheap Namesilo - NearlyFreeSpeech.NET + NearlyFreeSpeech.NET Neodigit Netcup Netlify - Nicmanager + Nicmanager NIFCloud Njalla Nodion - NS1 + NS1 Octenium Open Telekom Cloud Oracle Cloud - OVH + OVH plesk.com Porkbun PowerDNS - Rackspace + Rackspace Rain Yun/雨云 RcodeZero reg.ru - Regfish + Regfish RFC2136 RimuHosting RU CENTER - Sakura Cloud + Sakura Cloud Scaleway Selectel Selectel v2 - SelfHost.(de|eu) + SelfHost.(de|eu) Servercow Shellrent Simply.com - Sonic + Sonic Spaceship Stackpath Syse - Technitium + Technitium Tencent Cloud DNS Tencent EdgeOne Timeweb Cloud - TransIP + TransIP UKFast SafeDNS Ultradns United-Domains - Variomedia + Variomedia VegaDNS Vercel Versio.[nl|eu|uk] - VinylDNS + VinylDNS Virtualname VK Cloud Volcano Engine/火山引擎 - Vscale + Vscale Vultr webnames.ca webnames.ru - Websupport + Websupport WEDOS West.cn/西部数码 Yandex 360 - Yandex Cloud + Yandex Cloud Yandex PDD Zone.ee ZoneEdit + Zonomi + + + diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 44fec8e54..a918a1484 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -105,6 +105,7 @@ func allDNSCodes() string { "ispconfig", "ispconfigddns", "iwantmyname", + "jdcloud", "joker", "keyhelp", "liara", @@ -2195,6 +2196,28 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/iwantmyname`) + case "jdcloud": + // generated from: providers/dns/jdcloud/jdcloud.toml + ew.writeln(`Configuration for JD Cloud.`) + ew.writeln(`Code: 'jdcloud'`) + ew.writeln(`Since: 'v4.31.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "JDCLOUD_ACCESS_KEY_ID": Access key ID`) + ew.writeln(` - "JDCLOUD_ACCESS_KEY_SECRET": Access key secret`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "JDCLOUD_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "JDCLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "JDCLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "JDCLOUD_REGION_ID": Region ID (Default: cn-north-1)`) + ew.writeln(` - "JDCLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/jdcloud`) + case "joker": // generated from: providers/dns/joker/joker.toml ew.writeln(`Configuration for Joker.`) diff --git a/docs/content/dns/zz_gen_jdcloud.md b/docs/content/dns/zz_gen_jdcloud.md new file mode 100644 index 000000000..a37cc3520 --- /dev/null +++ b/docs/content/dns/zz_gen_jdcloud.md @@ -0,0 +1,71 @@ +--- +title: "JD Cloud" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: jdcloud +dnsprovider: + since: "v4.31.0" + code: "jdcloud" + url: "https://www.jdcloud.com/" +--- + + + + + + +Configuration for [JD Cloud](https://www.jdcloud.com/). + + + + +- Code: `jdcloud` +- Since: v4.31.0 + + +Here is an example bash command using the JD Cloud provider: + +```bash +JDCLOUD_ACCESS_KEY_ID="xxx" \ +JDCLOUD_ACCESS_KEY_SECRET="yyy" \ +lego --dns jdcloud -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `JDCLOUD_ACCESS_KEY_ID` | Access key ID | +| `JDCLOUD_ACCESS_KEY_SECRET` | Access key secret | + +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 | +|--------------------------------|-------------| +| `JDCLOUD_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `JDCLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `JDCLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `JDCLOUD_REGION_ID` | Region ID (Default: cn-north-1) | +| `JDCLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://docs.jdcloud.com/cn/jd-cloud-dns/api/overview) +- [Go client](https://github.com/jdcloud-api/jdcloud-sdk-go) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 2ee3e9006..7d1a4b2c3 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/go.mod b/go.mod index dd1a5b4b7..59da5e349 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/exoscale/egoscale/v3 v3.1.31 github.com/go-acme/alidns-20150109/v4 v4.7.0 github.com/go-acme/esa-20240910/v2 v2.40.3 + github.com/go-acme/jdcloud-sdk-go v1.64.0 github.com/go-acme/tencentclouddnspod v1.1.25 github.com/go-acme/tencentedgdeone v1.1.48 github.com/go-jose/go-jose/v4 v4.1.3 @@ -154,6 +155,7 @@ require ( github.com/go-resty/resty/v2 v2.17.0 // indirect github.com/goccy/go-yaml v1.9.8 // indirect github.com/gofrs/flock v0.13.0 // indirect + github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/protobuf v1.5.4 // indirect diff --git a/go.sum b/go.sum index 5e63fdba3..993e97c7d 100644 --- a/go.sum +++ b/go.sum @@ -317,6 +317,8 @@ github.com/go-acme/alidns-20150109/v4 v4.7.0 h1:PqJ/wR0JTpL4v0Owu1uM7bPQ1Yww0eQL github.com/go-acme/alidns-20150109/v4 v4.7.0/go.mod h1:btQvB6xZoN6ykKB74cPhiR+uvhrEE2AFVXm6RDmCHm0= github.com/go-acme/esa-20240910/v2 v2.40.3 h1:xXOMRex148wKEHbv7Izn73/HdAxSmz5GOaF4HdnqN+M= github.com/go-acme/esa-20240910/v2 v2.40.3/go.mod h1:ZYdN9EN9ikn26SNapxCVjZ65pHT/1qm4fzuJ7QGVX6g= +github.com/go-acme/jdcloud-sdk-go v1.64.0 h1:AW9j5khk8tRYbpBJPxKmqdwIqgLs2Fz3HUK3hn2YXjs= +github.com/go-acme/jdcloud-sdk-go v1.64.0/go.mod h1:qc/m8HNX1Zgd7GAv2DSEinup8fwy3Ted3/VVx7LB5bU= github.com/go-acme/tencentclouddnspod v1.1.25 h1:7H3ZKshkaHzCXfRpAHVB5nvxeDDl2XLeNZfrNHiZj/s= github.com/go-acme/tencentclouddnspod v1.1.25/go.mod h1:XXfzp0AYV7UAUsHKT6R0KAUJFhqAUXmWGF07Elpa5cE= github.com/go-acme/tencentedgdeone v1.1.48 h1:WLyLBsRVhSLFmtbEFXk0naLODSQn7X6J0Fc/qR8xVUk= @@ -374,6 +376,8 @@ github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXK github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= diff --git a/providers/dns/jdcloud/fixtures/create_record-request.json b/providers/dns/jdcloud/fixtures/create_record-request.json new file mode 100644 index 000000000..581c00fea --- /dev/null +++ b/providers/dns/jdcloud/fixtures/create_record-request.json @@ -0,0 +1,15 @@ +{ + "domainId": "20", + "regionId": "cn-north-1", + "req": { + "hostRecord": "_acme-challenge", + "hostValue": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "jcloudRes": null, + "mxPriority": null, + "port": null, + "ttl": 120, + "type": "TXT", + "viewValue": -1, + "weight": null + } +} diff --git a/providers/dns/jdcloud/fixtures/create_record.json b/providers/dns/jdcloud/fixtures/create_record.json new file mode 100644 index 000000000..08bd3db26 --- /dev/null +++ b/providers/dns/jdcloud/fixtures/create_record.json @@ -0,0 +1,25 @@ +{ + "requestId": "azerty", + "error": { + "code": 0, + "status": "", + "message": "" + }, + "result": { + "dataList": { + "id": 123, + "hostRecord": "_acme-challenge", + "hostValue": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "jcloudRes": false, + "mxPriority": 0, + "port": 0, + "ttl": 120, + "type": "TXT", + "weight": 0, + "viewValue": [ + 1, + 2 + ] + } + } +} diff --git a/providers/dns/jdcloud/fixtures/delete_record.json b/providers/dns/jdcloud/fixtures/delete_record.json new file mode 100644 index 000000000..20525751c --- /dev/null +++ b/providers/dns/jdcloud/fixtures/delete_record.json @@ -0,0 +1,9 @@ +{ + "requestId": "azerty", + "error": { + "code": 0, + "status": "", + "message": "" + }, + "result": {} +} diff --git a/providers/dns/jdcloud/fixtures/describe_domains_page1.json b/providers/dns/jdcloud/fixtures/describe_domains_page1.json new file mode 100644 index 000000000..cde6dcd6f --- /dev/null +++ b/providers/dns/jdcloud/fixtures/describe_domains_page1.json @@ -0,0 +1,55 @@ +{ + "requestId": "azerty", + "error": { + "code": 0, + "status": "", + "message": "" + }, + "result": { + "dataList": [ + { + "id": 1, + "domainName": "1.example" + }, + { + "id": 2, + "domainName": "2.example" + }, + { + "id": 3, + "domainName": "3.example" + }, + { + "id": 4, + "domainName": "4.example" + }, + { + "id": 5, + "domainName": "5.example" + }, + { + "id": 6, + "domainName": "6.example" + }, + { + "id": 7, + "domainName": "7.example" + }, + { + "id": 8, + "domainName": "8.example" + }, + { + "id": 9, + "domainName": "9.example" + }, + { + "id": 10, + "domainName": "10.example" + } + ], + "currentCount": 10, + "totalCount": 20, + "totalPage": 2 + } +} diff --git a/providers/dns/jdcloud/fixtures/describe_domains_page2.json b/providers/dns/jdcloud/fixtures/describe_domains_page2.json new file mode 100644 index 000000000..b1e1560ab --- /dev/null +++ b/providers/dns/jdcloud/fixtures/describe_domains_page2.json @@ -0,0 +1,55 @@ +{ + "requestId": "azerty", + "error": { + "code": 0, + "status": "", + "message": "" + }, + "result": { + "dataList": [ + { + "id": 11, + "domainName": "11.example" + }, + { + "id": 12, + "domainName": "12.example" + }, + { + "id": 13, + "domainName": "13.example" + }, + { + "id": 14, + "domainName": "14.example" + }, + { + "id": 15, + "domainName": "15.example" + }, + { + "id": 16, + "domainName": "16.example" + }, + { + "id": 17, + "domainName": "17.example" + }, + { + "id": 18, + "domainName": "18.example" + }, + { + "id": 19, + "domainName": "19.example" + }, + { + "id": 20, + "domainName": "example.com" + } + ], + "currentCount": 10, + "totalCount": 20, + "totalPage": 2 + } +} diff --git a/providers/dns/jdcloud/jdcloud.go b/providers/dns/jdcloud/jdcloud.go new file mode 100644 index 000000000..7d9ad4e6b --- /dev/null +++ b/providers/dns/jdcloud/jdcloud.go @@ -0,0 +1,217 @@ +// Package jdcloud implements a DNS provider for solving the DNS-01 challenge using JD Cloud. +package jdcloud + +import ( + "errors" + "fmt" + "strconv" + "sync" + "time" + + "github.com/go-acme/jdcloud-sdk-go/core" + "github.com/go-acme/jdcloud-sdk-go/services/domainservice/apis" + jdcclient "github.com/go-acme/jdcloud-sdk-go/services/domainservice/client" + domainservice "github.com/go-acme/jdcloud-sdk-go/services/domainservice/models" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" +) + +// Environment variables names. +const ( + envNamespace = "JDCLOUD_" + + EnvAccessKeyID = envNamespace + "ACCESS_KEY_ID" + EnvAccessKeySecret = envNamespace + "ACCESS_KEY_SECRET" + EnvRegionID = envNamespace + "REGION_ID" + + 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 { + AccessKeyID string + AccessKeySecret string + RegionID string + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPTimeout time.Duration +} + +// 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), + HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *jdcclient.DomainserviceClient + + recordIDs map[string]int + domainIDs map[string]int + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for JD Cloud. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAccessKeyID, EnvAccessKeySecret) + if err != nil { + return nil, fmt.Errorf("jdcloud: %w", err) + } + + config := NewDefaultConfig() + config.AccessKeyID = values[EnvAccessKeyID] + config.AccessKeySecret = values[EnvAccessKeySecret] + + // https://docs.jdcloud.com/en/common-declaration/api/introduction#Region%20Code + config.RegionID = env.GetOrDefaultString(EnvRegionID, "cn-north-1") + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for JD Cloud. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("jdcloud: the configuration of the DNS provider is nil") + } + + if config.AccessKeyID == "" || config.AccessKeySecret == "" { + return nil, errors.New("jdcloud: missing credentials") + } + + cred := core.NewCredentials(config.AccessKeyID, config.AccessKeySecret) + + client := jdcclient.NewDomainserviceClient(cred) + client.DisableLogger() + client.Config.SetTimeout(config.HTTPTimeout) + + return &DNSProvider{ + config: config, + client: client, + recordIDs: make(map[string]int), + domainIDs: 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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("jdcloud: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("jdcloud: %w", err) + } + + zone, err := d.findZone(dns01.UnFqdn(authZone)) + if err != nil { + return fmt.Errorf("jdcloud: %w", err) + } + + // https://docs.jdcloud.com/cn/jd-cloud-dns/api/createresourcerecord + crrr := apis.NewCreateResourceRecordRequestWithAllParams( + d.config.RegionID, + strconv.Itoa(zone.Id), + &domainservice.AddRR{ + HostRecord: subDomain, + HostValue: info.Value, + Ttl: d.config.TTL, + Type: "TXT", + ViewValue: -1, + }, + ) + + record, err := jdcclient.CreateResourceRecord(d.client, crrr) + if err != nil { + return fmt.Errorf("jdcloud: create resource record: %w", err) + } + + d.recordIDsMu.Lock() + d.domainIDs[token] = zone.Id + d.recordIDs[token] = record.Result.DataList.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) + + d.recordIDsMu.Lock() + recordID, recordOK := d.recordIDs[token] + domainID, domainOK := d.domainIDs[token] + d.recordIDsMu.Unlock() + + if !recordOK { + return fmt.Errorf("jdcloud: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) + } + + if !domainOK { + return fmt.Errorf("jdcloud: unknown domain ID for '%s' '%s'", info.EffectiveFQDN, token) + } + + // https://docs.jdcloud.com/cn/jd-cloud-dns/api/deleteresourcerecord + drrr := apis.NewDeleteResourceRecordRequestWithAllParams( + d.config.RegionID, + strconv.Itoa(domainID), + strconv.Itoa(recordID), + ) + + _, err := jdcclient.DeleteResourceRecord(d.client, drrr) + if err != nil { + return fmt.Errorf("jdcloud: delete resource record: %w", err) + } + + 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 +} + +func (d *DNSProvider) findZone(zone string) (*domainservice.DomainInfo, error) { + // https://docs.jdcloud.com/cn/jd-cloud-dns/api/describedomains + ddr := apis.NewDescribeDomainsRequestWithoutParam() + ddr.SetRegionId(d.config.RegionID) + ddr.SetPageNumber(1) + ddr.SetPageSize(10) + ddr.SetDomainName(zone) + + for { + response, err := jdcclient.DescribeDomains(d.client, ddr) + if err != nil { + return nil, fmt.Errorf("describe domains: %w", err) + } + + for _, d := range response.Result.DataList { + if d.DomainName == zone { + return &d, nil + } + } + + if len(response.Result.DataList) < ddr.PageSize || response.Result.TotalPage <= ddr.PageNumber { + break + } + + ddr.SetPageNumber(ddr.PageNumber + 1) + } + + return nil, errors.New("zone not found") +} diff --git a/providers/dns/jdcloud/jdcloud.toml b/providers/dns/jdcloud/jdcloud.toml new file mode 100644 index 000000000..7ab403822 --- /dev/null +++ b/providers/dns/jdcloud/jdcloud.toml @@ -0,0 +1,27 @@ +Name = "JD Cloud" +Description = '''''' +URL = "https://www.jdcloud.com/" +Code = "jdcloud" +Since = "v4.31.0" + +Example = ''' +JDCLOUD_ACCESS_KEY_ID="xxx" \ +JDCLOUD_ACCESS_KEY_SECRET="yyy" \ +lego --dns jdcloud -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + JDCLOUD_ACCESS_KEY_ID = "Access key ID" + JDCLOUD_ACCESS_KEY_SECRET = "Access key secret" + [Configuration.Additional] + JDCLOUD_REGION_ID = "Region ID (Default: cn-north-1)" + JDCLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + JDCLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + JDCLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + JDCLOUD_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://docs.jdcloud.com/cn/jd-cloud-dns/api/overview" + Common = "https://docs.jdcloud.com/en/common-declaration/api/introduction" + GoClient = "https://github.com/jdcloud-api/jdcloud-sdk-go" diff --git a/providers/dns/jdcloud/jdcloud_test.go b/providers/dns/jdcloud/jdcloud_test.go new file mode 100644 index 000000000..6b3368938 --- /dev/null +++ b/providers/dns/jdcloud/jdcloud_test.go @@ -0,0 +1,242 @@ +package jdcloud + +import ( + "fmt" + "net" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest( + EnvAccessKeyID, + EnvAccessKeySecret, + EnvRegionID, +).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvAccessKeyID: "abc123", + EnvAccessKeySecret: "secret", + }, + }, + { + desc: "missing access key ID", + envVars: map[string]string{ + EnvAccessKeyID: "", + EnvAccessKeySecret: "secret", + }, + expected: "jdcloud: some credentials information are missing: JDCLOUD_ACCESS_KEY_ID", + }, + { + desc: "missing access key secret", + envVars: map[string]string{ + EnvAccessKeyID: "abc123", + EnvAccessKeySecret: "", + }, + expected: "jdcloud: some credentials information are missing: JDCLOUD_ACCESS_KEY_SECRET", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "jdcloud: some credentials information are missing: JDCLOUD_ACCESS_KEY_ID,JDCLOUD_ACCESS_KEY_SECRET", + }, + } + + 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 + accessKeyID string + accessKeySecret string + expected string + }{ + { + desc: "success", + accessKeyID: "abc123", + accessKeySecret: "secret", + }, + { + desc: "missing access key ID", + accessKeySecret: "secret", + expected: "jdcloud: missing credentials", + }, + { + desc: "missing access key secret", + accessKeyID: "abc123", + expected: "jdcloud: missing credentials", + }, + { + desc: "missing credentials", + expected: "jdcloud: missing credentials", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.AccessKeyID = test.accessKeyID + config.AccessKeySecret = test.accessKeySecret + config.RegionID = "cn-north-1" + + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.AccessKeyID = "abc123" + config.AccessKeySecret = "secret" + config.RegionID = "cn-north-1" + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + serverURL, _ := url.Parse(server.URL) + + p.client.Config.SetEndpoint(net.JoinHostPort(serverURL.Hostname(), serverURL.Port())) + p.client.Config.SetScheme(serverURL.Scheme) + p.client.Config.SetTimeout(server.Client().Timeout) + + return p, nil + }, + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("GET /v2/regions/cn-north-1/domain", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + pageNumber := req.URL.Query().Get("pageNumber") + + servermock.ResponseFromFixture( + fmt.Sprintf("describe_domains_page%s.json", pageNumber), + ).ServeHTTP(rw, req) + }), + servermock.CheckQueryParameter().Strict(). + With("domainName", "example.com"). + WithRegexp("pageNumber", `(1|2)`). + With("pageSize", "10"), + servermock.CheckHeader(). + WithRegexp("Authorization", + `JDCLOUD2-HMAC-SHA256 Credential=abc123/\d{8}/cn-north-1/domainservice/jdcloud2_request, SignedHeaders=content-type;host;x-jdcloud-date;x-jdcloud-nonce, Signature=\w+`). + WithRegexp("X-Jdcloud-Date", `\d{8}T\d{6}Z`). + WithRegexp("X-Jdcloud-Nonce", `[\w-]+`), + ). + Route("POST /v2/regions/cn-north-1/domain/20/ResourceRecord", + servermock.ResponseFromFixture("create_record.json"), + servermock.CheckRequestJSONBodyFromFixture("create_record-request.json"), + servermock.CheckHeader(). + WithRegexp("Authorization", + `JDCLOUD2-HMAC-SHA256 Credential=abc123/\d{8}/cn-north-1/domainservice/jdcloud2_request, SignedHeaders=content-type;host;x-jdcloud-date;x-jdcloud-nonce, Signature=\w+`). + WithRegexp("X-Jdcloud-Date", `\d{8}T\d{6}Z`). + WithRegexp("X-Jdcloud-Nonce", `[\w-]+`), + ). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) + + require.Len(t, provider.domainIDs, 1) + require.Len(t, provider.recordIDs, 1) + + assert.Equal(t, 20, provider.domainIDs["abc"]) + assert.Equal(t, 123, provider.recordIDs["abc"]) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("DELETE /v2/regions/cn-north-1/domain/20/ResourceRecord/123", + servermock.ResponseFromFixture("delete_record.json"), + servermock.CheckHeader(). + WithRegexp("Authorization", + `JDCLOUD2-HMAC-SHA256 Credential=abc123/\d{8}/cn-north-1/domainservice/jdcloud2_request, SignedHeaders=content-type;host;x-jdcloud-date;x-jdcloud-nonce, Signature=\w+`). + WithRegexp("X-Jdcloud-Date", `\d{8}T\d{6}Z`). + WithRegexp("X-Jdcloud-Nonce", `[\w-]+`), + ). + Build(t) + + provider.domainIDs["abc"] = 20 + provider.recordIDs["abc"] = 123 + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 0d9ad26e8..7099a3f4c 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -99,6 +99,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/ispconfig" "github.com/go-acme/lego/v4/providers/dns/ispconfigddns" "github.com/go-acme/lego/v4/providers/dns/iwantmyname" + "github.com/go-acme/lego/v4/providers/dns/jdcloud" "github.com/go-acme/lego/v4/providers/dns/joker" "github.com/go-acme/lego/v4/providers/dns/keyhelp" "github.com/go-acme/lego/v4/providers/dns/liara" @@ -377,6 +378,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return ispconfigddns.NewDNSProvider() case "iwantmyname": return iwantmyname.NewDNSProvider() + case "jdcloud": + return jdcloud.NewDNSProvider() case "joker": return joker.NewDNSProvider() case "keyhelp": From b7a9b7dad0a2ffe4f90f88c0141f337e781253db Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 8 Jan 2026 17:33:11 +0100 Subject: [PATCH 261/298] chore: update dependencies (#2783) --- go.mod | 60 ++++++++++++++-------------- go.sum | 123 +++++++++++++++++++++++++++++---------------------------- 2 files changed, 92 insertions(+), 91 deletions(-) diff --git a/go.mod b/go.mod index 59da5e349..42e11820b 100644 --- a/go.mod +++ b/go.mod @@ -13,43 +13,43 @@ require ( github.com/Azure/go-autorest/autorest v0.11.30 github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 github.com/Azure/go-autorest/autorest/to v0.4.1 - github.com/BurntSushi/toml v1.5.0 + github.com/BurntSushi/toml v1.6.0 github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 - github.com/alibabacloud-go/tea v1.3.14 + github.com/alibabacloud-go/tea v1.4.0 github.com/aliyun/credentials-go v1.4.7 github.com/aws/aws-sdk-go-v2 v1.41.0 - github.com/aws/aws-sdk-go-v2/config v1.32.5 - github.com/aws/aws-sdk-go-v2/credentials v1.19.5 + github.com/aws/aws-sdk-go-v2/config v1.32.6 + github.com/aws/aws-sdk-go-v2/credentials v1.19.6 github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.10 github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0 + github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 github.com/aziontech/azionapi-go-sdk v0.144.0 - github.com/baidubce/bce-sdk-go v0.9.254 + github.com/baidubce/bce-sdk-go v0.9.256 github.com/cenkalti/backoff/v5 v5.0.3 github.com/dnsimple/dnsimple-go/v4 v4.0.0 - github.com/exoscale/egoscale/v3 v3.1.31 + github.com/exoscale/egoscale/v3 v3.1.33 github.com/go-acme/alidns-20150109/v4 v4.7.0 - github.com/go-acme/esa-20240910/v2 v2.40.3 + github.com/go-acme/esa-20240910/v2 v2.44.0 github.com/go-acme/jdcloud-sdk-go v1.64.0 github.com/go-acme/tencentclouddnspod v1.1.25 github.com/go-acme/tencentedgdeone v1.1.48 github.com/go-jose/go-jose/v4 v4.1.3 github.com/go-viper/mapstructure/v2 v2.4.0 github.com/google/go-cmp v0.7.0 - github.com/google/go-querystring v1.1.0 + github.com/google/go-querystring v1.2.0 github.com/google/uuid v1.6.0 github.com/gophercloud/gophercloud v1.14.1 github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/go-version v1.8.0 - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.180 + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.182 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 github.com/labbsr0x/bindman-dns-webhook v1.0.2 github.com/ldez/grignotin v0.10.1 - github.com/linode/linodego v1.62.0 + github.com/linode/linodego v1.64.0 github.com/liquidweb/liquidweb-go v1.6.4 github.com/mattn/go-isatty v0.0.20 github.com/miekg/dns v1.1.69 @@ -65,8 +65,8 @@ require ( github.com/nrdcg/mailinabox v0.3.0 github.com/nrdcg/namesilo v0.5.0 github.com/nrdcg/nodion v0.1.0 - github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.1 - github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.1 + github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2 + github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.2 github.com/nrdcg/porkbun v0.4.0 github.com/nrdcg/vegadns v0.3.0 github.com/nzdjb/go-metaname v1.0.0 @@ -76,34 +76,34 @@ require ( github.com/regfish/regfish-dnsapi-go v0.1.1 github.com/sacloud/api-client-go v0.3.3 github.com/sacloud/iaas-api-go v1.23.1 - github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35 + github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 github.com/selectel/domains-go v1.1.0 github.com/selectel/go-selvpcclient/v4 v4.1.0 github.com/softlayer/softlayer-go v1.2.1 github.com/stretchr/testify v1.11.1 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.12 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.28 github.com/transip/gotransip/v6 v6.26.1 github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 github.com/urfave/cli/v2 v2.27.7 github.com/vinyldns/go-vinyldns v0.9.17 - github.com/volcengine/volc-sdk-golang v1.0.230 - github.com/vultr/govultr/v3 v3.26.0 - github.com/yandex-cloud/go-genproto v0.41.0 - github.com/yandex-cloud/go-sdk/services/dns v0.0.23 - github.com/yandex-cloud/go-sdk/v2 v2.33.0 + github.com/volcengine/volc-sdk-golang v1.0.233 + github.com/vultr/govultr/v3 v3.26.1 + github.com/yandex-cloud/go-genproto v0.43.0 + github.com/yandex-cloud/go-sdk/services/dns v0.0.25 + github.com/yandex-cloud/go-sdk/v2 v2.37.0 golang.org/x/crypto v0.46.0 golang.org/x/net v0.48.0 golang.org/x/oauth2 v0.34.0 golang.org/x/text v0.32.0 golang.org/x/time v0.14.0 - google.golang.org/api v0.257.0 + google.golang.org/api v0.259.0 gopkg.in/ns1/ns1-go.v2 v2.16.0 gopkg.in/yaml.v2 v2.4.0 - software.sslmate.com/src/go-pkcs12 v0.6.0 + software.sslmate.com/src/go-pkcs12 v0.7.0 ) require ( - cloud.google.com/go/auth v0.17.0 // indirect + cloud.google.com/go/auth v0.18.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect @@ -129,7 +129,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect github.com/aws/smithy-go v1.24.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect @@ -152,7 +152,7 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.23.0 // indirect - github.com/go-resty/resty/v2 v2.17.0 // indirect + github.com/go-resty/resty/v2 v2.17.1 // indirect github.com/goccy/go-yaml v1.9.8 // indirect github.com/gofrs/flock v0.13.0 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect @@ -161,7 +161,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect - github.com/googleapis/gax-go/v2 v2.15.0 // indirect + github.com/googleapis/gax-go/v2 v2.16.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -217,10 +217,10 @@ require ( golang.org/x/sys v0.39.0 // indirect golang.org/x/tools v0.39.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect - google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect + google.golang.org/grpc v1.78.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 993e97c7d..889e6b5b5 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,8 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= -cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= +cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -88,8 +88,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mo github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= -github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= @@ -144,8 +144,8 @@ github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= github.com/alibabacloud-go/tea v1.3.13/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= -github.com/alibabacloud-go/tea v1.3.14 h1:/Uzj5ZCFPpbPR+Bs7jfzsyXkYIVsi5TOIuQNOWwc/9c= -github.com/alibabacloud-go/tea v1.3.14/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= +github.com/alibabacloud-go/tea v1.4.0 h1:MSKhu/kWLPX7mplWMngki8nNt+CyUZ+kfkzaR5VpMhA= +github.com/alibabacloud-go/tea v1.4.0/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= @@ -173,10 +173,10 @@ github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgP github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4= -github.com/aws/aws-sdk-go-v2/config v1.32.5 h1:pz3duhAfUgnxbtVhIK39PGF/AHYyrzGEyRD9Og0QrE8= -github.com/aws/aws-sdk-go-v2/config v1.32.5/go.mod h1:xmDjzSUs/d0BB7ClzYPAZMmgQdrodNjPPhd6bGASwoE= -github.com/aws/aws-sdk-go-v2/credentials v1.19.5 h1:xMo63RlqP3ZZydpJDMBsH9uJ10hgHYfQFIk1cHDXrR4= -github.com/aws/aws-sdk-go-v2/credentials v1.19.5/go.mod h1:hhbH6oRcou+LpXfA/0vPElh/e0M3aFeOblE1sssAAEk= +github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8= +github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE= +github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc= @@ -200,12 +200,12 @@ github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.10 h1:MQuZZ6Tq1qQabPlkVxrCM github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.10/go.mod h1:U5C3JME1ibKESmpzBAqlRpTYZfVbTqrb5ICJm+sVVd8= github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0 h1:80pDB3Tpmb2RCSZORrK9/3iQxsd+w6vSzVqpT1FGiwE= github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0/go.mod h1:6EZUGGNLPLh5Unt30uEoA+KQcByERfXIkax9qrc80nA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0 h1:SWTxh/EcUCDVqi/0s26V6pVUq0BBG7kx0tDTmF/hCgA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 h1:MIWra+MSq53CFaXXAywB2qg9YvVZifkk6vEGl/1Qor0= +github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8= github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ= github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 h1:eYnlt6QxnFINKzwxP5/Ucs1vkG7VT3Iezmvfgc2waUw= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.7/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0= github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70= @@ -215,8 +215,8 @@ github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/aziontech/azionapi-go-sdk v0.144.0 h1:T+/w18o+FCiZsk3Z0ACBVVe7c/5EGLG15S3P8JfuPfo= github.com/aziontech/azionapi-go-sdk v0.144.0/go.mod h1:OKxP/R0iVXnJJakYwMhh2BGAXnud8Ruy55Ak9ANuWoU= -github.com/baidubce/bce-sdk-go v0.9.254 h1:A7GtBOt7z2lnV7fqlZPZefhcBFg7z6iliUAhEOiIhoE= -github.com/baidubce/bce-sdk-go v0.9.254/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/baidubce/bce-sdk-go v0.9.256 h1:/6UwBzDp+dRFpKRIb5WsvxfSiG4SLOIOghvagOK/q4Y= +github.com/baidubce/bce-sdk-go v0.9.256/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -285,8 +285,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/exoscale/egoscale/v3 v3.1.31 h1:/dySEUSAxU+hlAS/eLxAoY8ZYmtOtaoL1P+lDwH7ojY= -github.com/exoscale/egoscale/v3 v3.1.31/go.mod h1:0iY8OxgHJCS5TKqDNhwOW95JBKCnBZl3YGU4Yt+NqkU= +github.com/exoscale/egoscale/v3 v3.1.33 h1:5Lk/pwZ+K0sjNu9obS0VYPfhZQffRkvvO0BpdPoir4o= +github.com/exoscale/egoscale/v3 v3.1.33/go.mod h1:0iY8OxgHJCS5TKqDNhwOW95JBKCnBZl3YGU4Yt+NqkU= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= @@ -315,8 +315,8 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-acme/alidns-20150109/v4 v4.7.0 h1:PqJ/wR0JTpL4v0Owu1uM7bPQ1Yww0eQLAuuSdLjjQaQ= github.com/go-acme/alidns-20150109/v4 v4.7.0/go.mod h1:btQvB6xZoN6ykKB74cPhiR+uvhrEE2AFVXm6RDmCHm0= -github.com/go-acme/esa-20240910/v2 v2.40.3 h1:xXOMRex148wKEHbv7Izn73/HdAxSmz5GOaF4HdnqN+M= -github.com/go-acme/esa-20240910/v2 v2.40.3/go.mod h1:ZYdN9EN9ikn26SNapxCVjZ65pHT/1qm4fzuJ7QGVX6g= +github.com/go-acme/esa-20240910/v2 v2.44.0 h1:ACi2uFb7ig4ousFs/YiFBR+aw3A4SHtOxvkMWB2Hbcs= +github.com/go-acme/esa-20240910/v2 v2.44.0/go.mod h1:ZYdN9EN9ikn26SNapxCVjZ65pHT/1qm4fzuJ7QGVX6g= github.com/go-acme/jdcloud-sdk-go v1.64.0 h1:AW9j5khk8tRYbpBJPxKmqdwIqgLs2Fz3HUK3hn2YXjs= github.com/go-acme/jdcloud-sdk-go v1.64.0/go.mod h1:qc/m8HNX1Zgd7GAv2DSEinup8fwy3Ted3/VVx7LB5bU= github.com/go-acme/tencentclouddnspod v1.1.25 h1:7H3ZKshkaHzCXfRpAHVB5nvxeDDl2XLeNZfrNHiZj/s= @@ -359,8 +359,8 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/go-resty/resty/v2 v2.17.0 h1:pW9DeXcaL4Rrym4EZ8v7L19zZiIlWPg5YXAcVmt+gN0= -github.com/go-resty/resty/v2 v2.17.0/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= +github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4= +github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -444,8 +444,9 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= +github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -470,8 +471,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAV github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= -github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= +github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw= github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= @@ -536,8 +537,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.180 h1:uia+R3K1izQRGpxTV+bS4q3/ueMW7ProAMWqM6OlqOU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.180/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.182 h1:B3W9acgpqu5XsN8v+W8SOTfqn/6n4JsjgoKBsm30HFY= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.182/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -611,8 +612,8 @@ github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufp github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/linode/linodego v1.62.0 h1:eCo1sepsIPGkI66Cz9IaCylWxKDD2aSc5UYq20iBMfw= -github.com/linode/linodego v1.62.0/go.mod h1:FoIEsuZMRlXiUt6RnuGcPTek5iAO3VfE6bjMpGlcQ2U= +github.com/linode/linodego v1.64.0 h1:If6pULIwHuQytgogtpQaBdVLX7z2TTHUF5u1tj2TPiY= +github.com/linode/linodego v1.64.0/go.mod h1:GoiwLVuLdBQcAebxAVKVL3mMYUgJZR/puOUSla04xBE= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= @@ -710,10 +711,10 @@ github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE= github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.1 h1:yHD01L6wN7mhGikS08izrMuEp9PRtvingePXkjRHrSg= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.1/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.1 h1:9ApYlc4bjup9WnxOFmgvh00bDqd6KMqAbAR4klKzluA= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.1/go.mod h1:iOzhDeDcQGJZVgSDKrl5p3HUWexNo3LOlicf0D9ltgk= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2 h1:l0tH15ACQADZAzC+LZ+mo2tIX4H6uZu0ulrVmG5Tqz0= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.2 h1:gzB4c6ztb38C/jYiqEaFC+mCGcWFHDji9e6jwymY9d4= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.2/go.mod h1:l1qIPIq2uRV5WTSvkbhbl/ndbeOu7OCb3UZ+0+2ZSb8= github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= github.com/nrdcg/vegadns v0.3.0 h1:11FQMw7xVIRUWO9o5+Z/5YZhmPWlm4oxUUH3F6EVqQU= @@ -829,8 +830,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35 h1:8xfn1RzeI9yoCUuEwDy08F+No6PcKZGEDOQ6hrRyLts= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35/go.mod h1:47B1d/YXmSAxlJxUJxClzHR6b3T4M1WyCvwENPQNBWc= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 h1:ObX9hZmK+VmijreZO/8x9pQ8/P/ToHD/bdSb4Eg4tUo= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36/go.mod h1:LEsDu4BubxK7/cWhtlQWfuxwL4rf/2UEpxXz1o1EMtM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/selectel/domains-go v1.1.0 h1:futG50J43ALLKQAnZk9H9yOtLGnSUh7c5hSvuC5gSHo= github.com/selectel/domains-go v1.1.0/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA= @@ -905,8 +906,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.25/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.12 h1:/ABtv4x4FSGxGW0d6Sc88iQn6Up2LalWKwt/Tj7Dtz8= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.12/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.28 h1:Rj1WXXNPm9AsPf0PJhWCvlsqfcKPUYdyVnkmEc3O8sI= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.28/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= @@ -921,10 +922,10 @@ github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/vinyldns/go-vinyldns v0.9.17 h1:hfPZfCaxcRBX6Gsgl42rLCeoal58/BH8kkvJShzjjdI= github.com/vinyldns/go-vinyldns v0.9.17/go.mod h1:pwWhE9K/leGDOIduVhRGvQ3ecVMHWRfEnKYUTEU3gB4= -github.com/volcengine/volc-sdk-golang v1.0.230 h1:84/MOF0zUPtAHt3e1+MsFq5qrnQRC+e3XzTUwIOzZxw= -github.com/volcengine/volc-sdk-golang v1.0.230/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= -github.com/vultr/govultr/v3 v3.26.0 h1:pm/GM+RZo9T1JLQzrUti5HiNAIFZFEHcPFMOWGvvNIY= -github.com/vultr/govultr/v3 v3.26.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= +github.com/volcengine/volc-sdk-golang v1.0.233 h1:Hh2pzwu/Wq19rsZgNo3HdpjQB28D/F0+m6EjLVggmhM= +github.com/volcengine/volc-sdk-golang v1.0.233/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= +github.com/vultr/govultr/v3 v3.26.1 h1:G/M0rMQKwVSmL+gb0UgETbW5mcQi0Vf/o/ZSGdBCxJw= +github.com/vultr/govultr/v3 v3.26.1/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= @@ -933,12 +934,12 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yandex-cloud/go-genproto v0.41.0 h1:l0HWC7S82XgfioqOQ+d2wx7PRB5Eo71KiUb4PiWbDXQ= -github.com/yandex-cloud/go-genproto v0.41.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= -github.com/yandex-cloud/go-sdk/services/dns v0.0.23 h1:fR4tqSRKTpzh4RczXJbU7EOXh4+kJnp+dccRpL2PLPQ= -github.com/yandex-cloud/go-sdk/services/dns v0.0.23/go.mod h1:Lgly3dyKBGrAIpIo6nrkEpQOoSQYlnik1HLKMeZcA98= -github.com/yandex-cloud/go-sdk/v2 v2.33.0 h1:wuvpirhYcHSejLDXSxLGsNoZHqkjrXevzVxw7SrrN/0= -github.com/yandex-cloud/go-sdk/v2 v2.33.0/go.mod h1:OqkwauVaBxbrrfN+JOYBIuE8GrHz1g0Z42VIkbsGvPI= +github.com/yandex-cloud/go-genproto v0.43.0 h1:HjBesEmCN8ZOhjjh8gs605vvi9/MBJAW3P20OJ4iQnw= +github.com/yandex-cloud/go-genproto v0.43.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-sdk/services/dns v0.0.25 h1:BcGEuOnwq2X3LS2kvFC6BOdZkOq4Lc7XAYvzap/SJJY= +github.com/yandex-cloud/go-sdk/services/dns v0.0.25/go.mod h1:B4QHijALUHIjRxL3aqmOwDrHYUI2XdeeG4WKItth3jI= +github.com/yandex-cloud/go-sdk/v2 v2.37.0 h1:WvttW6p9xcWag9j+GQv+GJXPggggXGwOlIJNfkWmFWw= +github.com/yandex-cloud/go-sdk/v2 v2.37.0/go.mod h1:Dt4a81enjRsm4xMJyW5E1Y/vaUYwXJvUGRdDLuM2k6I= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= @@ -1380,8 +1381,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.257.0 h1:8Y0lzvHlZps53PEaw+G29SsQIkuKrumGWs9puiexNAA= -google.golang.org/api v0.257.0/go.mod h1:4eJrr+vbVaZSqs7vovFd1Jb/A6ml6iw2e6FBYf3GAO4= +google.golang.org/api v0.259.0 h1:90TaGVIxScrh1Vn/XI2426kRpBqHwWIzVBzJsVZ5XrQ= +google.golang.org/api v0.259.0/go.mod h1:LC2ISWGWbRoyQVpxGntWwLWN/vLNxxKBK9KuJRI8Te4= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1420,12 +1421,12 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= -google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= -google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= -google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= +google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1443,8 +1444,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1459,8 +1460,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1509,5 +1510,5 @@ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -software.sslmate.com/src/go-pkcs12 v0.6.0 h1:f3sQittAeF+pao32Vb+mkli+ZyT+VwKaD014qFGq6oU= -software.sslmate.com/src/go-pkcs12 v0.6.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= +software.sslmate.com/src/go-pkcs12 v0.7.0 h1:Db8W44cB54TWD7stUFFSWxdfpdn6fZVcDl0w3R4RVM0= +software.sslmate.com/src/go-pkcs12 v0.7.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= From eed3f0dcc8ffaa5457c31c92342259dd1e222463 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 8 Jan 2026 17:33:57 +0100 Subject: [PATCH 262/298] chore: update linter (#2785) --- .github/workflows/pr.yml | 2 +- providers/dns/internal/selectel/internal/client.go | 4 ++-- providers/dns/selectelv2/selectelv2.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 151a2a6e0..5df64db7f 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: stable - GOLANGCI_LINT_VERSION: v2.7.1 + GOLANGCI_LINT_VERSION: v2.8.0 HUGO_VERSION: 0.148.2 CGO_ENABLED: 0 LEGO_E2E_TESTS: CI diff --git a/providers/dns/internal/selectel/internal/client.go b/providers/dns/internal/selectel/internal/client.go index b17df6d83..d441c9894 100644 --- a/providers/dns/internal/selectel/internal/client.go +++ b/providers/dns/internal/selectel/internal/client.go @@ -53,8 +53,8 @@ func (c *Client) GetDomainByName(ctx context.Context, domainName string) (*Domai if err != nil { if statusCode == http.StatusNotFound && strings.Count(domainName, ".") > 1 { // Look up for the next subdomain - subIndex := strings.Index(domainName, ".") - return c.GetDomainByName(ctx, domainName[subIndex+1:]) + _, after, _ := strings.Cut(domainName, ".") + return c.GetDomainByName(ctx, after) } return nil, err diff --git a/providers/dns/selectelv2/selectelv2.go b/providers/dns/selectelv2/selectelv2.go index 6e3c1f42c..1fcb48583 100644 --- a/providers/dns/selectelv2/selectelv2.go +++ b/providers/dns/selectelv2/selectelv2.go @@ -297,10 +297,10 @@ func (w *clientWrapper) getZone(ctx context.Context, name string) (*selectelapi. return nil, fmt.Errorf("zone '%s' for challenge has not been found", name) } - // -1 can not be returned since if no dots present we exit above - i := strings.Index(name, ".") + // after is always defined since if no dots present we exit above. + _, after, _ := strings.Cut(name, ".") - return w.getZone(ctx, name[i+1:]) + return w.getZone(ctx, after) } func (w *clientWrapper) getRRset(ctx context.Context, name, zoneID string) (*selectelapi.RRSet, error) { From b77b8709b6802da29a702b44bb0a5279c35eb337 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 8 Jan 2026 17:34:37 +0100 Subject: [PATCH 263/298] namedotcom: follow CNAME (#2390) --- providers/dns/namedotcom/namedotcom.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/providers/dns/namedotcom/namedotcom.go b/providers/dns/namedotcom/namedotcom.go index 3d1f33af1..04c8b5967 100644 --- a/providers/dns/namedotcom/namedotcom.go +++ b/providers/dns/namedotcom/namedotcom.go @@ -116,7 +116,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - // TODO(ldez) replace domain by FQDN to follow CNAME. + if info.EffectiveFQDN != info.FQDN { + domain = dns01.UnFqdn(info.EffectiveFQDN) + } + domainDetails, err := d.client.GetDomain(&namecom.GetDomainRequest{DomainName: domain}) if err != nil { return fmt.Errorf("namedotcom: API call failed: %w", err) @@ -127,7 +130,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("namedotcom: %w", err) } - // TODO(ldez) replace domain by FQDN to follow CNAME. request := &namecom.Record{ DomainName: domain, Host: subDomain, @@ -148,7 +150,10 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - // TODO(ldez) replace domain by FQDN to follow CNAME. + if info.EffectiveFQDN != info.FQDN { + domain = dns01.UnFqdn(info.EffectiveFQDN) + } + records, err := d.getRecords(domain) if err != nil { return fmt.Errorf("namedotcom: %w", err) @@ -156,7 +161,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { for _, rec := range records { if rec.Fqdn == info.EffectiveFQDN && rec.Type == "TXT" { - // TODO(ldez) replace domain by FQDN to follow CNAME. request := &namecom.DeleteRecordRequest{ DomainName: domain, ID: rec.ID, From 9f3dde3f6d2ca2204b823f7f58ba5c2a07bc35e3 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 8 Jan 2026 17:42:25 +0100 Subject: [PATCH 264/298] Prepare release v4.31.0 --- CHANGELOG.md | 24 +++++++++++++++++++ acme/api/internal/sender/useragent.go | 4 ++-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 ++-- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9974d550..ee191cdb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,30 @@ Everybody thinks that the others will donate, but in the end, nobody does. So if you think that lego is worth it, please consider [donating](https://donate.ldez.dev). +## v4.31.0 + +- Release date: 2026-01-08 +- Tag: [v4.31.0](https://github.com/go-acme/lego/releases/tag/v4.31.0) + +### Added + +- **[dnsprovider]** Add DNS provider for ISPConfig +- **[dnsprovider]** Add DNS Provider for ISPConfig (DDNS Module) +- **[dnsprovider]** Add DNS provider for Alwaysdata +- **[dnsprovider]** Add DNS provider for JDCloud +- **[dnsprovider]** Add DNS provider for 35.com/三五互联 +- **[dnsprovider]** f5xc: add an option to configure the domain of the server + +### Changed + +- **[lib]** feat: improve ACME error types +- **[dnsprovider,cname]** namedotcom: follow CNAME + +### Fixed + +- **[dnsprovider]** hetzner: fix compatibility with _FILE suffix +- **[dnsprovider]** gandiv5: fix API Key header + ## v4.30.1 - Release date: 2025-12-16 diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 25443fa05..6b1ebb1c7 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.30.1" + ourUserAgent = "xenolf-acme/4.31.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index da867f0cd..f3fb3d253 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.30.1+dev-detach" +const defaultVersion = "v4.31.0+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 4f8e693c2..480c35af1 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.30.1" + ourUserAgent = "goacme-lego/4.31.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From ac1092710d4c7b5032d894ccaac15498fc9cbabc Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 8 Jan 2026 17:42:41 +0100 Subject: [PATCH 265/298] Detach v4.31.0 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 6b1ebb1c7..570b3b67c 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index f3fb3d253..57899e15e 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.31.0+dev-release" +const defaultVersion = "v4.31.0+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 480c35af1..da24329cc 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From 7f10c131f438d65b418cbb92840af978fbd19c67 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 12 Jan 2026 17:50:21 +0100 Subject: [PATCH 266/298] =?UTF-8?q?Add=20DNS=20provider=20for=20TodayNIC/?= =?UTF-8?q?=E6=97=B6=E4=BB=A3=E4=BA=92=E8=81=94=20(#2788)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +- cmd/zz_gen_cmd_dnshelp.go | 22 ++ docs/content/dns/zz_gen_todaynic.md | 69 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/todaynic/internal/client.go | 141 ++++++++++++ .../dns/todaynic/internal/client_test.go | 94 ++++++++ .../internal/fixtures/add_record.json | 4 + .../dns/todaynic/internal/fixtures/error.json | 4 + providers/dns/todaynic/internal/types.go | 26 +++ providers/dns/todaynic/todaynic.go | 164 ++++++++++++++ providers/dns/todaynic/todaynic.toml | 25 +++ providers/dns/todaynic/todaynic_test.go | 207 ++++++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 13 files changed, 767 insertions(+), 8 deletions(-) create mode 100644 docs/content/dns/zz_gen_todaynic.md create mode 100644 providers/dns/todaynic/internal/client.go create mode 100644 providers/dns/todaynic/internal/client_test.go create mode 100644 providers/dns/todaynic/internal/fixtures/add_record.json create mode 100644 providers/dns/todaynic/internal/fixtures/error.json create mode 100644 providers/dns/todaynic/internal/types.go create mode 100644 providers/dns/todaynic/todaynic.go create mode 100644 providers/dns/todaynic/todaynic.toml create mode 100644 providers/dns/todaynic/todaynic_test.go diff --git a/README.md b/README.md index c02347e23..8ca213962 100644 --- a/README.md +++ b/README.md @@ -256,40 +256,40 @@ If your DNS provider is not supported, please open an [issue](https://github.com Tencent EdgeOne Timeweb Cloud + TodayNIC/时代互联 TransIP UKFast SafeDNS Ultradns - United-Domains + United-Domains Variomedia VegaDNS Vercel - Versio.[nl|eu|uk] + Versio.[nl|eu|uk] VinylDNS Virtualname VK Cloud - Volcano Engine/火山引擎 + Volcano Engine/火山引擎 Vscale Vultr webnames.ca - webnames.ru + webnames.ru Websupport WEDOS West.cn/西部数码 - Yandex 360 + Yandex 360 Yandex Cloud Yandex PDD Zone.ee - ZoneEdit + ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index a918a1484..a2e0d4aaa 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -169,6 +169,7 @@ func allDNSCodes() string { "technitium", "tencentcloud", "timewebcloud", + "todaynic", "transip", "ultradns", "uniteddomains", @@ -3574,6 +3575,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/timewebcloud`) + case "todaynic": + // generated from: providers/dns/todaynic/todaynic.toml + ew.writeln(`Configuration for TodayNIC/时代互联.`) + ew.writeln(`Code: 'todaynic'`) + ew.writeln(`Since: 'v4.32.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "TODAYNIC_API_KEY": API key`) + ew.writeln(` - "TODAYNIC_AUTH_USER_ID": account ID`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "TODAYNIC_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "TODAYNIC_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "TODAYNIC_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "TODAYNIC_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/todaynic`) + case "transip": // generated from: providers/dns/transip/transip.toml ew.writeln(`Configuration for TransIP.`) diff --git a/docs/content/dns/zz_gen_todaynic.md b/docs/content/dns/zz_gen_todaynic.md new file mode 100644 index 000000000..7b06c012d --- /dev/null +++ b/docs/content/dns/zz_gen_todaynic.md @@ -0,0 +1,69 @@ +--- +title: "TodayNIC/时代互联" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: todaynic +dnsprovider: + since: "v4.32.0" + code: "todaynic" + url: "https://www.todaynic.com/" +--- + + + + + + +Configuration for [TodayNIC/时代互联](https://www.todaynic.com/). + + + + +- Code: `todaynic` +- Since: v4.32.0 + + +Here is an example bash command using the TodayNIC/时代互联 provider: + +```bash +TODAYNIC_AUTH_USER_ID="xxx" \ +TODAYNIC_API_KEY="yyy" \ +lego --dns todaynic -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `TODAYNIC_API_KEY` | API key | +| `TODAYNIC_AUTH_USER_ID` | account ID | + +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 | +|--------------------------------|-------------| +| `TODAYNIC_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `TODAYNIC_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `TODAYNIC_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `TODAYNIC_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 600) | + +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://www.todaynic.com/partner/mode_Http_Api_detail.php) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 7d1a4b2c3..8de6475e1 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/todaynic/internal/client.go b/providers/dns/todaynic/internal/client.go new file mode 100644 index 000000000..2c537f4a7 --- /dev/null +++ b/providers/dns/todaynic/internal/client.go @@ -0,0 +1,141 @@ +package internal + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" + querystring "github.com/google/go-querystring/query" +) + +const defaultBaseURL = "https://todapi.now.cn:2443" + +// Client the TodayNIC API client. +type Client struct { + authUserID string + apiKey string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(authUserID, apiKey string) (*Client, error) { + if authUserID == "" || apiKey == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + authUserID: authUserID, + apiKey: apiKey, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) AddRecord(ctx context.Context, record Record) (int, error) { + endpoint := c.BaseURL.JoinPath("api", "dns", "add-domain-record.json") + + query, err := querystring.Values(record) + if err != nil { + return 0, err + } + + req, err := c.newRequest(ctx, endpoint, query) + if err != nil { + return 0, err + } + + var result APIResponse + + err = c.do(req, &result) + if err != nil { + return 0, err + } + + return result.ID, nil +} + +func (c *Client) DeleteRecord(ctx context.Context, recordID int) error { + endpoint := c.BaseURL.JoinPath("api", "dns", "delete-domain-record.json") + + query := endpoint.Query() + query.Set("Id", strconv.Itoa(recordID)) + + req, err := c.newRequest(ctx, endpoint, query) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) do(req *http.Request, result any) error { + useragent.SetHeader(req.Header) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func (c *Client) newRequest(ctx context.Context, endpoint *url.URL, query url.Values) (*http.Request, error) { + query.Set("auth-userid", c.authUserID) + query.Set("api-key", c.apiKey) + + endpoint.RawQuery = query.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/todaynic/internal/client_test.go b/providers/dns/todaynic/internal/client_test.go new file mode 100644 index 000000000..71ee7f8b7 --- /dev/null +++ b/providers/dns/todaynic/internal/client_test.go @@ -0,0 +1,94 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("user123", "secret") + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(), + ) +} + +func TestClient_AddRecord(t *testing.T) { + client := mockBuilder(). + Route("GET /api/dns/add-domain-record.json", + servermock.ResponseFromFixture("add_record.json"), + servermock.CheckQueryParameter().Strict(). + With("Domain", "example.com"). + With("Host", "_acme-challenge"). + With("Type", "TXT"). + With("Value", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"). + With("Ttl", "600"). + With("auth-userid", "user123"). + With("api-key", "secret"), + ). + Build(t) + + record := Record{ + Domain: "example.com", + Host: "_acme-challenge", + Type: "TXT", + Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: "600", + } + + recordID, err := client.AddRecord(t.Context(), record) + require.NoError(t, err) + + assert.Equal(t, 11554102, recordID) +} + +func TestClient_AddRecord_error(t *testing.T) { + client := mockBuilder(). + Route("GET /api/dns/add-domain-record.json", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusNotFound), + ). + Build(t) + + record := Record{ + Domain: "example.com", + Host: "_acme-challenge", + Type: "TXT", + Value: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: "600", + } + + _, err := client.AddRecord(t.Context(), record) + require.EqualError(t, err, "host.repeat (2d5876b2-f272-43e9-acc1-4c6a3d3683b1)") +} + +func TestClient_DeleteRecord(t *testing.T) { + client := mockBuilder(). + Route("GET /api/dns/delete-domain-record.json", + servermock.ResponseFromFixture("add_record.json"), + servermock.CheckQueryParameter().Strict(). + With("Id", "123"). + With("auth-userid", "user123"). + With("api-key", "secret"), + ). + Build(t) + + err := client.DeleteRecord(t.Context(), 123) + require.NoError(t, err) +} diff --git a/providers/dns/todaynic/internal/fixtures/add_record.json b/providers/dns/todaynic/internal/fixtures/add_record.json new file mode 100644 index 000000000..27f34d71c --- /dev/null +++ b/providers/dns/todaynic/internal/fixtures/add_record.json @@ -0,0 +1,4 @@ +{ + "RequestId": "f60ea4d9-67ef-49fa-bbae-06178a6e7293", + "Id": 11554102 +} diff --git a/providers/dns/todaynic/internal/fixtures/error.json b/providers/dns/todaynic/internal/fixtures/error.json new file mode 100644 index 000000000..3ea9c9310 --- /dev/null +++ b/providers/dns/todaynic/internal/fixtures/error.json @@ -0,0 +1,4 @@ +{ + "RequestId": "2d5876b2-f272-43e9-acc1-4c6a3d3683b1", + "error": "host.repeat" +} diff --git a/providers/dns/todaynic/internal/types.go b/providers/dns/todaynic/internal/types.go new file mode 100644 index 000000000..0a15c7da8 --- /dev/null +++ b/providers/dns/todaynic/internal/types.go @@ -0,0 +1,26 @@ +package internal + +import "fmt" + +type APIError struct { + RequestID string `json:"RequestId"` + Message string `json:"error"` +} + +func (a *APIError) Error() string { + return fmt.Sprintf("%s (%s)", a.Message, a.RequestID) +} + +type Record struct { + Domain string `url:"Domain,omitempty"` + Host string `url:"Host,omitempty"` + Type string `url:"Type,omitempty"` + Value string `url:"Value,omitempty"` + Mx string `url:"Mx,omitempty"` + TTL string `url:"Ttl,omitempty"` +} + +type APIResponse struct { + RequestID string `json:"RequestId"` + ID int `json:"Id"` +} diff --git a/providers/dns/todaynic/todaynic.go b/providers/dns/todaynic/todaynic.go new file mode 100644 index 000000000..3a3734033 --- /dev/null +++ b/providers/dns/todaynic/todaynic.go @@ -0,0 +1,164 @@ +// Package todaynic implements a DNS provider for solving the DNS-01 challenge using TodayNIC. +package todaynic + +import ( + "context" + "errors" + "fmt" + "net/http" + "strconv" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/todaynic/internal" +) + +// Environment variables names. +const ( + envNamespace = "TODAYNIC_" + + EnvAuthUserID = envNamespace + "AUTH_USER_ID" + 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 { + AuthUserID string + 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, 600), + 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 *internal.Client + + recordIDs map[string]int + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for TodayNIC. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAuthUserID, EnvAPIKey) + if err != nil { + return nil, fmt.Errorf("todaynic: %w", err) + } + + config := NewDefaultConfig() + config.AuthUserID = values[EnvAuthUserID] + config.APIKey = values[EnvAPIKey] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for TodayNIC. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("todaynic: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.AuthUserID, config.APIKey) + if err != nil { + return nil, fmt.Errorf("todaynic: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + 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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("todaynic: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("todaynic: %w", err) + } + + record := internal.Record{ + Domain: dns01.UnFqdn(authZone), + Host: subDomain, + Type: "TXT", + Value: info.Value, + TTL: strconv.Itoa(d.config.TTL), + } + + recordID, err := d.client.AddRecord(context.Background(), record) + if err != nil { + return fmt.Errorf("todaynic: add record: %w", err) + } + + d.recordIDsMu.Lock() + d.recordIDs[token] = recordID + 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) + + d.recordIDsMu.Lock() + recordID, ok := d.recordIDs[token] + d.recordIDsMu.Unlock() + + if !ok { + return fmt.Errorf("todaynic: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) + } + + err := d.client.DeleteRecord(context.Background(), recordID) + if err != nil { + return fmt.Errorf("todaynic: delete record: %w", err) + } + + 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/todaynic/todaynic.toml b/providers/dns/todaynic/todaynic.toml new file mode 100644 index 000000000..16d55ccc0 --- /dev/null +++ b/providers/dns/todaynic/todaynic.toml @@ -0,0 +1,25 @@ +Name = "TodayNIC/时代互联" +Description = '''''' +URL = "https://www.todaynic.com/" +Code = "todaynic" +Since = "v4.32.0" + +Example = ''' +TODAYNIC_AUTH_USER_ID="xxx" \ +TODAYNIC_API_KEY="yyy" \ +lego --dns todaynic -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + TODAYNIC_AUTH_USER_ID = "account ID" + TODAYNIC_API_KEY = "API key" + [Configuration.Additional] + TODAYNIC_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + TODAYNIC_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + TODAYNIC_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)" + TODAYNIC_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://www.todaynic.com/partner/mode_Http_Api_detail.php" + apipost = "https://docs.apipost.net/docs/detail/49dcef10a876000?target_id=0" diff --git a/providers/dns/todaynic/todaynic_test.go b/providers/dns/todaynic/todaynic_test.go new file mode 100644 index 000000000..c73bf6cc5 --- /dev/null +++ b/providers/dns/todaynic/todaynic_test.go @@ -0,0 +1,207 @@ +package todaynic + +import ( + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvAuthUserID, EnvAPIKey).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvAuthUserID: "user123", + EnvAPIKey: "secret", + }, + }, + { + desc: "missing user ID", + envVars: map[string]string{ + EnvAuthUserID: "", + EnvAPIKey: "secret", + }, + expected: "todaynic: some credentials information are missing: TODAYNIC_AUTH_USER_ID", + }, + { + desc: "missing API key", + envVars: map[string]string{ + EnvAuthUserID: "user123", + EnvAPIKey: "", + }, + expected: "todaynic: some credentials information are missing: TODAYNIC_API_KEY", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "todaynic: some credentials information are missing: TODAYNIC_AUTH_USER_ID,TODAYNIC_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 + authUserID string + apiKey string + expected string + }{ + { + desc: "success", + authUserID: "user123", + apiKey: "secret", + }, + { + desc: "missing user ID", + apiKey: "secret", + expected: "todaynic: credentials missing", + }, + { + desc: "missing API key", + authUserID: "user123", + expected: "todaynic: credentials missing", + }, + { + desc: "missing credentials", + expected: "todaynic: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.AuthUserID = test.authUserID + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.AuthUserID = "user123" + config.APIKey = "secret" + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("GET /api/dns/add-domain-record.json", + servermock.ResponseFromInternal("add_record.json"), + servermock.CheckQueryParameter().Strict(). + With("Domain", "example.com"). + With("Host", "_acme-challenge"). + With("Type", "TXT"). + With("Value", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"). + With("Ttl", "600"). + With("auth-userid", "user123"). + With("api-key", "secret"), + ). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("GET /api/dns/delete-domain-record.json", + servermock.ResponseFromInternal("add_record.json"), + servermock.CheckQueryParameter().Strict(). + With("Id", "123"). + With("auth-userid", "user123"). + With("api-key", "secret"), + ). + Build(t) + + provider.recordIDs["abc"] = 123 + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 7099a3f4c..ddf991209 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -163,6 +163,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/technitium" "github.com/go-acme/lego/v4/providers/dns/tencentcloud" "github.com/go-acme/lego/v4/providers/dns/timewebcloud" + "github.com/go-acme/lego/v4/providers/dns/todaynic" "github.com/go-acme/lego/v4/providers/dns/transip" "github.com/go-acme/lego/v4/providers/dns/ultradns" "github.com/go-acme/lego/v4/providers/dns/uniteddomains" @@ -506,6 +507,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return tencentcloud.NewDNSProvider() case "timewebcloud": return timewebcloud.NewDNSProvider() + case "todaynic": + return todaynic.NewDNSProvider() case "transip": return transip.NewDNSProvider() case "ultradns": From 527d51d4858a8b1dcad8b57e0f7d7d3f5dc7b72d Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 12 Jan 2026 18:04:28 +0100 Subject: [PATCH 267/298] Add DNS provider for DNSExit (#2787) --- README.md | 72 ++++---- cmd/zz_gen_cmd_dnshelp.go | 21 +++ docs/content/dns/zz_gen_dnsexit.md | 67 +++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/dnsexit/dnsexit.go | 163 +++++++++++++++++ providers/dns/dnsexit/dnsexit.toml | 22 +++ providers/dns/dnsexit/dnsexit_test.go | 165 ++++++++++++++++++ providers/dns/dnsexit/internal/client.go | 156 +++++++++++++++++ providers/dns/dnsexit/internal/client_test.go | 111 ++++++++++++ .../internal/fixtures/add_record-request.json | 11 ++ .../fixtures/delete_record-request.json | 10 ++ .../dns/dnsexit/internal/fixtures/error.json | 4 + .../dnsexit/internal/fixtures/success.json | 7 + providers/dns/dnsexit/internal/types.go | 41 +++++ providers/dns/zz_gen_dns_providers.go | 3 + 15 files changed, 818 insertions(+), 37 deletions(-) create mode 100644 docs/content/dns/zz_gen_dnsexit.md create mode 100644 providers/dns/dnsexit/dnsexit.go create mode 100644 providers/dns/dnsexit/dnsexit.toml create mode 100644 providers/dns/dnsexit/dnsexit_test.go create mode 100644 providers/dns/dnsexit/internal/client.go create mode 100644 providers/dns/dnsexit/internal/client_test.go create mode 100644 providers/dns/dnsexit/internal/fixtures/add_record-request.json create mode 100644 providers/dns/dnsexit/internal/fixtures/delete_record-request.json create mode 100644 providers/dns/dnsexit/internal/fixtures/error.json create mode 100644 providers/dns/dnsexit/internal/fixtures/success.json create mode 100644 providers/dns/dnsexit/internal/types.go diff --git a/README.md b/README.md index 8ca213962..eff3bda32 100644 --- a/README.md +++ b/README.md @@ -114,182 +114,182 @@ If your DNS provider is not supported, please open an [issue](https://github.com Digital Ocean DirectAdmin DNS Made Easy - dnsHome.de + DNSExit + dnsHome.de DNSimple DNSPod (deprecated) Domain Offensive (do.de) - Domeneshop + Domeneshop DreamHost Duck DNS Dyn - DynDnsFree.de + DynDnsFree.de Dynu EasyDNS EdgeCenter - Efficient IP + Efficient IP Epik Exoscale External program - F5 XC + F5 XC freemyip.com G-Core Gandi - Gandi Live DNS (v5) + Gandi Live DNS (v5) Gigahost.no Glesys Go Daddy - Google Cloud + Google Cloud Google Domains Gravity Hetzner - Hosting.de + Hosting.de Hosting.nl Hostinger Hosttech - HTTP request + HTTP request http.net Huawei Cloud Hurricane Electric DNS - HyperOne + HyperOne IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox - Infomaniak + Infomaniak Internet Initiative Japan Internet.bs INWX - Ionos + Ionos Ionos Cloud IPv64 ISPConfig 3 - ISPConfig 3 - Dynamic DNS (DDNS) Module + ISPConfig 3 - Dynamic DNS (DDNS) Module iwantmyname (Deprecated) JD Cloud Joker - Joohoi's ACME-DNS + Joohoi's ACME-DNS KeyHelp Liara Lima-City - Linode (v4) + Linode (v4) Liquid Web Loopia LuaDNS - Mail-in-a-Box + Mail-in-a-Box ManageEngine CloudDNS Manual Metaname - Metaregistrar + Metaregistrar mijn.host Mittwald myaddr.{tools,dev,io} - MyDNS.jp + MyDNS.jp MythicBeasts Name.com Namecheap - Namesilo + Namesilo NearlyFreeSpeech.NET Neodigit Netcup - Netlify + Netlify Nicmanager NIFCloud Njalla - Nodion + Nodion NS1 Octenium Open Telekom Cloud - Oracle Cloud + Oracle Cloud OVH plesk.com Porkbun - PowerDNS + PowerDNS Rackspace Rain Yun/雨云 RcodeZero - reg.ru + reg.ru Regfish RFC2136 RimuHosting - RU CENTER + RU CENTER Sakura Cloud Scaleway Selectel - Selectel v2 + Selectel v2 SelfHost.(de|eu) Servercow Shellrent - Simply.com + Simply.com Sonic Spaceship Stackpath - Syse + Syse Technitium Tencent Cloud DNS Tencent EdgeOne - Timeweb Cloud + Timeweb Cloud TodayNIC/时代互联 TransIP UKFast SafeDNS - Ultradns + Ultradns United-Domains Variomedia VegaDNS - Vercel + Vercel Versio.[nl|eu|uk] VinylDNS Virtualname - VK Cloud + VK Cloud Volcano Engine/火山引擎 Vscale Vultr - webnames.ca + webnames.ca webnames.ru Websupport WEDOS - West.cn/西部数码 + West.cn/西部数码 Yandex 360 Yandex Cloud Yandex PDD - Zone.ee + Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index a2e0d4aaa..4c3bcb694 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -52,6 +52,7 @@ func allDNSCodes() string { "designate", "digitalocean", "directadmin", + "dnsexit", "dnshomede", "dnsimple", "dnsmadeeasy", @@ -1087,6 +1088,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/directadmin`) + case "dnsexit": + // generated from: providers/dns/dnsexit/dnsexit.toml + ew.writeln(`Configuration for DNSExit.`) + ew.writeln(`Code: 'dnsexit'`) + ew.writeln(`Since: 'v4.32.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "DNSEXIT_API_KEY": API key`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "DNSEXIT_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "DNSEXIT_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "DNSEXIT_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) + ew.writeln(` - "DNSEXIT_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/dnsexit`) + case "dnshomede": // generated from: providers/dns/dnshomede/dnshomede.toml ew.writeln(`Configuration for dnsHome.de.`) diff --git a/docs/content/dns/zz_gen_dnsexit.md b/docs/content/dns/zz_gen_dnsexit.md new file mode 100644 index 000000000..aca5357e8 --- /dev/null +++ b/docs/content/dns/zz_gen_dnsexit.md @@ -0,0 +1,67 @@ +--- +title: "DNSExit" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: dnsexit +dnsprovider: + since: "v4.32.0" + code: "dnsexit" + url: "https://dnsexit.com" +--- + + + + + + +Configuration for [DNSExit](https://dnsexit.com). + + + + +- Code: `dnsexit` +- Since: v4.32.0 + + +Here is an example bash command using the DNSExit provider: + +```bash +DNSEXIT_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ +lego --dns dnsexit -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `DNSEXIT_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 | +|--------------------------------|-------------| +| `DNSEXIT_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `DNSEXIT_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `DNSEXIT_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | +| `DNSEXIT_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://dnsexit.com/dns/dns-api/) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 8de6475e1..c2f6ea4c1 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/dnsexit/dnsexit.go b/providers/dns/dnsexit/dnsexit.go new file mode 100644 index 000000000..ce9373a50 --- /dev/null +++ b/providers/dns/dnsexit/dnsexit.go @@ -0,0 +1,163 @@ +// Package dnsexit implements a DNS provider for solving the DNS-01 challenge using DNSExit. +package dnsexit + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/dnsexit/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" +) + +// Environment variables names. +const ( + envNamespace = "DNSEXIT_" + + 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, 5*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for DNSExit. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIKey) + if err != nil { + return nil, fmt.Errorf("dnsexit: %w", err) + } + + config := NewDefaultConfig() + config.APIKey = values[EnvAPIKey] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for DNSExit. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("dnsexit: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.APIKey) + if err != nil { + return nil, fmt.Errorf("dnsexit: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("dnsexit: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("dnsexit: %w", err) + } + + record := internal.Record{ + Type: "TXT", + Name: subDomain, + Content: info.Value, + TTL: toMinutes(d.config.TTL), + } + + err = d.client.AddRecord(context.Background(), dns01.UnFqdn(authZone), record) + if err != nil { + return fmt.Errorf("dnsexit: add record: %w", err) + } + + 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) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("dnsexit: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("dnsexit: %w", err) + } + + record := internal.Record{ + Type: "TXT", + Name: subDomain, + Content: info.Value, + } + + err = d.client.DeleteRecord(context.Background(), dns01.UnFqdn(authZone), record) + if err != nil { + return fmt.Errorf("dnsexit: add record: %w", err) + } + + 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 +} + +func toMinutes(seconds int) int { + i := seconds / 60 + if seconds%60 > 0 { + i++ + } + + return i +} diff --git a/providers/dns/dnsexit/dnsexit.toml b/providers/dns/dnsexit/dnsexit.toml new file mode 100644 index 000000000..0d5321835 --- /dev/null +++ b/providers/dns/dnsexit/dnsexit.toml @@ -0,0 +1,22 @@ +Name = "DNSExit" +Description = '''''' +URL = "https://dnsexit.com" +Code = "dnsexit" +Since = "v4.32.0" + +Example = ''' +DNSEXIT_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ +lego --dns dnsexit -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + DNSEXIT_API_KEY = "API key" + [Configuration.Additional] + DNSEXIT_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + DNSEXIT_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" + DNSEXIT_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + DNSEXIT_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://dnsexit.com/dns/dns-api/" diff --git a/providers/dns/dnsexit/dnsexit_test.go b/providers/dns/dnsexit/dnsexit_test.go new file mode 100644 index 000000000..31fe61497 --- /dev/null +++ b/providers/dns/dnsexit/dnsexit_test.go @@ -0,0 +1,165 @@ +package dnsexit + +import ( + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "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: "key", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "dnsexit: some credentials information are missing: DNSEXIT_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: "key", + }, + { + desc: "missing credentials", + expected: "dnsexit: 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.APIKey = "secret" + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With("apikey", "secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("POST /", + servermock.ResponseFromInternal("success.json"), + servermock.CheckRequestJSONBodyFromInternal("add_record-request.json"), + ). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("POST /", + servermock.ResponseFromInternal("success.json"), + servermock.CheckRequestJSONBodyFromInternal("delete_record-request.json"), + ). + Build(t) + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/dnsexit/internal/client.go b/providers/dns/dnsexit/internal/client.go new file mode 100644 index 000000000..9b0164846 --- /dev/null +++ b/providers/dns/dnsexit/internal/client.go @@ -0,0 +1,156 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" +) + +const defaultBaseURL = "https://api.dnsexit.com/dns/" + +// Client the DNSExit API client. +type Client struct { + apiKey string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(apiKey string) (*Client, error) { + if apiKey == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + apiKey: apiKey, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +// AddRecord adds a record. +// https://dnsexit.com/dns/dns-api/#example-add-spf +// https://dnsexit.com/dns/dns-api/#example-lse +func (c *Client) AddRecord(ctx context.Context, domain string, record Record) error { + payload := APIRequest{ + Domain: domain, + Add: []Record{record}, + } + + req, err := newJSONRequest(ctx, http.MethodPost, c.BaseURL, payload) + if err != nil { + return err + } + + err = c.do(req) + if err != nil { + return err + } + + return nil +} + +// DeleteRecord deletes a record. +// https://dnsexit.com/dns/dns-api/#delete-a-record +func (c *Client) DeleteRecord(ctx context.Context, domain string, record Record) error { + payload := APIRequest{ + Domain: domain, + Delete: []Record{record}, + } + + req, err := newJSONRequest(ctx, http.MethodPost, c.BaseURL, payload) + if err != nil { + return err + } + + err = c.do(req) + if err != nil { + return err + } + + return nil +} + +func (c *Client) do(req *http.Request) error { + useragent.SetHeader(req.Header) + + req.Header.Set("apikey", c.apiKey) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode > http.StatusBadRequest { + return parseError(req, resp) + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + result := &APIResponse{} + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + if result.Code != 0 { + return result + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIResponse + + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/dnsexit/internal/client_test.go b/providers/dns/dnsexit/internal/client_test.go new file mode 100644 index 000000000..26ea01203 --- /dev/null +++ b/providers/dns/dnsexit/internal/client_test.go @@ -0,0 +1,111 @@ +package internal + +import ( + "context" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("secret") + if err != nil { + return nil, err + } + + client.HTTPClient = server.Client() + client.BaseURL, _ = url.Parse(server.URL) + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With("apikey", "secret"), + ) +} + +func TestClient_AddRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture("success.json"), + servermock.CheckRequestJSONBodyFromFixture("add_record-request.json"), + ). + Build(t) + + record := Record{ + Type: "TXT", + Name: "_acme-challenge", + Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 2, + } + + err := client.AddRecord(context.Background(), "example.com", record) + require.NoError(t, err) +} + +func TestClient_AddRecord_error(t *testing.T) { + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusBadRequest), + ). + Build(t) + + record := Record{ + Type: "TXT", + Name: "_acme-challenge", + Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 480, + Overwrite: true, + } + + err := client.AddRecord(context.Background(), "example.com", record) + require.Error(t, err) + + require.EqualError(t, err, "JSON Defined Record Type not Supported (code=6)") +} + +func TestClient_DeleteRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture("success.json"), + servermock.CheckRequestJSONBodyFromFixture("delete_record-request.json"), + ). + Build(t) + + record := Record{ + Type: "TXT", + Name: "_acme-challenge", + Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + } + + err := client.DeleteRecord(context.Background(), "example.com", record) + require.NoError(t, err) +} + +func TestClient_DeleteRecord_error(t *testing.T) { + client := mockBuilder(). + Route("POST /", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusBadRequest), + ). + Build(t) + + record := Record{ + Type: "TXT", + Name: "foo", + Content: "txtTXTtxt", + } + + err := client.DeleteRecord(context.Background(), "example.com", record) + + require.Error(t, err) + + require.EqualError(t, err, "JSON Defined Record Type not Supported (code=6)") +} diff --git a/providers/dns/dnsexit/internal/fixtures/add_record-request.json b/providers/dns/dnsexit/internal/fixtures/add_record-request.json new file mode 100644 index 000000000..6e5e2b520 --- /dev/null +++ b/providers/dns/dnsexit/internal/fixtures/add_record-request.json @@ -0,0 +1,11 @@ +{ + "domain": "example.com", + "add": [ + { + "type": "TXT", + "name": "_acme-challenge", + "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "ttl": 2 + } + ] +} diff --git a/providers/dns/dnsexit/internal/fixtures/delete_record-request.json b/providers/dns/dnsexit/internal/fixtures/delete_record-request.json new file mode 100644 index 000000000..dcfef9cdf --- /dev/null +++ b/providers/dns/dnsexit/internal/fixtures/delete_record-request.json @@ -0,0 +1,10 @@ +{ + "domain": "example.com", + "delete": [ + { + "type": "TXT", + "name": "_acme-challenge", + "content": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" + } + ] +} diff --git a/providers/dns/dnsexit/internal/fixtures/error.json b/providers/dns/dnsexit/internal/fixtures/error.json new file mode 100644 index 000000000..9ba835895 --- /dev/null +++ b/providers/dns/dnsexit/internal/fixtures/error.json @@ -0,0 +1,4 @@ +{ + "code": 6, + "message": "JSON Defined Record Type not Supported" +} diff --git a/providers/dns/dnsexit/internal/fixtures/success.json b/providers/dns/dnsexit/internal/fixtures/success.json new file mode 100644 index 000000000..3af47a936 --- /dev/null +++ b/providers/dns/dnsexit/internal/fixtures/success.json @@ -0,0 +1,7 @@ +{ + "code": 0, + "details": [ + "UPDATE Record A example.com. TTL(hh:mm) 08:00 IP 1.1.1.10" + ], + "message": "Success" +} diff --git a/providers/dns/dnsexit/internal/types.go b/providers/dns/dnsexit/internal/types.go new file mode 100644 index 000000000..060dd883e --- /dev/null +++ b/providers/dns/dnsexit/internal/types.go @@ -0,0 +1,41 @@ +package internal + +import ( + "fmt" + "strings" +) + +type Record struct { + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Content string `json:"content,omitempty"` + TTL int `json:"ttl,omitempty"` // NOTE: ttl value is in minutes. + Overwrite bool `json:"overwrite,omitempty"` +} + +type APIRequest struct { + Domain string `json:"domain,omitempty"` + Add []Record `json:"add,omitempty"` + Delete []Record `json:"delete,omitempty"` + Update []Record `json:"update,omitempty"` +} + +// https://dnsexit.com/dns/dns-api/#server-reply + +type APIResponse struct { + Code int `json:"code,omitempty"` + Details []string `json:"details,omitempty"` + Message string `json:"message,omitempty"` +} + +func (a APIResponse) Error() string { + var msg strings.Builder + + msg.WriteString(fmt.Sprintf("%s (code=%d)", a.Message, a.Code)) + + for _, detail := range a.Details { + msg.WriteString(fmt.Sprintf(", %s", detail)) + } + + return msg.String() +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index ddf991209..fbeef67d0 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -46,6 +46,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/designate" "github.com/go-acme/lego/v4/providers/dns/digitalocean" "github.com/go-acme/lego/v4/providers/dns/directadmin" + "github.com/go-acme/lego/v4/providers/dns/dnsexit" "github.com/go-acme/lego/v4/providers/dns/dnshomede" "github.com/go-acme/lego/v4/providers/dns/dnsimple" "github.com/go-acme/lego/v4/providers/dns/dnsmadeeasy" @@ -273,6 +274,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return digitalocean.NewDNSProvider() case "directadmin": return directadmin.NewDNSProvider() + case "dnsexit": + return dnsexit.NewDNSProvider() case "dnshomede": return dnshomede.NewDNSProvider() case "dnsimple": From d063b15c0266d7dd6709597f67f5c41d7764adc3 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 15 Jan 2026 01:04:30 +0100 Subject: [PATCH 268/298] azure: reinforces deprecation (#2792) --- providers/dns/azure/azure.go | 15 +++++++++++++++ providers/dns/azure/azure_test.go | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/providers/dns/azure/azure.go b/providers/dns/azure/azure.go index fd00bcbe2..8bfc6cfe1 100644 --- a/providers/dns/azure/azure.go +++ b/providers/dns/azure/azure.go @@ -8,6 +8,7 @@ import ( "io" "net/http" "net/url" + "strings" "time" "github.com/Azure/go-autorest/autorest" @@ -37,6 +38,8 @@ const ( EnvPollingInterval = envNamespace + "POLLING_INTERVAL" ) +const EnvLegoAzureBypassDeprecation = "LEGO_AZURE_BYPASS_DEPRECATION" + const defaultMetadataEndpoint = "http://169.254.169.254" var _ challenge.ProviderTimeout = (*DNSProvider)(nil) @@ -133,6 +136,18 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { return nil, errors.New("azure: the configuration of the DNS provider is nil") } + if !env.GetOrDefaultBool(EnvLegoAzureBypassDeprecation, false) { + var msg strings.Builder + + msg.WriteString("azure: ") + msg.WriteString("The `azure` provider has been deprecated since 2023, and replaced by `azuredns` provider. ") + msg.WriteString("It can be TEMPORARILY reactivated by using the environment variable `LEGO_AZURE_BYPASS_DEPRECATION=true`. ") + msg.WriteString("The `azure` provider will be removed in a future release, please migrate to the `azuredns` provider. ") + msg.WriteString("The documentation of the `azuredns` provider can be found at https://go-acme.github.io/lego/dns/azuredns/") + + return nil, errors.New(msg.String()) + } + if config.HTTPClient == nil { config.HTTPClient = &http.Client{Timeout: 5 * time.Second} } diff --git a/providers/dns/azure/azure_test.go b/providers/dns/azure/azure_test.go index 44fb81eef..c4fec4359 100644 --- a/providers/dns/azure/azure_test.go +++ b/providers/dns/azure/azure_test.go @@ -14,6 +14,7 @@ import ( const envDomain = envNamespace + "DOMAIN" var envTest = tester.NewEnvTest( + EnvLegoAzureBypassDeprecation, EnvEnvironment, EnvClientID, EnvClientSecret, @@ -57,6 +58,8 @@ func TestNewDNSProvider(t *testing.T) { envTest.ClearEnv() + test.envVars[EnvLegoAzureBypassDeprecation] = "true" + envTest.Apply(test.envVars) p, err := NewDNSProvider() @@ -140,6 +143,11 @@ func TestNewDNSProviderConfig(t *testing.T) { }, } + defer envTest.RestoreEnv() + + envTest.ClearEnv() + envTest.Apply(map[string]string{EnvLegoAzureBypassDeprecation: "true"}) + for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { config := NewDefaultConfig() From 4d41c52db80b5d0517475c2e50da7f3830ccc403 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 15 Jan 2026 23:16:02 +0100 Subject: [PATCH 269/298] Add DNS provider for DDNSS (#2795) --- README.md | 74 ++++---- cmd/zz_gen_cmd_dnshelp.go | 22 +++ docs/content/dns/zz_gen_ddnss.md | 68 +++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/ddnss/ddnss.go | 130 ++++++++++++++ providers/dns/ddnss/ddnss.toml | 23 +++ providers/dns/ddnss/ddnss_test.go | 168 ++++++++++++++++++ providers/dns/ddnss/internal/client.go | 137 ++++++++++++++ providers/dns/ddnss/internal/client_test.go | 56 ++++++ .../dns/ddnss/internal/fixtures/error.html | 12 ++ .../dns/ddnss/internal/fixtures/success.html | 8 + providers/dns/ddnss/internal/types.go | 39 ++++ providers/dns/zz_gen_dns_providers.go | 3 + 13 files changed, 704 insertions(+), 38 deletions(-) create mode 100644 docs/content/dns/zz_gen_ddnss.md create mode 100644 providers/dns/ddnss/ddnss.go create mode 100644 providers/dns/ddnss/ddnss.toml create mode 100644 providers/dns/ddnss/ddnss_test.go create mode 100644 providers/dns/ddnss/internal/client.go create mode 100644 providers/dns/ddnss/internal/client_test.go create mode 100644 providers/dns/ddnss/internal/fixtures/error.html create mode 100644 providers/dns/ddnss/internal/fixtures/success.html create mode 100644 providers/dns/ddnss/internal/types.go diff --git a/README.md b/README.md index eff3bda32..557542ca0 100644 --- a/README.md +++ b/README.md @@ -107,189 +107,189 @@ If your DNS provider is not supported, please open an [issue](https://github.com Core-Networks CPanel/WHM + DDnss (DynDNS Service) Derak Cloud deSEC.io - Designate DNSaaS for Openstack + Designate DNSaaS for Openstack Digital Ocean DirectAdmin DNS Made Easy - DNSExit + DNSExit dnsHome.de DNSimple DNSPod (deprecated) - Domain Offensive (do.de) + Domain Offensive (do.de) Domeneshop DreamHost Duck DNS - Dyn + Dyn DynDnsFree.de Dynu EasyDNS - EdgeCenter + EdgeCenter Efficient IP Epik Exoscale - External program + External program F5 XC freemyip.com G-Core - Gandi + Gandi Gandi Live DNS (v5) Gigahost.no Glesys - Go Daddy + Go Daddy Google Cloud Google Domains Gravity - Hetzner + Hetzner Hosting.de Hosting.nl Hostinger - Hosttech + Hosttech HTTP request http.net Huawei Cloud - Hurricane Electric DNS + Hurricane Electric DNS HyperOne IBM Cloud (SoftLayer) IIJ DNS Platform Service - Infoblox + Infoblox Infomaniak Internet Initiative Japan Internet.bs - INWX + INWX Ionos Ionos Cloud IPv64 - ISPConfig 3 + ISPConfig 3 ISPConfig 3 - Dynamic DNS (DDNS) Module iwantmyname (Deprecated) JD Cloud - Joker + Joker Joohoi's ACME-DNS KeyHelp Liara - Lima-City + Lima-City Linode (v4) Liquid Web Loopia - LuaDNS + LuaDNS Mail-in-a-Box ManageEngine CloudDNS Manual - Metaname + Metaname Metaregistrar mijn.host Mittwald - myaddr.{tools,dev,io} + myaddr.{tools,dev,io} MyDNS.jp MythicBeasts Name.com - Namecheap + Namecheap Namesilo NearlyFreeSpeech.NET Neodigit - Netcup + Netcup Netlify Nicmanager NIFCloud - Njalla + Njalla Nodion NS1 Octenium - Open Telekom Cloud + Open Telekom Cloud Oracle Cloud OVH plesk.com - Porkbun + Porkbun PowerDNS Rackspace Rain Yun/雨云 - RcodeZero + RcodeZero reg.ru Regfish RFC2136 - RimuHosting + RimuHosting RU CENTER Sakura Cloud Scaleway - Selectel + Selectel Selectel v2 SelfHost.(de|eu) Servercow - Shellrent + Shellrent Simply.com Sonic Spaceship - Stackpath + Stackpath Syse Technitium Tencent Cloud DNS - Tencent EdgeOne + Tencent EdgeOne Timeweb Cloud TodayNIC/时代互联 TransIP - UKFast SafeDNS + UKFast SafeDNS Ultradns United-Domains Variomedia - VegaDNS + VegaDNS Vercel Versio.[nl|eu|uk] VinylDNS - Virtualname + Virtualname VK Cloud Volcano Engine/火山引擎 Vscale - Vultr + Vultr webnames.ca webnames.ru Websupport - WEDOS + WEDOS West.cn/西部数码 Yandex 360 Yandex Cloud - Yandex PDD + Yandex PDD Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 4c3bcb694..cf2da8563 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -47,6 +47,7 @@ func allDNSCodes() string { "constellix", "corenetworks", "cpanel", + "ddnss", "derak", "desec", "designate", @@ -973,6 +974,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/cpanel`) + case "ddnss": + // generated from: providers/dns/ddnss/ddnss.toml + ew.writeln(`Configuration for DDnss (DynDNS Service).`) + ew.writeln(`Code: 'ddnss'`) + ew.writeln(`Since: 'v4.32.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "DDNSS_KEY": Update key`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "DDNSS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "DDNSS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "DDNSS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "DDNSS_SEQUENCE_INTERVAL": Time between sequential requests in seconds (Default: 60)`) + ew.writeln(` - "DDNSS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/ddnss`) + case "derak": // generated from: providers/dns/derak/derak.toml ew.writeln(`Configuration for Derak Cloud.`) diff --git a/docs/content/dns/zz_gen_ddnss.md b/docs/content/dns/zz_gen_ddnss.md new file mode 100644 index 000000000..e159d58b4 --- /dev/null +++ b/docs/content/dns/zz_gen_ddnss.md @@ -0,0 +1,68 @@ +--- +title: "DDnss (DynDNS Service)" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: ddnss +dnsprovider: + since: "v4.32.0" + code: "ddnss" + url: "https://ddnss.de/" +--- + + + + + + +Configuration for [DDnss (DynDNS Service)](https://ddnss.de/). + + + + +- Code: `ddnss` +- Since: v4.32.0 + + +Here is an example bash command using the DDnss (DynDNS Service) provider: + +```bash +DDNSS_KEY="xxxxxxxxxxxxxxxxxxxxx" \ +lego --dns ddnss -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `DDNSS_KEY` | Update 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 | +|--------------------------------|-------------| +| `DDNSS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `DDNSS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `DDNSS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `DDNSS_SEQUENCE_INTERVAL` | Time between sequential requests in seconds (Default: 60) | +| `DDNSS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://ddnss.de/info.php) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index c2f6ea4c1..3d3043690 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, ddnss, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/ddnss/ddnss.go b/providers/dns/ddnss/ddnss.go new file mode 100644 index 000000000..381151c55 --- /dev/null +++ b/providers/dns/ddnss/ddnss.go @@ -0,0 +1,130 @@ +// Package ddnss implements a DNS provider for solving the DNS-01 challenge using DynDNS Service. +package ddnss + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/ddnss/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" +) + +// Environment variables names. +const ( + envNamespace = "DDNSS_" + + EnvKey = envNamespace + "KEY" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" + EnvSequenceInterval = envNamespace + "SEQUENCE_INTERVAL" +) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + Key string + + PropagationTimeout time.Duration + PollingInterval time.Duration + SequenceInterval 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), + SequenceInterval: env.GetOrDefaultSecond(EnvSequenceInterval, dns01.DefaultPropagationTimeout), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for DynDNS Service. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvKey) + if err != nil { + return nil, fmt.Errorf("ddnss: %w", err) + } + + config := NewDefaultConfig() + config.Key = values[EnvKey] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for DynDNS Service. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("ddnss: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(&internal.Authentication{Key: config.Key}) + if err != nil { + return nil, fmt.Errorf("ddnss: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + err := d.client.AddTXTRecord(context.Background(), dns01.UnFqdn(info.EffectiveFQDN), info.Value) + if err != nil { + return fmt.Errorf("ddnss: add TXT record: %w", err) + } + + 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) + + err := d.client.RemoveTXTRecord(context.Background(), dns01.UnFqdn(info.EffectiveFQDN)) + if err != nil { + return fmt.Errorf("ddnss: remove TXT record: %w", err) + } + + 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 +} + +// Sequential All DNS challenges for this provider will be resolved sequentially. +// Returns the interval between each iteration. +func (d *DNSProvider) Sequential() time.Duration { + return d.config.SequenceInterval +} diff --git a/providers/dns/ddnss/ddnss.toml b/providers/dns/ddnss/ddnss.toml new file mode 100644 index 000000000..0d0a7132c --- /dev/null +++ b/providers/dns/ddnss/ddnss.toml @@ -0,0 +1,23 @@ +Name = "DDnss (DynDNS Service)" +Description = '''''' +URL = "https://ddnss.de/" +Code = "ddnss" +Since = "v4.32.0" + +Example = ''' +DDNSS_KEY="xxxxxxxxxxxxxxxxxxxxx" \ +lego --dns ddnss -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + DDNSS_KEY = "Update key" + [Configuration.Additional] + DDNSS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + DDNSS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + DDNSS_SEQUENCE_INTERVAL = "Time between sequential requests in seconds (Default: 60)" + DDNSS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + DDNSS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://ddnss.de/info.php" diff --git a/providers/dns/ddnss/ddnss_test.go b/providers/dns/ddnss/ddnss_test.go new file mode 100644 index 000000000..5b1d7df58 --- /dev/null +++ b/providers/dns/ddnss/ddnss_test.go @@ -0,0 +1,168 @@ +package ddnss + +import ( + "net/http/httptest" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvKey).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvKey: "secret", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "ddnss: some credentials information are missing: DDNSS_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 + Key string + expected string + }{ + { + desc: "success", + Key: "secret", + }, + { + desc: "missing credentials", + expected: "ddnss: missing credentials", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Key = test.Key + + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.Key = "secret" + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BaseURL = server.URL + + return p, nil + }, + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("GET /", + servermock.ResponseFromInternal("success.html"), + servermock.CheckQueryParameter().Strict(). + With("host", "_acme-challenge.example.com"). + With("key", "secret"). + With("txt", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"). + With("txtm", "1"), + ). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("GET /", + servermock.ResponseFromInternal("success.html"), + servermock.CheckQueryParameter().Strict(). + With("host", "_acme-challenge.example.com"). + With("key", "secret"). + With("txtm", "2"), + ). + Build(t) + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/ddnss/internal/client.go b/providers/dns/ddnss/internal/client.go new file mode 100644 index 000000000..a0cf4b4a6 --- /dev/null +++ b/providers/dns/ddnss/internal/client.go @@ -0,0 +1,137 @@ +package internal + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" + "golang.org/x/net/html" +) + +const defaultBaseURL = "https://ddnss.de/upd.php" + +// Client the DDns API client. +type Client struct { + auth *Authentication + + BaseURL string + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(auth *Authentication) (*Client, error) { + if auth == nil { + return nil, errors.New("credentials missing") + } + + err := auth.validate() + if err != nil { + return nil, err + } + + return &Client{ + auth: auth, + BaseURL: defaultBaseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) AddTXTRecord(ctx context.Context, host, value string) error { + return c.update(ctx, map[string]string{ + "host": host, + "txt": value, + "txtm": "1", + }) +} + +func (c *Client) RemoveTXTRecord(ctx context.Context, host string) error { + return c.update(ctx, map[string]string{ + "host": host, + "txtm": "2", + }) +} + +func (c *Client) update(ctx context.Context, params map[string]string) error { + endpoint, err := url.Parse(c.BaseURL) + if err != nil { + return err + } + + query := endpoint.Query() + + for k, v := range params { + query.Set(k, v) + } + + c.auth.set(query) + + endpoint.RawQuery = query.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return fmt.Errorf("unable to create request: %w", err) + } + + useragent.SetHeader(req.Header) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + raw, _ := io.ReadAll(resp.Body) + + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + content, err := readPage(raw) + if err != nil { + return err + } + + if strings.Contains(content, "Updated 1 hostname.") { + return nil + } + + return fmt.Errorf("unexpected response: %s", content) +} + +func readPage(raw []byte) (string, error) { + page, err := html.Parse(strings.NewReader(string(raw))) + if err != nil { + return "", err + } + + var b strings.Builder + extractText(page, &b) + + return strings.TrimSpace(b.String()), nil +} + +func extractText(n *html.Node, b *strings.Builder) { + if n.Type == html.TextNode { + text := strings.TrimSpace(n.Data) + if text != "" { + b.WriteString(text + " ") + } + } + + for c := n.FirstChild; c != nil; c = c.NextSibling { + extractText(c, b) + } +} diff --git a/providers/dns/ddnss/internal/client_test.go b/providers/dns/ddnss/internal/client_test.go new file mode 100644 index 000000000..3faddded0 --- /dev/null +++ b/providers/dns/ddnss/internal/client_test.go @@ -0,0 +1,56 @@ +package internal + +import ( + "net/http/httptest" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(&Authentication{Key: "secret"}) + if err != nil { + return nil, err + } + + client.BaseURL = server.URL + client.HTTPClient = server.Client() + + return client, nil + }, + ) +} + +func TestClient_AddTXTRecord(t *testing.T) { + client := mockBuilder(). + Route("GET /", + servermock.ResponseFromFixture("success.html"), + servermock.CheckQueryParameter().Strict(). + With("host", "_acme-challenge.example.com"). + With("key", "secret"). + With("txt", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"). + With("txtm", "1"), + ). + Build(t) + + err := client.AddTXTRecord(t.Context(), "_acme-challenge.example.com", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY") + require.NoError(t, err) +} + +func TestClient_RemoveTXTRecord(t *testing.T) { + client := mockBuilder(). + Route("GET /", + servermock.ResponseFromFixture("success.html"), + servermock.CheckQueryParameter().Strict(). + With("host", "_acme-challenge.example.com"). + With("key", "secret"). + With("txtm", "2"), + ). + Build(t) + + err := client.RemoveTXTRecord(t.Context(), "_acme-challenge.example.com") + require.NoError(t, err) +} diff --git a/providers/dns/ddnss/internal/fixtures/error.html b/providers/dns/ddnss/internal/fixtures/error.html new file mode 100644 index 000000000..f0599ad9a --- /dev/null +++ b/providers/dns/ddnss/internal/fixtures/error.html @@ -0,0 +1,12 @@ + + + DDNSS - Kostenloser DynDNS Service : Re-ProutDNS v5.01v + + +

+

Error Occurred While Processing Request :

+
+ - badysys : Der System Parameter ist ungültig.
+ - badauth : Die Authorisation ist fehlgeschlagen. Die Parameter username und/oder password sind falsch.
+ - notfqdn : Hostname fehlt oder ist falsch.
+ diff --git a/providers/dns/ddnss/internal/fixtures/success.html b/providers/dns/ddnss/internal/fixtures/success.html new file mode 100644 index 000000000..f51957334 --- /dev/null +++ b/providers/dns/ddnss/internal/fixtures/success.html @@ -0,0 +1,8 @@ + + + DDNSS - Kostenloser DynDNS Service : Re-ProutDNS v5.01v + + +

+

Updated 1 hostname.

+ diff --git a/providers/dns/ddnss/internal/types.go b/providers/dns/ddnss/internal/types.go new file mode 100644 index 000000000..37d41e076 --- /dev/null +++ b/providers/dns/ddnss/internal/types.go @@ -0,0 +1,39 @@ +package internal + +import ( + "errors" + "net/url" +) + +type Authentication struct { + Username string `url:"user,omitempty"` + Password string `url:"pwd,omitempty"` + Key string `url:"key,omitempty"` +} + +func (a *Authentication) validate() error { + if a.Username == "" && a.Password == "" && a.Key == "" { + return errors.New("missing credentials") + } + + if a.Username != "" && a.Password != "" && a.Key != "" { + return errors.New("only one of username, password or key can be set") + } + + if (a.Username != "" && a.Password == "") || a.Username == "" && a.Password != "" { + return errors.New("username and password must be set together") + } + + return nil +} + +func (a *Authentication) set(query url.Values) { + if a.Key != "" { + query.Set("key", a.Key) + + return + } + + query.Set("user", a.Username) + query.Set("pwd", a.Password) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index fbeef67d0..b4b98e23f 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -41,6 +41,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/constellix" "github.com/go-acme/lego/v4/providers/dns/corenetworks" "github.com/go-acme/lego/v4/providers/dns/cpanel" + "github.com/go-acme/lego/v4/providers/dns/ddnss" "github.com/go-acme/lego/v4/providers/dns/derak" "github.com/go-acme/lego/v4/providers/dns/desec" "github.com/go-acme/lego/v4/providers/dns/designate" @@ -264,6 +265,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return corenetworks.NewDNSProvider() case "cpanel": return cpanel.NewDNSProvider() + case "ddnss": + return ddnss.NewDNSProvider() case "derak": return derak.NewDNSProvider() case "desec": From 05333f3c84a2b54a3d882bca7ac0b190a78ea052 Mon Sep 17 00:00:00 2001 From: Ameer Ghani Date: Fri, 16 Jan 2026 20:13:42 +0000 Subject: [PATCH 270/298] chore: improve warning message about backups (#2797) --- cmd/cmd_run.go | 6 +++--- docs/content/dns/zz_gen_manual.md | 12 ++++++------ providers/dns/manual/manual.toml | 12 ++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index 16814b4de..5924c4b66 100644 --- a/cmd/cmd_run.go +++ b/cmd/cmd_run.go @@ -104,9 +104,9 @@ Your account credentials have been saved in your configuration directory at "%s". You should make a secure backup of this folder now. This -configuration directory will also contain certificates and -private keys obtained from the ACME server so making regular -backups of this folder is ideal. +configuration directory will also contain private keys +generated by lego and certificates obtained from the ACME +server. Making regular backups of this folder is ideal. ` func run(ctx *cli.Context) error { diff --git a/docs/content/dns/zz_gen_manual.md b/docs/content/dns/zz_gen_manual.md index 056726c74..832ccaf58 100644 --- a/docs/content/dns/zz_gen_manual.md +++ b/docs/content/dns/zz_gen_manual.md @@ -54,13 +54,13 @@ If you accept the linked Terms of Service, hit `Enter`. [INFO] acme: Registering account for you@example.com !!!! HEADS UP !!!! - Your account credentials have been saved in your Let's Encrypt - configuration directory at "./.lego/accounts". +Your account credentials have been saved in your +configuration directory at "./.lego/accounts". - You should make a secure backup of this folder now. This - configuration directory will also contain certificates and - private keys obtained from Let's Encrypt so making regular - backups of this folder is ideal. +You should make a secure backup of this folder now. This +configuration directory will also contain private keys +generated by lego and certificates obtained from the ACME +server. Making regular backups of this folder is ideal. [INFO] [example.com] acme: Obtaining bundled SAN certificate [INFO] [example.com] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz-v3/2345678901 [INFO] [example.com] acme: Could not find solver for: tls-alpn-01 diff --git a/providers/dns/manual/manual.toml b/providers/dns/manual/manual.toml index aca67536d..fc47a8fae 100644 --- a/providers/dns/manual/manual.toml +++ b/providers/dns/manual/manual.toml @@ -31,13 +31,13 @@ If you accept the linked Terms of Service, hit `Enter`. [INFO] acme: Registering account for you@example.com !!!! HEADS UP !!!! - Your account credentials have been saved in your Let's Encrypt - configuration directory at "./.lego/accounts". +Your account credentials have been saved in your +configuration directory at "./.lego/accounts". - You should make a secure backup of this folder now. This - configuration directory will also contain certificates and - private keys obtained from Let's Encrypt so making regular - backups of this folder is ideal. +You should make a secure backup of this folder now. This +configuration directory will also contain private keys +generated by lego and certificates obtained from the ACME +server. Making regular backups of this folder is ideal. [INFO] [example.com] acme: Obtaining bundled SAN certificate [INFO] [example.com] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz-v3/2345678901 [INFO] [example.com] acme: Could not find solver for: tls-alpn-01 From de869c8a7ebce8beb0397b470eda7d04dc89dbe2 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 19 Jan 2026 17:31:56 +0100 Subject: [PATCH 271/298] Add DNS provider for Bluecat v2 (#2791) --- README.md | 85 ++-- cmd/zz_gen_cmd_dnshelp.go | 26 ++ docs/content/dns/zz_gen_bluecatv2.md | 76 ++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/bluecatv2/bluecatv2.go | 249 +++++++++++ providers/dns/bluecatv2/bluecatv2.toml | 33 ++ providers/dns/bluecatv2/bluecatv2_test.go | 414 ++++++++++++++++++ providers/dns/bluecatv2/internal/client.go | 221 ++++++++++ .../dns/bluecatv2/internal/client_test.go | 208 +++++++++ .../fixtures/deleteResourceRecord.json | 75 ++++ .../bluecatv2/internal/fixtures/error.json | 6 + .../internal/fixtures/getZoneDeployments.json | 46 ++ .../fixtures/postSession-request.json | 4 + .../internal/fixtures/postSession.json | 50 +++ .../fixtures/postZoneDeployment-request.json | 3 + .../internal/fixtures/postZoneDeployment.json | 40 ++ .../postZoneResourceRecord-request.json | 7 + .../fixtures/postZoneResourceRecord.json | 25 ++ .../bluecatv2/internal/fixtures/zones.json | 185 ++++++++ providers/dns/bluecatv2/internal/identity.go | 60 +++ .../dns/bluecatv2/internal/identity_test.go | 82 ++++ .../dns/bluecatv2/internal/predicates.go | 64 +++ .../dns/bluecatv2/internal/predicates_test.go | 78 ++++ providers/dns/bluecatv2/internal/types.go | 122 ++++++ providers/dns/zz_gen_dns_providers.go | 3 + 25 files changed, 2123 insertions(+), 41 deletions(-) create mode 100644 docs/content/dns/zz_gen_bluecatv2.md create mode 100644 providers/dns/bluecatv2/bluecatv2.go create mode 100644 providers/dns/bluecatv2/bluecatv2.toml create mode 100644 providers/dns/bluecatv2/bluecatv2_test.go create mode 100644 providers/dns/bluecatv2/internal/client.go create mode 100644 providers/dns/bluecatv2/internal/client_test.go create mode 100644 providers/dns/bluecatv2/internal/fixtures/deleteResourceRecord.json create mode 100644 providers/dns/bluecatv2/internal/fixtures/error.json create mode 100644 providers/dns/bluecatv2/internal/fixtures/getZoneDeployments.json create mode 100644 providers/dns/bluecatv2/internal/fixtures/postSession-request.json create mode 100644 providers/dns/bluecatv2/internal/fixtures/postSession.json create mode 100644 providers/dns/bluecatv2/internal/fixtures/postZoneDeployment-request.json create mode 100644 providers/dns/bluecatv2/internal/fixtures/postZoneDeployment.json create mode 100644 providers/dns/bluecatv2/internal/fixtures/postZoneResourceRecord-request.json create mode 100644 providers/dns/bluecatv2/internal/fixtures/postZoneResourceRecord.json create mode 100644 providers/dns/bluecatv2/internal/fixtures/zones.json create mode 100644 providers/dns/bluecatv2/internal/identity.go create mode 100644 providers/dns/bluecatv2/internal/identity_test.go create mode 100644 providers/dns/bluecatv2/internal/predicates.go create mode 100644 providers/dns/bluecatv2/internal/predicates_test.go create mode 100644 providers/dns/bluecatv2/internal/types.go diff --git a/README.md b/README.md index 557542ca0..6324ece67 100644 --- a/README.md +++ b/README.md @@ -88,208 +88,213 @@ If your DNS provider is not supported, please open an [issue](https://github.com Bindman Bluecat + Bluecat v2 BookMyName - Brandit (deprecated) + Brandit (deprecated) Bunny Checkdomain Civo - Cloud.ru + Cloud.ru CloudDNS Cloudflare ClouDNS - CloudXNS (Deprecated) + CloudXNS (Deprecated) ConoHa v2 ConoHa v3 Constellix - Core-Networks + Core-Networks CPanel/WHM DDnss (DynDNS Service) Derak Cloud - deSEC.io + deSEC.io Designate DNSaaS for Openstack Digital Ocean DirectAdmin - DNS Made Easy + DNS Made Easy DNSExit dnsHome.de DNSimple - DNSPod (deprecated) + DNSPod (deprecated) Domain Offensive (do.de) Domeneshop DreamHost - Duck DNS + Duck DNS Dyn DynDnsFree.de Dynu - EasyDNS + EasyDNS EdgeCenter Efficient IP Epik - Exoscale + Exoscale External program F5 XC freemyip.com - G-Core + G-Core Gandi Gandi Live DNS (v5) Gigahost.no - Glesys + Glesys Go Daddy Google Cloud Google Domains - Gravity + Gravity Hetzner Hosting.de Hosting.nl - Hostinger + Hostinger Hosttech HTTP request http.net - Huawei Cloud + Huawei Cloud Hurricane Electric DNS HyperOne IBM Cloud (SoftLayer) - IIJ DNS Platform Service + IIJ DNS Platform Service Infoblox Infomaniak Internet Initiative Japan - Internet.bs + Internet.bs INWX Ionos Ionos Cloud - IPv64 + IPv64 ISPConfig 3 ISPConfig 3 - Dynamic DNS (DDNS) Module iwantmyname (Deprecated) - JD Cloud + JD Cloud Joker Joohoi's ACME-DNS KeyHelp - Liara + Liara Lima-City Linode (v4) Liquid Web - Loopia + Loopia LuaDNS Mail-in-a-Box ManageEngine CloudDNS - Manual + Manual Metaname Metaregistrar mijn.host - Mittwald + Mittwald myaddr.{tools,dev,io} MyDNS.jp MythicBeasts - Name.com + Name.com Namecheap Namesilo NearlyFreeSpeech.NET - Neodigit + Neodigit Netcup Netlify Nicmanager - NIFCloud + NIFCloud Njalla Nodion NS1 - Octenium + Octenium Open Telekom Cloud Oracle Cloud OVH - plesk.com + plesk.com Porkbun PowerDNS Rackspace - Rain Yun/雨云 + Rain Yun/雨云 RcodeZero reg.ru Regfish - RFC2136 + RFC2136 RimuHosting RU CENTER Sakura Cloud - Scaleway + Scaleway Selectel Selectel v2 SelfHost.(de|eu) - Servercow + Servercow Shellrent Simply.com Sonic - Spaceship + Spaceship Stackpath Syse Technitium - Tencent Cloud DNS + Tencent Cloud DNS Tencent EdgeOne Timeweb Cloud TodayNIC/时代互联 - TransIP + TransIP UKFast SafeDNS Ultradns United-Domains - Variomedia + Variomedia VegaDNS Vercel Versio.[nl|eu|uk] - VinylDNS + VinylDNS Virtualname VK Cloud Volcano Engine/火山引擎 - Vscale + Vscale Vultr webnames.ca webnames.ru - Websupport + Websupport WEDOS West.cn/西部数码 Yandex 360 - Yandex Cloud + Yandex Cloud Yandex PDD Zone.ee ZoneEdit + Zonomi + + + diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index cf2da8563..600e49753 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -31,6 +31,7 @@ func allDNSCodes() string { "binarylane", "bindman", "bluecat", + "bluecatv2", "bookmyname", "brandit", "bunny", @@ -623,6 +624,31 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/bluecat`) + case "bluecatv2": + // generated from: providers/dns/bluecatv2/bluecatv2.toml + ew.writeln(`Configuration for Bluecat v2.`) + ew.writeln(`Code: 'bluecatv2'`) + ew.writeln(`Since: 'v4.32.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "BLUECATV2_CONFIG_NAME": Configuration name`) + ew.writeln(` - "BLUECATV2_PASSWORD": API password`) + ew.writeln(` - "BLUECATV2_USERNAME": API username`) + ew.writeln(` - "BLUECATV2_VIEW_NAME": DNS View Name`) + ew.writeln(` - "BLUECAT_SERVER_URL": The server URL: it should have a scheme, hostname, and port (if required) of the authoritative Bluecat BAM serve`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "BLUECATV2_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "BLUECATV2_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "BLUECATV2_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "BLUECATV2_SKIP_DEPLOY": Skip quick deployements`) + ew.writeln(` - "BLUECATV2_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/bluecatv2`) + case "bookmyname": // generated from: providers/dns/bookmyname/bookmyname.toml ew.writeln(`Configuration for BookMyName.`) diff --git a/docs/content/dns/zz_gen_bluecatv2.md b/docs/content/dns/zz_gen_bluecatv2.md new file mode 100644 index 000000000..7d748df99 --- /dev/null +++ b/docs/content/dns/zz_gen_bluecatv2.md @@ -0,0 +1,76 @@ +--- +title: "Bluecat v2" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: bluecatv2 +dnsprovider: + since: "v4.32.0" + code: "bluecatv2" + url: "https://www.bluecatnetworks.com" +--- + + + + + + +Configuration for [Bluecat v2](https://www.bluecatnetworks.com). + + + + +- Code: `bluecatv2` +- Since: v4.32.0 + + +Here is an example bash command using the Bluecat v2 provider: + +```bash +BLUECATV2_SERVER_URL="https://example.com" \ +BLUECATV2_USERNAME="xxx" \ +BLUECATV2_PASSWORD="yyy" \ +BLUECATV2_CONFIG_NAME="myConfiguration" \ +BLUECATV2_VIEW_NAME="myView" \ +lego --dns bluecatv2 -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `BLUECATV2_CONFIG_NAME` | Configuration name | +| `BLUECATV2_PASSWORD` | API password | +| `BLUECATV2_USERNAME` | API username | +| `BLUECATV2_VIEW_NAME` | DNS View Name | +| `BLUECAT_SERVER_URL` | The server URL: it should have a scheme, hostname, and port (if required) of the authoritative Bluecat BAM serve | + +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 | +|--------------------------------|-------------| +| `BLUECATV2_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `BLUECATV2_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `BLUECATV2_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `BLUECATV2_SKIP_DEPLOY` | Skip quick deployements | +| `BLUECATV2_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://docs.bluecatnetworks.com/r/Address-Manager-RESTful-v2-API-Guide/Introduction/9.6.0) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 3d3043690..e31633567 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, ddnss, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bluecatv2, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, ddnss, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/bluecatv2/bluecatv2.go b/providers/dns/bluecatv2/bluecatv2.go new file mode 100644 index 000000000..0efe99661 --- /dev/null +++ b/providers/dns/bluecatv2/bluecatv2.go @@ -0,0 +1,249 @@ +// Package bluecatv2 implements a DNS provider for solving the DNS-01 challenge using Bluecat v2. +package bluecatv2 + +import ( + "context" + "errors" + "fmt" + "net/http" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/bluecatv2/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" +) + +// Environment variables names. +const ( + envNamespace = "BLUECATV2_" + + EnvServerURL = envNamespace + "SERVER_URL" + EnvUsername = envNamespace + "USERNAME" + EnvPassword = envNamespace + "PASSWORD" + EnvConfigName = envNamespace + "CONFIG_NAME" + EnvViewName = envNamespace + "VIEW_NAME" + EnvSkipDeploy = envNamespace + "SKIP_DEPLOY" + + 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 { + ServerURL string + Username string + Password string + ConfigName string + ViewName string + SkipDeploy bool + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + SkipDeploy: env.GetOrDefaultBool(EnvSkipDeploy, false), + + 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 *internal.Client + + zoneIDs map[string]int64 + recordIDs map[string]int64 + recordIDsMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for Bluecat v2. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvServerURL, EnvUsername, EnvPassword, EnvConfigName, EnvViewName) + if err != nil { + return nil, fmt.Errorf("bluecatv2: %w", err) + } + + config := NewDefaultConfig() + config.ServerURL = values[EnvServerURL] + config.Username = values[EnvUsername] + config.Password = values[EnvPassword] + config.ConfigName = values[EnvConfigName] + config.ViewName = values[EnvViewName] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Bluecat v2. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("bluecatv2: the configuration of the DNS provider is nil") + } + + if config.ServerURL == "" { + return nil, errors.New("bluecatv2: missing server URL") + } + + if config.ConfigName == "" { + return nil, errors.New("bluecatv2: missing configuration name") + } + + if config.ViewName == "" { + return nil, errors.New("bluecatv2: missing view name") + } + + client, err := internal.NewClient(config.ServerURL, config.Username, config.Password) + if err != nil { + return nil, fmt.Errorf("bluecatv2: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + recordIDs: make(map[string]int64), + zoneIDs: make(map[string]int64), + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + ctx, err := d.client.CreateAuthenticatedContext(context.Background()) + if err != nil { + return fmt.Errorf("bluecatv2: %w", err) + } + + zone, err := d.findZone(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("bluecatv2: %w", err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone.AbsoluteName) + if err != nil { + return fmt.Errorf("bluecatv2: %w", err) + } + + record := internal.RecordTXT{ + CommonResource: internal.CommonResource{ + Type: "TXTRecord", + Name: subDomain, + }, + Text: info.Value, + TTL: d.config.TTL, + RecordType: "TXT", + } + + newRecord, err := d.client.CreateZoneResourceRecord(ctx, zone.ID, record) + if err != nil { + return fmt.Errorf("bluecatv2: create resource record: %w", err) + } + + d.recordIDsMu.Lock() + d.zoneIDs[token] = zone.ID + d.recordIDs[token] = newRecord.ID + d.recordIDsMu.Unlock() + + if d.config.SkipDeploy { + return nil + } + + _, err = d.client.CreateZoneDeployment(ctx, zone.ID) + if err != nil { + return fmt.Errorf("bluecat: deploy zone: %w", err) + } + + 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) + + d.recordIDsMu.Lock() + recordID, recordOK := d.recordIDs[token] + zoneID, zoneOK := d.zoneIDs[token] + d.recordIDsMu.Unlock() + + if !recordOK { + return fmt.Errorf("bluecatv2: unknown record ID for '%s' '%s'", info.EffectiveFQDN, token) + } + + if !zoneOK { + return fmt.Errorf("bluecatv2: unknown zone ID for '%s' '%s'", info.EffectiveFQDN, token) + } + + ctx, err := d.client.CreateAuthenticatedContext(context.Background()) + if err != nil { + return fmt.Errorf("bluecatv2: %w", err) + } + + err = d.client.DeleteResourceRecord(ctx, recordID) + if err != nil { + return fmt.Errorf("bluecatv2: delete resource record: %w", err) + } + + if d.config.SkipDeploy { + return nil + } + + _, err = d.client.CreateZoneDeployment(ctx, zoneID) + if err != nil { + return fmt.Errorf("bluecat: deploy zone: %w", err) + } + + 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 +} + +func (d *DNSProvider) findZone(ctx context.Context, fqdn string) (*internal.ZoneResource, error) { + for name := range dns01.UnFqdnDomainsSeq(fqdn) { + opts := &internal.CollectionOptions{ + Fields: "id,absoluteName,configuration.id,configuration.name,view.id,view.name", + Filter: internal.And( + internal.Eq("absoluteName", name), + internal.Eq("configuration.name", d.config.ConfigName), + internal.Eq("view.name", d.config.ViewName), + ).String(), + } + + zones, err := d.client.RetrieveZones(ctx, opts) + if err != nil { + // TODO(ldez) maybe add a log in v5. + continue + } + + for _, zone := range zones { + if zone.AbsoluteName == name { + return &zone, nil + } + } + } + + return nil, fmt.Errorf("no zone found for fqdn: %s", fqdn) +} diff --git a/providers/dns/bluecatv2/bluecatv2.toml b/providers/dns/bluecatv2/bluecatv2.toml new file mode 100644 index 000000000..6ec3781c6 --- /dev/null +++ b/providers/dns/bluecatv2/bluecatv2.toml @@ -0,0 +1,33 @@ +Name = "Bluecat v2" +Description = '''''' +URL = "https://www.bluecatnetworks.com" +Code = "bluecatv2" +Since = "v4.32.0" + +Example = ''' +BLUECATV2_SERVER_URL="https://example.com" \ +BLUECATV2_USERNAME="xxx" \ +BLUECATV2_PASSWORD="yyy" \ +BLUECATV2_CONFIG_NAME="myConfiguration" \ +BLUECATV2_VIEW_NAME="myView" \ +lego --dns bluecatv2 -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + BLUECAT_SERVER_URL = "The server URL: it should have a scheme, hostname, and port (if required) of the authoritative Bluecat BAM serve" + BLUECATV2_USERNAME = "API username" + BLUECATV2_PASSWORD = "API password" + BLUECATV2_CONFIG_NAME = "Configuration name" + BLUECATV2_VIEW_NAME = "DNS View Name" + [Configuration.Additional] + BLUECATV2_SKIP_DEPLOY = "Skip quick deployements" + BLUECATV2_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + BLUECATV2_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + BLUECATV2_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + BLUECATV2_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://docs.bluecatnetworks.com/r/Address-Manager-RESTful-v2-API-Guide/Introduction/9.6.0" + Swagger = "http://{Address_Manager_IP}/api/openapi.json" + SwaggerDump = "https://github.com/go-acme/lego/discussions/2218#discussioncomment-13060545" diff --git a/providers/dns/bluecatv2/bluecatv2_test.go b/providers/dns/bluecatv2/bluecatv2_test.go new file mode 100644 index 000000000..d852f0e18 --- /dev/null +++ b/providers/dns/bluecatv2/bluecatv2_test.go @@ -0,0 +1,414 @@ +package bluecatv2 + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/go-acme/lego/v4/providers/dns/bluecatv2/internal" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest( + EnvServerURL, + EnvUsername, + EnvPassword, + EnvConfigName, + EnvViewName, + EnvSkipDeploy, +).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvServerURL: "https://example.com/", + EnvUsername: "userA", + EnvPassword: "secret", + EnvConfigName: "myConfig", + EnvViewName: "myView", + }, + }, + { + desc: "missing server URL", + envVars: map[string]string{ + EnvServerURL: "", + EnvUsername: "userA", + EnvPassword: "secret", + EnvConfigName: "myConfig", + EnvViewName: "myView", + }, + expected: "bluecatv2: some credentials information are missing: BLUECATV2_SERVER_URL", + }, + { + desc: "missing username", + envVars: map[string]string{ + EnvServerURL: "https://example.com/", + EnvUsername: "", + EnvPassword: "secret", + EnvConfigName: "myConfig", + EnvViewName: "myView", + }, + expected: "bluecatv2: some credentials information are missing: BLUECATV2_USERNAME", + }, + { + desc: "missing password", + envVars: map[string]string{ + EnvServerURL: "https://example.com/", + EnvUsername: "userA", + EnvPassword: "", + EnvConfigName: "myConfig", + EnvViewName: "myView", + }, + expected: "bluecatv2: some credentials information are missing: BLUECATV2_PASSWORD", + }, + { + desc: "missing configuration name", + envVars: map[string]string{ + EnvServerURL: "https://example.com/", + EnvUsername: "userA", + EnvPassword: "secret", + EnvConfigName: "", + EnvViewName: "myView", + }, + expected: "bluecatv2: some credentials information are missing: BLUECATV2_CONFIG_NAME", + }, + { + desc: "missing view name", + envVars: map[string]string{ + EnvServerURL: "https://example.com/", + EnvUsername: "userA", + EnvPassword: "secret", + EnvConfigName: "myConfig", + EnvViewName: "", + }, + expected: "bluecatv2: some credentials information are missing: BLUECATV2_VIEW_NAME", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "bluecatv2: some credentials information are missing: BLUECATV2_SERVER_URL,BLUECATV2_USERNAME,BLUECATV2_PASSWORD,BLUECATV2_CONFIG_NAME,BLUECATV2_VIEW_NAME", + }, + } + + 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 + serverURL string + username string + password string + configName string + viewName string + expected string + }{ + { + desc: "success", + serverURL: "https://example.com/", + username: "userA", + password: "secret", + configName: "myConfig", + viewName: "myView", + }, + { + desc: "missing server URL", + username: "userA", + password: "secret", + configName: "myConfig", + viewName: "myView", + expected: "bluecatv2: missing server URL", + }, + { + desc: "missing username", + serverURL: "https://example.com/", + password: "secret", + configName: "myConfig", + viewName: "myView", + expected: "bluecatv2: credentials missing", + }, + { + desc: "missing password", + serverURL: "https://example.com/", + username: "userA", + configName: "myConfig", + viewName: "myView", + expected: "bluecatv2: credentials missing", + }, + { + desc: "missing configuration name", + serverURL: "https://example.com/", + username: "userA", + password: "secret", + viewName: "myView", + expected: "bluecatv2: missing configuration name", + }, + { + desc: "missing view name", + serverURL: "https://example.com/", + username: "userA", + password: "secret", + configName: "myConfig", + expected: "bluecatv2: missing view name", + }, + { + desc: "missing credentials", + expected: "bluecatv2: missing server URL", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.ServerURL = test.serverURL + config.Username = test.username + config.Password = test.password + config.ConfigName = test.configName + config.ViewName = test.viewName + + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + + config.ServerURL = server.URL + config.Username = "userA" + config.Password = "secret" + config.ConfigName = "myConfiguration" + config.ViewName = "myView" + + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + return p, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("POST /api/v2/sessions", + servermock.ResponseFromInternal("postSession.json"), + servermock.CheckRequestJSONBodyFromInternal("postSession-request.json"), + ). + Route("GET /api/v2/configurations", + servermock.ResponseFromInternal("configurations.json"), + servermock.CheckQueryParameter().Strict(). + With("filter", "name:eq('myConfiguration')"), + ). + Route("GET /api/v2/configurations/12345/views", + servermock.ResponseFromInternal("views.json"), + servermock.CheckQueryParameter().Strict(). + With("filter", "name:eq('myView')"), + ). + Route("GET /api/v2/zones", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + filter := req.URL.Query().Get("filter") + + if strings.Contains(filter, internal.Eq("absoluteName", "example.com").String()) { + servermock.ResponseFromInternal("zones.json").ServeHTTP(rw, req) + + return + } + + servermock.ResponseFromInternal("error.json"). + WithStatusCode(http.StatusNotFound).ServeHTTP(rw, req) + }), + ). + Route("POST /api/v2/zones/12345/resourceRecords", + servermock.ResponseFromInternal("postZoneResourceRecord.json"), + servermock.CheckRequestJSONBodyFromInternal("postZoneResourceRecord-request.json"), + ). + Route("POST /api/v2/zones/12345/deployments", + servermock.ResponseFromInternal("postZoneDeployment.json"). + WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBodyFromInternal("postZoneDeployment-request.json"), + ). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_Present_skipDeploy(t *testing.T) { + defer envTest.RestoreEnv() + + envTest.ClearEnv() + + envTest.Apply(map[string]string{ + EnvSkipDeploy: "true", + }) + + provider := mockBuilder(). + Route("POST /api/v2/sessions", + servermock.ResponseFromInternal("postSession.json"), + servermock.CheckRequestJSONBodyFromInternal("postSession-request.json"), + ). + Route("GET /api/v2/configurations", + servermock.ResponseFromInternal("configurations.json"), + servermock.CheckQueryParameter().Strict(). + With("filter", "name:eq('myConfiguration')"), + ). + Route("GET /api/v2/configurations/12345/views", + servermock.ResponseFromInternal("views.json"), + servermock.CheckQueryParameter().Strict(). + With("filter", "name:eq('myView')"), + ). + Route("GET /api/v2/zones", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + filter := req.URL.Query().Get("filter") + + if strings.Contains(filter, internal.Eq("absoluteName", "example.com").String()) { + servermock.ResponseFromInternal("zones.json").ServeHTTP(rw, req) + + return + } + + servermock.ResponseFromInternal("error.json"). + WithStatusCode(http.StatusNotFound).ServeHTTP(rw, req) + }), + ). + Route("POST /api/v2/zones/12345/resourceRecords", + servermock.ResponseFromInternal("postZoneResourceRecord.json"), + servermock.CheckRequestJSONBodyFromInternal("postZoneResourceRecord-request.json"), + ). + Route("POST /api/v2/zones/456789/deployments", + servermock.Noop(). + WithStatusCode(http.StatusUnauthorized), + ). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("POST /api/v2/sessions", + servermock.ResponseFromInternal("postSession.json"), + servermock.CheckRequestJSONBodyFromInternal("postSession-request.json"), + ). + Route("DELETE /api/v2/resourceRecords/12345", + servermock.ResponseFromInternal("deleteResourceRecord.json"), + ). + Route("POST /api/v2/zones/456789/deployments", + servermock.ResponseFromInternal("postZoneDeployment.json"). + WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBodyFromInternal("postZoneDeployment-request.json"), + ). + Build(t) + + provider.zoneIDs["abc"] = 456789 + provider.recordIDs["abc"] = 12345 + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp_skipDeploy(t *testing.T) { + defer envTest.RestoreEnv() + + envTest.ClearEnv() + + envTest.Apply(map[string]string{ + EnvSkipDeploy: "true", + }) + + provider := mockBuilder(). + Route("POST /api/v2/sessions", + servermock.ResponseFromInternal("postSession.json"), + servermock.CheckRequestJSONBodyFromInternal("postSession-request.json"), + ). + Route("DELETE /api/v2/resourceRecords/12345", + servermock.ResponseFromInternal("deleteResourceRecord.json"), + ). + Route("POST /api/v2/zones/456789/deployments", + servermock.Noop(). + WithStatusCode(http.StatusUnauthorized), + ). + Build(t) + + provider.zoneIDs["abc"] = 456789 + provider.recordIDs["abc"] = 12345 + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/bluecatv2/internal/client.go b/providers/dns/bluecatv2/internal/client.go new file mode 100644 index 000000000..d3c801154 --- /dev/null +++ b/providers/dns/bluecatv2/internal/client.go @@ -0,0 +1,221 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" + querystring "github.com/google/go-querystring/query" +) + +// Client the Bluecat v2 API client. +type Client struct { + username string + password string + + baseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(serverURL, username, password string) (*Client, error) { + if serverURL == "" { + return nil, errors.New("server URL missing") + } + + if username == "" || password == "" { + return nil, errors.New("credentials missing") + } + + baseURL, err := url.Parse(serverURL) + if err != nil { + return nil, err + } + + return &Client{ + username: username, + password: password, + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +// RetrieveZones retrieves all zones. +func (c *Client) RetrieveZones(ctx context.Context, opts *CollectionOptions) ([]ZoneResource, error) { + endpoint := c.baseURL.JoinPath("api", "v2", "zones") + + collection, err := retrieveCollection[ZoneResource](ctx, c, endpoint, opts) + if err != nil { + return nil, err + } + + return collection.Data, nil +} + +// RetrieveZoneDeployments retrieves all deployments for a zone. +func (c *Client) RetrieveZoneDeployments(ctx context.Context, zoneID int64, opts *CollectionOptions) ([]QuickDeployment, error) { + endpoint := c.baseURL.JoinPath("api", "v2", "zones", strconv.FormatInt(zoneID, 10), "deployments") + + collection, err := retrieveCollection[QuickDeployment](ctx, c, endpoint, opts) + if err != nil { + return nil, err + } + + return collection.Data, nil +} + +// CreateZoneDeployment creates a new deployment for a zone. +func (c *Client) CreateZoneDeployment(ctx context.Context, zoneID int64) (*QuickDeployment, error) { + endpoint := c.baseURL.JoinPath("api", "v2", "zones", strconv.FormatInt(zoneID, 10), "deployments") + + payload := CommonResource{ + Type: "QuickDeployment", + } + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, payload) + if err != nil { + return nil, err + } + + result := new(QuickDeployment) + + err = c.doAuthenticated(ctx, req, result) + if err != nil { + return nil, err + } + + return result, nil +} + +// CreateZoneResourceRecord creates a new TXT record in a zone. +func (c *Client) CreateZoneResourceRecord(ctx context.Context, zoneID int64, record RecordTXT) (*RecordTXT, error) { + endpoint := c.baseURL.JoinPath("api", "v2", "zones", strconv.FormatInt(zoneID, 10), "resourceRecords") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) + if err != nil { + return nil, err + } + + result := new(RecordTXT) + + err = c.doAuthenticated(ctx, req, result) + if err != nil { + return nil, err + } + + return result, nil +} + +// DeleteResourceRecord deletes a resource record. +func (c *Client) DeleteResourceRecord(ctx context.Context, recordID int64) error { + endpoint := c.baseURL.JoinPath("api", "v2", "resourceRecords", strconv.FormatInt(recordID, 10)) + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + return c.doAuthenticated(ctx, req, nil) +} + +func (c *Client) do(req *http.Request, result any) error { + useragent.SetHeader(req.Header) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func retrieveCollection[T any](ctx context.Context, client *Client, endpoint *url.URL, opts *CollectionOptions) (*Collection[T], error) { + if opts != nil { + values, err := querystring.Values(opts) + if err != nil { + return nil, err + } + + endpoint.RawQuery = values.Encode() + } + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + result := &Collection[T]{} + + err = client.doAuthenticated(ctx, req, result) + if err != nil { + return nil, err + } + + return result, nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return &errAPI +} diff --git a/providers/dns/bluecatv2/internal/client_test.go b/providers/dns/bluecatv2/internal/client_test.go new file mode 100644 index 000000000..2559af66e --- /dev/null +++ b/providers/dns/bluecatv2/internal/client_test.go @@ -0,0 +1,208 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + "time" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilderAuthenticated() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(server.URL, "userA", "secret") + if err != nil { + return nil, err + } + + client.baseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(), + servermock.CheckHeader(). + WithAuthorization("Basic secretToken"), + ) +} + +func TestClient_RetrieveZones(t *testing.T) { + client := mockBuilderAuthenticated(). + Route("GET /api/v2/zones", + servermock.ResponseFromFixture("zones.json"), + servermock.CheckQueryParameter().Strict(). + With( + "filter", + "absoluteName:eq('example.com') and configuration.name:eq('myConfiguration') and view.name:eq('myView')", + ), + ). + Build(t) + + opts := &CollectionOptions{ + Filter: And( + Eq("absoluteName", "example.com"), + Eq("configuration.name", "myConfiguration"), + Eq("view.name", "myView"), + ).String(), + } + + result, err := client.RetrieveZones(mockToken(t.Context()), opts) + require.NoError(t, err) + + expected := []ZoneResource{ + { + CommonResource: CommonResource{ID: 12345, Type: "ENUMZone", Name: "5678"}, + AbsoluteName: "string", + }, + { + CommonResource: CommonResource{ID: 12345, Type: "ExternalHostsZone", Name: "name"}, + }, + { + CommonResource: CommonResource{ID: 12345, Type: "InternalRootZone", Name: "name"}, + }, + { + CommonResource: CommonResource{ID: 12345, Type: "ResponsePolicyZone", Name: "name"}, + }, + { + CommonResource: CommonResource{ID: 12345, Type: "Zone", Name: "example.com"}, + AbsoluteName: "example.com", + }, + } + + assert.Equal(t, expected, result) +} + +func TestClient_RetrieveZones_error(t *testing.T) { + client := mockBuilderAuthenticated(). + Route("GET /api/v2/zones", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized), + ). + Build(t) + + opts := &CollectionOptions{ + Filter: And( + Eq("absoluteName", "example.com"), + Eq("configuration.name", "myConfiguration"), + Eq("view.name", "myView"), + ).String(), + } + + _, err := client.RetrieveZones(mockToken(t.Context()), opts) + require.EqualError(t, err, "401: Unauthorized: InvalidAuthorizationToken: The provided authorization token is invalid") +} + +func TestClient_RetrieveZoneDeployments(t *testing.T) { + client := mockBuilderAuthenticated(). + Route("GET /api/v2/zones/456789/deployments", + servermock.ResponseFromFixture("getZoneDeployments.json"), + servermock.CheckQueryParameter().Strict(). + With("filter", "id:eq('12345')"), + ). + Build(t) + + opts := &CollectionOptions{ + Filter: Eq("id", "12345").String(), + } + + result, err := client.RetrieveZoneDeployments(mockToken(t.Context()), 456789, opts) + require.NoError(t, err) + + expected := []QuickDeployment{ + { + CommonResource: CommonResource{ID: 12345, Type: "QuickDeployment", Name: ""}, + State: "PENDING", + Status: "CANCEL", + Message: "string", + PercentComplete: 50, + CreationDateTime: time.Date(2022, time.November, 23, 2, 53, 0, 0, time.UTC), + StartDateTime: time.Date(2022, time.November, 23, 2, 53, 3, 0, time.UTC), + CompletionDateTime: time.Date(2022, time.November, 23, 2, 54, 5, 0, time.UTC), + Method: "SCHEDULED", + }, + } + + assert.Equal(t, expected, result) +} + +func TestClient_CreateZoneDeployment(t *testing.T) { + client := mockBuilderAuthenticated(). + Route("POST /api/v2/zones/12345/deployments", + servermock.ResponseFromFixture("postZoneDeployment.json"). + WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBodyFromFixture("postZoneDeployment-request.json"), + ). + Build(t) + + quickDeployment, err := client.CreateZoneDeployment(mockToken(t.Context()), 12345) + require.NoError(t, err) + + expected := &QuickDeployment{ + CommonResource: CommonResource{ID: 12345, Type: "QuickDeployment"}, + State: "PENDING", + Status: "CANCEL", + Message: "string", + PercentComplete: 50, + CreationDateTime: time.Date(2022, time.November, 23, 2, 53, 0, 0, time.UTC), + StartDateTime: time.Date(2022, time.November, 23, 2, 53, 3, 0, time.UTC), + CompletionDateTime: time.Date(2022, time.November, 23, 2, 54, 5, 0, time.UTC), + Method: "SCHEDULED", + } + + assert.Equal(t, expected, quickDeployment) +} + +func TestClient_CreateZoneResourceRecord(t *testing.T) { + client := mockBuilderAuthenticated(). + Route("POST /api/v2/zones/12345/resourceRecords", + servermock.ResponseFromFixture("postZoneResourceRecord.json"), + servermock.CheckRequestJSONBodyFromFixture("postZoneResourceRecord-request.json"), + ). + Build(t) + + record := RecordTXT{ + CommonResource: CommonResource{ + Type: "TXTRecord", + Name: "_acme-challenge", + }, + Text: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 120, + RecordType: "TXT", + } + + result, err := client.CreateZoneResourceRecord(mockToken(t.Context()), 12345, record) + require.NoError(t, err) + + expected := &RecordTXT{ + CommonResource: CommonResource{ + ID: 12345, + Type: "ResourceRecord", + Name: "name", + }, + TTL: 3600, + AbsoluteName: "host1.example.com", + Comment: "Sample comment.", + Dynamic: true, + RecordType: "CNAME", + Text: "", + } + + assert.Equal(t, expected, result) +} + +func TestClient_DeleteResourceRecord(t *testing.T) { + client := mockBuilderAuthenticated(). + Route("DELETE /api/v2/resourceRecords/12345", + servermock.ResponseFromFixture("deleteResourceRecord.json"), + ). + Build(t) + + err := client.DeleteResourceRecord(mockToken(t.Context()), 12345) + require.NoError(t, err) +} diff --git a/providers/dns/bluecatv2/internal/fixtures/deleteResourceRecord.json b/providers/dns/bluecatv2/internal/fixtures/deleteResourceRecord.json new file mode 100644 index 000000000..38ae2db6e --- /dev/null +++ b/providers/dns/bluecatv2/internal/fixtures/deleteResourceRecord.json @@ -0,0 +1,75 @@ +{ + "id": 12345, + "type": "WorkflowRequest", + "state": "APPROVED", + "operation": "ADD_ALIAS_RECORD", + "creator": { + "id": 103307, + "type": "User", + "name": "admin", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "authenticator": { + "id": 12345, + "type": "Authenticator", + "name": "LDAP authenticator" + }, + "email": "user@example.com", + "phoneNumber": "555-1234", + "securityPrivilege": "NO_ACCESS", + "historyPrivilege": "HIDE", + "accessType": "GUI", + "passwordResetRequired": true, + "accountLocked": true, + "x509Required": true, + "administrativeAccessRights": [ + { + "resourceType": "Event", + "accessLevel": "HIDE" + } + ] + }, + "resourceId": 0, + "resourceType": "ACL", + "fieldUpdates": [ + { + "name": "string", + "value": {}, + "previousValue": {} + } + ], + "dependentRequest": "string", + "modifier": { + "id": 103307, + "type": "User", + "name": "admin", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "authenticator": { + "id": 12345, + "type": "Authenticator", + "name": "LDAP authenticator" + }, + "email": "user@example.com", + "phoneNumber": "555-1234", + "securityPrivilege": "NO_ACCESS", + "historyPrivilege": "HIDE", + "accessType": "GUI", + "passwordResetRequired": true, + "accountLocked": true, + "x509Required": true, + "administrativeAccessRights": [ + { + "resourceType": "Event", + "accessLevel": "HIDE" + } + ] + }, + "creationDateTime": "2022-10-17T19:11:45Z", + "modificationDateTime": "2022-10-18T19:11:45Z", + "comment": "Sample comment." +} diff --git a/providers/dns/bluecatv2/internal/fixtures/error.json b/providers/dns/bluecatv2/internal/fixtures/error.json new file mode 100644 index 000000000..d3d2b8b5f --- /dev/null +++ b/providers/dns/bluecatv2/internal/fixtures/error.json @@ -0,0 +1,6 @@ +{ + "status": 401, + "reason": "Unauthorized", + "code": "InvalidAuthorizationToken", + "message": "The provided authorization token is invalid" +} diff --git a/providers/dns/bluecatv2/internal/fixtures/getZoneDeployments.json b/providers/dns/bluecatv2/internal/fixtures/getZoneDeployments.json new file mode 100644 index 000000000..b1a4938ad --- /dev/null +++ b/providers/dns/bluecatv2/internal/fixtures/getZoneDeployments.json @@ -0,0 +1,46 @@ +{ + "count": 0, + "totalCount": 0, + "data": [ + { + "id": 12345, + "type": "QuickDeployment", + "state": "PENDING", + "status": "CANCEL", + "message": "string", + "percentComplete": 50, + "creationDateTime": "2022-11-23T02:53:00Z", + "startDateTime": "2022-11-23T02:53:03Z", + "completionDateTime": "2022-11-23T02:54:05Z", + "user": { + "id": 103307, + "type": "User", + "name": "admin", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "authenticator": { + "id": 12345, + "type": "Authenticator", + "name": "LDAP authenticator" + }, + "email": "user@example.com", + "phoneNumber": "555-1234", + "securityPrivilege": "NO_ACCESS", + "historyPrivilege": "HIDE", + "accessType": "GUI", + "passwordResetRequired": true, + "accountLocked": true, + "x509Required": true, + "administrativeAccessRights": [ + { + "resourceType": "Event", + "accessLevel": "HIDE" + } + ] + }, + "method": "SCHEDULED" + } + ] +} diff --git a/providers/dns/bluecatv2/internal/fixtures/postSession-request.json b/providers/dns/bluecatv2/internal/fixtures/postSession-request.json new file mode 100644 index 000000000..e62048eb9 --- /dev/null +++ b/providers/dns/bluecatv2/internal/fixtures/postSession-request.json @@ -0,0 +1,4 @@ +{ + "username": "userA", + "password": "secret" +} diff --git a/providers/dns/bluecatv2/internal/fixtures/postSession.json b/providers/dns/bluecatv2/internal/fixtures/postSession.json new file mode 100644 index 000000000..4599ad0ad --- /dev/null +++ b/providers/dns/bluecatv2/internal/fixtures/postSession.json @@ -0,0 +1,50 @@ +{ + "id": 12345, + "type": "UserSession", + "apiToken": "VZoO2Z0BjBaJyvuhE4vNJRWqI9upwDHk70UNi0Ez", + "apiTokenExpirationDateTime": "2022-09-15T17:52:07Z", + "basicAuthenticationCredentials": "YXBpOlQ0OExOT3VIRGhDcnVBNEo1bGFES3JuS3hTZC9QK3pjczZXTzBJMDY=", + "remoteAddress": "192.168.1.1", + "readOnly": true, + "loginDateTime": "2022-09-14T17:45:03Z", + "logoutDateTime": "2022-09-14T19:45:03Z", + "state": "LOGGED_IN", + "response": "Authentication Error: Ensure that your username and password are correct.", + "user": { + "id": 103307, + "type": "User", + "name": "admin", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "authenticator": { + "id": 12345, + "type": "Authenticator", + "name": "LDAP authenticator" + }, + "email": "user@example.com", + "phoneNumber": "555-1234", + "securityPrivilege": "NO_ACCESS", + "historyPrivilege": "HIDE", + "accessType": "GUI", + "passwordResetRequired": true, + "accountLocked": true, + "x509Required": true, + "administrativeAccessRights": [ + { + "resourceType": "Event", + "accessLevel": "HIDE" + } + ] + }, + "authenticator": { + "id": 12345, + "type": "Authenticator", + "name": "LDAP authenticator", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + } + } +} diff --git a/providers/dns/bluecatv2/internal/fixtures/postZoneDeployment-request.json b/providers/dns/bluecatv2/internal/fixtures/postZoneDeployment-request.json new file mode 100644 index 000000000..099573a84 --- /dev/null +++ b/providers/dns/bluecatv2/internal/fixtures/postZoneDeployment-request.json @@ -0,0 +1,3 @@ +{ + "type": "QuickDeployment" +} diff --git a/providers/dns/bluecatv2/internal/fixtures/postZoneDeployment.json b/providers/dns/bluecatv2/internal/fixtures/postZoneDeployment.json new file mode 100644 index 000000000..fd26781fb --- /dev/null +++ b/providers/dns/bluecatv2/internal/fixtures/postZoneDeployment.json @@ -0,0 +1,40 @@ +{ + "id": 12345, + "type": "QuickDeployment", + "state": "PENDING", + "status": "CANCEL", + "message": "string", + "percentComplete": 50, + "creationDateTime": "2022-11-23T02:53:00Z", + "startDateTime": "2022-11-23T02:53:03Z", + "completionDateTime": "2022-11-23T02:54:05Z", + "user": { + "id": 103307, + "type": "User", + "name": "admin", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "authenticator": { + "id": 12345, + "type": "Authenticator", + "name": "LDAP authenticator" + }, + "email": "user@example.com", + "phoneNumber": "555-1234", + "securityPrivilege": "NO_ACCESS", + "historyPrivilege": "HIDE", + "accessType": "GUI", + "passwordResetRequired": true, + "accountLocked": true, + "x509Required": true, + "administrativeAccessRights": [ + { + "resourceType": "Event", + "accessLevel": "HIDE" + } + ] + }, + "method": "SCHEDULED" +} diff --git a/providers/dns/bluecatv2/internal/fixtures/postZoneResourceRecord-request.json b/providers/dns/bluecatv2/internal/fixtures/postZoneResourceRecord-request.json new file mode 100644 index 000000000..2de733c71 --- /dev/null +++ b/providers/dns/bluecatv2/internal/fixtures/postZoneResourceRecord-request.json @@ -0,0 +1,7 @@ +{ + "type": "TXTRecord", + "name": "_acme-challenge", + "ttl": 120, + "recordType": "TXT", + "text": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" +} diff --git a/providers/dns/bluecatv2/internal/fixtures/postZoneResourceRecord.json b/providers/dns/bluecatv2/internal/fixtures/postZoneResourceRecord.json new file mode 100644 index 000000000..78d028ee3 --- /dev/null +++ b/providers/dns/bluecatv2/internal/fixtures/postZoneResourceRecord.json @@ -0,0 +1,25 @@ +{ + "id": 12345, + "type": "ResourceRecord", + "name": "name", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "configuration": { + "id": 12345, + "type": "Configuration", + "name": "name" + }, + "ttl": 3600, + "absoluteName": "host1.example.com", + "comment": "Sample comment.", + "dynamic": true, + "recordType": "CNAME", + "linkedRecord": { + "id": 12345, + "type": "ResourceRecord", + "name": "name", + "absoluteName": "host1.example.com" + } +} diff --git a/providers/dns/bluecatv2/internal/fixtures/zones.json b/providers/dns/bluecatv2/internal/fixtures/zones.json new file mode 100644 index 000000000..b9f2dfa8f --- /dev/null +++ b/providers/dns/bluecatv2/internal/fixtures/zones.json @@ -0,0 +1,185 @@ +{ + "count": 0, + "totalCount": 0, + "data": [ + { + "id": 12345, + "type": "ENUMZone", + "name": "5678", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "configuration": { + "id": 12345, + "type": "Configuration", + "name": "name" + }, + "view": { + "id": 12345, + "type": "View", + "name": "default", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "configuration": { + "id": 12345, + "type": "Configuration", + "name": "name" + }, + "deviceRegistrationEnabled": true, + "deviceRegistrationPortalAddress": "10.10.10.10" + }, + "deploymentEnabled": true, + "absoluteName": "string" + }, + { + "id": 12345, + "type": "ExternalHostsZone", + "name": "name", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "configuration": { + "id": 12345, + "type": "Configuration", + "name": "name" + }, + "view": { + "id": 12345, + "type": "View", + "name": "default", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "configuration": { + "id": 12345, + "type": "Configuration", + "name": "name" + }, + "deviceRegistrationEnabled": true, + "deviceRegistrationPortalAddress": "10.10.10.10" + } + }, + { + "id": 12345, + "type": "InternalRootZone", + "name": "name", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "configuration": { + "id": 12345, + "type": "Configuration", + "name": "name" + }, + "view": { + "id": 12345, + "type": "View", + "name": "default", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "configuration": { + "id": 12345, + "type": "Configuration", + "name": "name" + }, + "deviceRegistrationEnabled": true, + "deviceRegistrationPortalAddress": "10.10.10.10" + }, + "deploymentEnabled": true + }, + { + "id": 12345, + "type": "ResponsePolicyZone", + "name": "name", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "configuration": { + "id": 12345, + "type": "Configuration", + "name": "name" + }, + "view": { + "id": 12345, + "type": "View", + "name": "default", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "configuration": { + "id": 12345, + "type": "Configuration", + "name": "name" + }, + "deviceRegistrationEnabled": true, + "deviceRegistrationPortalAddress": "10.10.10.10" + }, + "responsePolicyZoneType": "LOCAL", + "responsePolicy": { + "id": 12345, + "type": "ResponsePolicy", + "name": "Block Response Policy" + }, + "overridePolicyType": "ALLOWLIST", + "overrideRefreshTime": "string", + "redirectTarget": "string", + "feedCategories": [ + "string" + ] + }, + { + "id": 12345, + "type": "Zone", + "name": "example.com", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "configuration": { + "id": 12345, + "type": "Configuration", + "name": "name" + }, + "view": { + "id": 12345, + "type": "View", + "name": "default", + "userDefinedFields": { + "udf1": "value1", + "udf2": "value2" + }, + "configuration": { + "id": 12345, + "type": "Configuration", + "name": "name" + }, + "deviceRegistrationEnabled": true, + "deviceRegistrationPortalAddress": "10.10.10.10" + }, + "deploymentEnabled": true, + "dynamicUpdateEnabled": true, + "template": { + "id": 12345, + "type": "ZoneTemplate", + "name": "name" + }, + "signed": true, + "signingPolicy": { + "id": 12345, + "type": "DNSSECSigningPolicy", + "name": "name" + }, + "absoluteName": "example.com" + } + ] +} diff --git a/providers/dns/bluecatv2/internal/identity.go b/providers/dns/bluecatv2/internal/identity.go new file mode 100644 index 000000000..af9355ab2 --- /dev/null +++ b/providers/dns/bluecatv2/internal/identity.go @@ -0,0 +1,60 @@ +package internal + +import ( + "context" + "fmt" + "net/http" +) + +type token string + +const tokenKey token = "token" + +const authorizationHeader = "Authorization" + +// CreateSession creates a new session. +func (c *Client) CreateSession(ctx context.Context, info LoginInfo) (*Session, error) { + endpoint := c.baseURL.JoinPath("api", "v2", "sessions") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, info) + if err != nil { + return nil, err + } + + result := new(Session) + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result, nil +} + +// CreateAuthenticatedContext creates a new authenticated context. +func (c *Client) CreateAuthenticatedContext(ctx context.Context) (context.Context, error) { + tok, err := c.CreateSession(ctx, LoginInfo{Username: c.username, Password: c.password}) + if err != nil { + return nil, fmt.Errorf("create session: %w", err) + } + + return context.WithValue(ctx, tokenKey, tok.BasicAuthenticationCredentials), nil +} + +func (c *Client) doAuthenticated(ctx context.Context, req *http.Request, result any) error { + tok := getToken(ctx) + if tok != "" { + req.Header.Set(authorizationHeader, "Basic "+tok) + } + + return c.do(req, result) +} + +func getToken(ctx context.Context) string { + tok, ok := ctx.Value(tokenKey).(string) + if !ok { + return "" + } + + return tok +} diff --git a/providers/dns/bluecatv2/internal/identity_test.go b/providers/dns/bluecatv2/internal/identity_test.go new file mode 100644 index 000000000..3a1c4d2a2 --- /dev/null +++ b/providers/dns/bluecatv2/internal/identity_test.go @@ -0,0 +1,82 @@ +package internal + +import ( + "context" + "net/http/httptest" + "net/url" + "testing" + "time" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(server.URL, "userA", "secret") + if err != nil { + return nil, err + } + + client.baseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(), + ) +} + +func mockToken(ctx context.Context) context.Context { + return context.WithValue(ctx, tokenKey, "secretToken") +} + +func TestClient_CreateSession(t *testing.T) { + client := mockBuilder(). + Route("POST /api/v2/sessions", + servermock.ResponseFromFixture("postSession.json"), + servermock.CheckRequestJSONBodyFromFixture("postSession-request.json"), + ). + Build(t) + + info := LoginInfo{ + Username: "userA", + Password: "secret", + } + + result, err := client.CreateSession(mockToken(t.Context()), info) + require.NoError(t, err) + + expected := &Session{ + ID: 12345, + Type: "UserSession", + APIToken: "VZoO2Z0BjBaJyvuhE4vNJRWqI9upwDHk70UNi0Ez", + APITokenExpirationDateTime: time.Date(2022, time.September, 15, 17, 52, 7, 0, time.UTC), + BasicAuthenticationCredentials: "YXBpOlQ0OExOT3VIRGhDcnVBNEo1bGFES3JuS3hTZC9QK3pjczZXTzBJMDY=", + RemoteAddress: "192.168.1.1", + ReadOnly: true, + LoginDateTime: time.Date(2022, time.September, 14, 17, 45, 3, 0, time.UTC), + LogoutDateTime: time.Date(2022, time.September, 14, 19, 45, 3, 0, time.UTC), + State: "LOGGED_IN", + Response: "Authentication Error: Ensure that your username and password are correct.", + } + + assert.Equal(t, expected, result) +} + +func TestClient_CreateAuthenticatedContext(t *testing.T) { + client := mockBuilder(). + Route("POST /api/v2/sessions", + servermock.ResponseFromFixture("postSession.json"), + servermock.CheckRequestJSONBodyFromFixture("postSession-request.json"), + ). + Build(t) + + ctx, err := client.CreateAuthenticatedContext(t.Context()) + require.NoError(t, err) + + assert.Equal(t, "YXBpOlQ0OExOT3VIRGhDcnVBNEo1bGFES3JuS3hTZC9QK3pjczZXTzBJMDY=", getToken(ctx)) +} diff --git a/providers/dns/bluecatv2/internal/predicates.go b/providers/dns/bluecatv2/internal/predicates.go new file mode 100644 index 000000000..8ed6f714b --- /dev/null +++ b/providers/dns/bluecatv2/internal/predicates.go @@ -0,0 +1,64 @@ +package internal + +import ( + "fmt" + "strings" +) + +type Predicate struct { + field string + operator string + values []string +} + +func (p *Predicate) String() string { + var values []string + for _, v := range p.values { + values = append(values, fmt.Sprintf("'%s'", v)) + } + + return fmt.Sprintf("%s:%s(%s)", p.field, p.operator, strings.Join(values, ", ")) +} + +func Eq(field, value string) *Predicate { + return &Predicate{field: field, operator: "eq", values: []string{value}} +} + +func Contains(field, value string) *Predicate { + return &Predicate{field: field, operator: "contains", values: []string{value}} +} + +func StartsWith(field, value string) *Predicate { + return &Predicate{field: field, operator: "startsWith", values: []string{value}} +} + +func EndsWith(field, value string) *Predicate { + return &Predicate{field: field, operator: "endsWith", values: []string{value}} +} + +func In(field string, values ...string) *Predicate { + return &Predicate{field: field, operator: "in", values: values} +} + +type Combined struct { + predicates []*Predicate + operator string +} + +func (o *Combined) String() string { + var parts []string + + for _, predicate := range o.predicates { + parts = append(parts, predicate.String()) + } + + return strings.Join(parts, " "+o.operator+" ") +} + +func And(predicates ...*Predicate) *Combined { + return &Combined{predicates: predicates, operator: "and"} +} + +func Or(predicates ...*Predicate) *Combined { + return &Combined{predicates: predicates, operator: "or"} +} diff --git a/providers/dns/bluecatv2/internal/predicates_test.go b/providers/dns/bluecatv2/internal/predicates_test.go new file mode 100644 index 000000000..6913e8729 --- /dev/null +++ b/providers/dns/bluecatv2/internal/predicates_test.go @@ -0,0 +1,78 @@ +package internal + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPredicate(t *testing.T) { + testCases := []struct { + desc string + predicate fmt.Stringer + expected string + }{ + { + desc: "Equals", + predicate: Eq("foo", "bar"), + expected: "foo:eq('bar')", + }, + { + desc: "Contains", + predicate: Contains("foo", "bar"), + expected: "foo:contains('bar')", + }, + { + desc: "Starts with", + predicate: StartsWith("foo", "bar"), + expected: "foo:startsWith('bar')", + }, + { + desc: "Ends with", + predicate: EndsWith("foo", "bar"), + expected: "foo:endsWith('bar')", + }, + { + desc: "Match a list of values", + predicate: In("foo", "bar", "bir"), + expected: "foo:in('bar', 'bir')", + }, + { + desc: "Combined: and", + predicate: And(Eq("foo", "bar"), Eq("fii", "bir")), + expected: "foo:eq('bar') and fii:eq('bir')", + }, + { + desc: "Combined: multiple and", + predicate: And( + Eq("foo", "bar"), + Eq("fii", "bir"), + Eq("fuu", "bur"), + ), + expected: "foo:eq('bar') and fii:eq('bir') and fuu:eq('bur')", + }, + { + desc: "Combined: or", + predicate: Or(Eq("foo", "bar"), Eq("foo", "bir")), + expected: "foo:eq('bar') or foo:eq('bir')", + }, + { + desc: "Combined: multiple or", + predicate: Or( + Eq("foo", "bar"), + Eq("foo", "bir"), + Eq("foo", "bur"), + ), + expected: "foo:eq('bar') or foo:eq('bir') or foo:eq('bur')", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + assert.Equal(t, test.expected, test.predicate.String()) + }) + } +} diff --git a/providers/dns/bluecatv2/internal/types.go b/providers/dns/bluecatv2/internal/types.go new file mode 100644 index 000000000..562fd60b0 --- /dev/null +++ b/providers/dns/bluecatv2/internal/types.go @@ -0,0 +1,122 @@ +package internal + +import ( + "fmt" + "time" +) + +// Quick deployment states. +// +//nolint:misspell // US vs UK +const ( + QDStatePending = "PENDING" + QDStateQueued = "QUEUED" + QDStateRunning = "RUNNING" + QDStateCancelled = "CANCELLED" + QDStateCancelling = "CANCELLING" + QDStateCompleted = "COMPLETED" + QDStateCompletedWithErrors = "COMPLETED_WITH_ERRORS" + QDStateCompletedWithWarnings = "COMPLETED_WITH_WARNINGS" + QDStateFailed = "FAILED" + QDStateUnknown = "UNKNOWN" +) + +// APIError represents an error. +// https://docs.bluecatnetworks.com/r/Address-Manager-RESTful-v2-API-Guide/Errors/9.6.0 +type APIError struct { + Status int `json:"status"` + Reason string `json:"reason"` + Code string `json:"code"` + Message string `json:"message"` +} + +func (a *APIError) Error() string { + return fmt.Sprintf("%d: %s: %s: %s", a.Status, a.Reason, a.Code, a.Message) +} + +// CommonResource represents the common resource fields. +// https://docs.bluecatnetworks.com/r/Address-Manager-RESTful-v2-API-Guide/Resources/9.6.0 +type CommonResource struct { + ID int64 `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` +} + +// Collection represents a collection of resources. +// https://docs.bluecatnetworks.com/r/Address-Manager-RESTful-v2-API-Guide/Collections/9.6.0 +type Collection[T any] struct { + Count int64 `json:"count"` + TotalCount int64 `json:"totalCount"` + Data []T `json:"data"` +} + +type CollectionOptions struct { + // https://docs.bluecatnetworks.com/r/Address-Manager-RESTful-v2-API-Guide/Fields/9.6.0 + Fields string `url:"fields,omitempty"` + + // https://docs.bluecatnetworks.com/r/Address-Manager-RESTful-v2-API-Guide/Pagination/9.6.0 + Limit int `url:"limit,omitempty"` + Offset int `url:"offset,omitempty"` + + // https://docs.bluecatnetworks.com/r/Address-Manager-RESTful-v2-API-Guide/Filter/9.6.0 + Filter string `url:"filter,omitempty"` + + // https://docs.bluecatnetworks.com/r/Address-Manager-RESTful-v2-API-Guide/Ordering/9.6.0 + OrderBy string `url:"orderBy,omitempty"` + + // Should return or not the total number of resources matching the query. + Total bool `url:"total,omitempty"` +} + +type RecordTXT struct { + CommonResource + + TTL int `json:"ttl,omitempty"` + AbsoluteName string `json:"absoluteName,omitempty"` + Comment string `json:"comment,omitempty"` + Dynamic bool `json:"dynamic,omitempty"` + RecordType string `json:"recordType,omitempty"` + Text string `json:"text,omitempty"` +} + +type ZoneResource struct { + CommonResource + + AbsoluteName string `json:"absoluteName,omitempty"` +} + +type QuickDeployment struct { + CommonResource + + State string `json:"state,omitempty"` + Status string `json:"status,omitempty"` + Message string `json:"message,omitempty"` + PercentComplete int `json:"percentComplete,omitempty"` + CreationDateTime time.Time `json:"creationDateTime,omitzero"` + StartDateTime time.Time `json:"startDateTime,omitzero"` + CompletionDateTime time.Time `json:"completionDateTime,omitzero"` + Method string `json:"method,omitempty"` +} + +// LoginInfo represents the login information. +// https://docs.bluecatnetworks.com/r/Address-Manager-RESTful-v2-API-Guide/Creating-an-API-session/9.6.0 +type LoginInfo struct { + Username string `json:"username"` + Password string `json:"password"` +} + +// Session represents the session. +// https://docs.bluecatnetworks.com/r/Address-Manager-RESTful-v2-API-Guide/Creating-an-API-session/9.6.0 +type Session struct { + ID int `json:"id"` + Type string `json:"type"` + APIToken string `json:"apiToken"` + APITokenExpirationDateTime time.Time `json:"apiTokenExpirationDateTime"` + BasicAuthenticationCredentials string `json:"basicAuthenticationCredentials"` + RemoteAddress string `json:"remoteAddress"` + ReadOnly bool `json:"readOnly"` + LoginDateTime time.Time `json:"loginDateTime"` + LogoutDateTime time.Time `json:"logoutDateTime"` + State string `json:"state"` + Response string `json:"response"` +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index b4b98e23f..ae41f6a20 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -25,6 +25,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/binarylane" "github.com/go-acme/lego/v4/providers/dns/bindman" "github.com/go-acme/lego/v4/providers/dns/bluecat" + "github.com/go-acme/lego/v4/providers/dns/bluecatv2" "github.com/go-acme/lego/v4/providers/dns/bookmyname" "github.com/go-acme/lego/v4/providers/dns/brandit" "github.com/go-acme/lego/v4/providers/dns/bunny" @@ -233,6 +234,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return bindman.NewDNSProvider() case "bluecat": return bluecat.NewDNSProvider() + case "bluecatv2": + return bluecatv2.NewDNSProvider() case "bookmyname": return bookmyname.NewDNSProvider() case "brandit": From 16894fb99e3aa60fe0a5f9edcbea7a5fb9d32f34 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 20 Jan 2026 17:59:42 +0100 Subject: [PATCH 272/298] allinkl: detect zone through API (#2721) --- providers/dns/allinkl/allinkl.go | 24 +++- providers/dns/allinkl/allinkl_test.go | 114 ++++++++++++++++++ providers/dns/allinkl/internal/client.go | 87 ++++++++----- providers/dns/allinkl/internal/client_test.go | 27 ++++- .../internal/fixtures/auth-request.xml | 7 ++ .../internal/fixtures/flood_protection.xml | 11 ++ .../get_dns_settings-zone_not_found.xml | 11 ++ ...get_dns_settings-zone_syntax_incorrect.xml | 11 ++ providers/dns/allinkl/internal/identity.go | 22 ++-- .../dns/allinkl/internal/identity_test.go | 12 +- providers/dns/allinkl/internal/types.go | 5 +- providers/dns/allinkl/internal/types_api.go | 12 +- 12 files changed, 277 insertions(+), 66 deletions(-) create mode 100644 providers/dns/allinkl/internal/fixtures/auth-request.xml create mode 100644 providers/dns/allinkl/internal/fixtures/flood_protection.xml create mode 100644 providers/dns/allinkl/internal/fixtures/get_dns_settings-zone_not_found.xml create mode 100644 providers/dns/allinkl/internal/fixtures/get_dns_settings-zone_syntax_incorrect.xml diff --git a/providers/dns/allinkl/allinkl.go b/providers/dns/allinkl/allinkl.go index 4a0aadd2b..0ccce7226 100644 --- a/providers/dns/allinkl/allinkl.go +++ b/providers/dns/allinkl/allinkl.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" "github.com/go-acme/lego/v4/providers/dns/allinkl/internal" "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" @@ -121,11 +122,6 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { func (d *DNSProvider) Present(domain, token, keyAuth string) error { info := dns01.GetChallengeInfo(domain, keyAuth) - authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("allinkl: could not find zone for domain %q: %w", domain, err) - } - ctx := context.Background() credential, err := d.identifier.Authentication(ctx, 60, true) @@ -135,6 +131,24 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { ctx = internal.WithContext(ctx, credential) + var authZone string + + for z := range dns01.DomainsSeq(info.EffectiveFQDN) { + _, errG := d.client.GetDNSSettings(ctx, z, "") + if errG != nil { + log.Infof("allinkl: get DNS settings zone[%q] %v", z, errG) + continue + } + + authZone = z + + break + } + + if authZone == "" { + return fmt.Errorf("allinkl: unable to find auth zone for '%s'", info.EffectiveFQDN) + } + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) if err != nil { return fmt.Errorf("allinkl: %w", err) diff --git a/providers/dns/allinkl/allinkl_test.go b/providers/dns/allinkl/allinkl_test.go index b42adce5d..7da47aee4 100644 --- a/providers/dns/allinkl/allinkl_test.go +++ b/providers/dns/allinkl/allinkl_test.go @@ -1,9 +1,18 @@ package allinkl import ( + "encoding/json" + "encoding/xml" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" "testing" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/go-acme/lego/v4/providers/dns/allinkl/internal" "github.com/stretchr/testify/require" ) @@ -143,3 +152,108 @@ func TestLiveCleanUp(t *testing.T) { err = provider.CleanUp(envTest.GetDomain(), "", "123d==") require.NoError(t, err) } + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.Login = "user" + config.Password = "secret" + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BaseURL, _ = url.Parse(server.URL) + p.identifier.BaseURL, _ = url.Parse(server.URL) + + return p, err + }, + ).Route("POST /KasAuth.php", + servermock.ResponseFromInternal("auth.xml"), + servermock.CheckRequestBodyFromInternal("auth-request.xml"). + IgnoreWhitespace(), + ) +} + +func extractKasRequest(reader io.Reader) (*internal.KasRequest, error) { + type ReqEnvelope struct { + XMLName xml.Name `xml:"Envelope"` + Body struct { + KasAPI struct { + Params string `xml:"Params"` + } `xml:"KasApi"` + } `xml:"Body"` + } + + raw, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + + reqEnvelope := ReqEnvelope{} + + err = xml.Unmarshal(raw, &reqEnvelope) + if err != nil { + return nil, err + } + + var kReq internal.KasRequest + + err = json.Unmarshal([]byte(reqEnvelope.Body.KasAPI.Params), &kReq) + if err != nil { + return nil, err + } + + return &kReq, nil +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("POST /KasApi.php", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + kReq, err := extractKasRequest(req.Body) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + switch kReq.Action { + case "get_dns_settings": + params := kReq.RequestParams.(map[string]any) + + if params["zone_host"] == "_acme-challenge.example.com." { + servermock.ResponseFromInternal("get_dns_settings_not_found.xml").ServeHTTP(rw, req) + } else { + servermock.ResponseFromInternal("get_dns_settings.xml").ServeHTTP(rw, req) + } + + case "add_dns_settings": + servermock.ResponseFromInternal("add_dns_settings.xml").ServeHTTP(rw, req) + + default: + http.Error(rw, fmt.Sprintf("unknown action: %v", kReq.Action), http.StatusBadRequest) + } + }), + ). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("POST /KasApi.php", + servermock.ResponseFromInternal("delete_dns_settings.xml"), + servermock.CheckRequestBodyFromInternal("delete_dns_settings-request.xml"). + IgnoreWhitespace()). + Build(t) + + provider.recordIDs["abc"] = "57347450" + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/allinkl/internal/client.go b/providers/dns/allinkl/internal/client.go index d747e9b36..d4403cac5 100644 --- a/providers/dns/allinkl/internal/client.go +++ b/providers/dns/allinkl/internal/client.go @@ -6,16 +6,21 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "strconv" "strings" "sync" "time" + "github.com/cenkalti/backoff/v5" + "github.com/go-acme/lego/v4/platform/wait" "github.com/go-acme/lego/v4/providers/dns/internal/errutils" "github.com/go-viper/mapstructure/v2" ) -const apiEndpoint = "https://kasapi.kasserver.com/soap/KasApi.php" +const defaultBaseURL = "https://kasapi.kasserver.com/soap/" + +const apiPath = "KasApi.php" type Authentication interface { Authentication(ctx context.Context, sessionLifetime int, sessionUpdateLifetime bool) (string, error) @@ -28,16 +33,21 @@ type Client struct { floodTime time.Time muFloodTime sync.Mutex - baseURL string + maxElapsedTime time.Duration + + BaseURL *url.URL HTTPClient *http.Client } // NewClient creates a new Client. func NewClient(login string) *Client { + baseURL, _ := url.Parse(defaultBaseURL) + return &Client{ - login: login, - baseURL: apiEndpoint, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, + login: login, + BaseURL: baseURL, + maxElapsedTime: 3 * time.Minute, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, } } @@ -51,14 +61,9 @@ func (c *Client) GetDNSSettings(ctx context.Context, zone, recordID string) ([]R requestParams["record_id"] = recordID } - req, err := c.newRequest(ctx, "get_dns_settings", requestParams) - if err != nil { - return nil, err - } + var g APIResponse[GetDNSSettingsResponse] - var g GetDNSSettingsAPIResponse - - err = c.do(req, &g) + err := c.doRequest(ctx, "get_dns_settings", requestParams, &g) if err != nil { return nil, err } @@ -70,14 +75,9 @@ func (c *Client) GetDNSSettings(ctx context.Context, zone, recordID string) ([]R // AddDNSSettings Creation of a DNS resource record. func (c *Client) AddDNSSettings(ctx context.Context, record DNSRequest) (string, error) { - req, err := c.newRequest(ctx, "add_dns_settings", record) - if err != nil { - return "", err - } + var g APIResponse[AddDNSSettingsResponse] - var g AddDNSSettingsAPIResponse - - err = c.do(req, &g) + err := c.doRequest(ctx, "add_dns_settings", record, &g) if err != nil { return "", err } @@ -91,14 +91,9 @@ func (c *Client) AddDNSSettings(ctx context.Context, record DNSRequest) (string, func (c *Client) DeleteDNSSettings(ctx context.Context, recordID string) (string, error) { requestParams := map[string]string{"record_id": recordID} - req, err := c.newRequest(ctx, "delete_dns_settings", requestParams) - if err != nil { - return "", err - } + var g APIResponse[DeleteDNSSettingsResponse] - var g DeleteDNSSettingsAPIResponse - - err = c.do(req, &g) + err := c.doRequest(ctx, "delete_dns_settings", requestParams, &g) if err != nil { return "", err } @@ -124,7 +119,9 @@ func (c *Client) newRequest(ctx context.Context, action string, requestParams an payload := []byte(strings.TrimSpace(fmt.Sprintf(kasAPIEnvelope, body))) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL, bytes.NewReader(payload)) + endpoint := c.BaseURL.JoinPath(apiPath) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), bytes.NewReader(payload)) if err != nil { return nil, fmt.Errorf("unable to create request: %w", err) } @@ -132,6 +129,21 @@ func (c *Client) newRequest(ctx context.Context, action string, requestParams an return req, nil } +func (c *Client) doRequest(ctx context.Context, action string, requestParams, result any) error { + return wait.Retry(ctx, + func() error { + req, err := c.newRequest(ctx, action, requestParams) + if err != nil { + return backoff.Permanent(err) + } + + return c.do(req, result) + }, + backoff.WithBackOff(&backoff.ZeroBackOff{}), + backoff.WithMaxElapsedTime(c.maxElapsedTime), + ) +} + func (c *Client) do(req *http.Request, result any) error { c.muFloodTime.Lock() time.Sleep(time.Until(c.floodTime)) @@ -139,29 +151,40 @@ func (c *Client) do(req *http.Request, result any) error { resp, err := c.HTTPClient.Do(req) if err != nil { - return errutils.NewHTTPDoError(req, err) + return backoff.Permanent(errutils.NewHTTPDoError(req, err)) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { - return errutils.NewUnexpectedResponseStatusCodeError(req, resp) + return backoff.Permanent(errutils.NewUnexpectedResponseStatusCodeError(req, resp)) } envlp, err := decodeXML[KasAPIResponseEnvelope](resp.Body) if err != nil { - return err + return backoff.Permanent(err) } if envlp.Body.Fault != nil { - return envlp.Body.Fault + if envlp.Body.Fault.Message == "flood_protection" { + ft, errP := strconv.ParseFloat(envlp.Body.Fault.Detail, 64) + if errP != nil { + return fmt.Errorf("parse flood protection delay: %w", envlp.Body.Fault) + } + + c.updateFloodTime(ft) + + return envlp.Body.Fault + } + + return backoff.Permanent(envlp.Body.Fault) } raw := getValue(envlp.Body.KasAPIResponse.Return) err = mapstructure.Decode(raw, result) if err != nil { - return fmt.Errorf("response struct decode: %w", err) + return backoff.Permanent(fmt.Errorf("response struct decode: %w", err)) } return nil diff --git a/providers/dns/allinkl/internal/client_test.go b/providers/dns/allinkl/internal/client_test.go index 4b111e31c..949f45bf9 100644 --- a/providers/dns/allinkl/internal/client_test.go +++ b/providers/dns/allinkl/internal/client_test.go @@ -2,7 +2,9 @@ package internal import ( "net/http/httptest" + "net/url" "testing" + "time" "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" @@ -11,15 +13,17 @@ import ( func setupClient(server *httptest.Server) (*Client, error) { client := NewClient("user") - client.baseURL = server.URL + client.BaseURL, _ = url.Parse(server.URL) client.HTTPClient = server.Client() + client.maxElapsedTime = 1 * time.Second + return client, nil } func TestClient_GetDNSSettings(t *testing.T) { client := servermock.NewBuilder[*Client](setupClient). - Route("POST /", servermock.ResponseFromFixture("get_dns_settings.xml"), + Route("POST /KasApi.php", servermock.ResponseFromFixture("get_dns_settings.xml"), servermock.CheckRequestBodyFromFixture("get_dns_settings-request.xml"). IgnoreWhitespace()). Build(t) @@ -96,9 +100,24 @@ func TestClient_GetDNSSettings(t *testing.T) { assert.Equal(t, expected, records) } +func TestClient_GetDNSSettings_error_flood_protection(t *testing.T) { + client := servermock.NewBuilder[*Client](setupClient). + Route("POST /KasApi.php", + servermock.ResponseFromFixture("flood_protection.xml"), + ). + Build(t) + + assert.Zero(t, client.floodTime) + + _, err := client.GetDNSSettings(mockContext(t), "example.com", "") + require.EqualError(t, err, "KasApi: SOAP-ENV:Server: flood_protection: 0.0688529014587") + + assert.NotZero(t, client.floodTime) +} + func TestClient_AddDNSSettings(t *testing.T) { client := servermock.NewBuilder[*Client](setupClient). - Route("POST /", servermock.ResponseFromFixture("add_dns_settings.xml"), + Route("POST /KasApi.php", servermock.ResponseFromFixture("add_dns_settings.xml"), servermock.CheckRequestBodyFromFixture("add_dns_settings-request.xml"). IgnoreWhitespace()). Build(t) @@ -118,7 +137,7 @@ func TestClient_AddDNSSettings(t *testing.T) { func TestClient_DeleteDNSSettings(t *testing.T) { client := servermock.NewBuilder[*Client](setupClient). - Route("POST /", servermock.ResponseFromFixture("delete_dns_settings.xml"), + Route("POST /KasApi.php", servermock.ResponseFromFixture("delete_dns_settings.xml"), servermock.CheckRequestBodyFromFixture("delete_dns_settings-request.xml"). IgnoreWhitespace()). Build(t) diff --git a/providers/dns/allinkl/internal/fixtures/auth-request.xml b/providers/dns/allinkl/internal/fixtures/auth-request.xml new file mode 100644 index 000000000..1cba86f10 --- /dev/null +++ b/providers/dns/allinkl/internal/fixtures/auth-request.xml @@ -0,0 +1,7 @@ + + + + {"kas_login":"user","kas_auth_data":"secret","kas_auth_type":"plain","session_lifetime":60,"session_update_lifetime":"Y"} + + + diff --git a/providers/dns/allinkl/internal/fixtures/flood_protection.xml b/providers/dns/allinkl/internal/fixtures/flood_protection.xml new file mode 100644 index 000000000..b8da10fab --- /dev/null +++ b/providers/dns/allinkl/internal/fixtures/flood_protection.xml @@ -0,0 +1,11 @@ + + + + + SOAP-ENV:Server + flood_protection + KasApi + 0.0688529014587 + + + diff --git a/providers/dns/allinkl/internal/fixtures/get_dns_settings-zone_not_found.xml b/providers/dns/allinkl/internal/fixtures/get_dns_settings-zone_not_found.xml new file mode 100644 index 000000000..478d07a3a --- /dev/null +++ b/providers/dns/allinkl/internal/fixtures/get_dns_settings-zone_not_found.xml @@ -0,0 +1,11 @@ + + + + + SOAP-ENV:Server + zone_not_found + KasApi + example.com + + + diff --git a/providers/dns/allinkl/internal/fixtures/get_dns_settings-zone_syntax_incorrect.xml b/providers/dns/allinkl/internal/fixtures/get_dns_settings-zone_syntax_incorrect.xml new file mode 100644 index 000000000..c77d733db --- /dev/null +++ b/providers/dns/allinkl/internal/fixtures/get_dns_settings-zone_syntax_incorrect.xml @@ -0,0 +1,11 @@ + + + + + SOAP-ENV:Server + zone_syntax_incorrect + KasApi + _acme-challenge.example.com + + + diff --git a/providers/dns/allinkl/internal/identity.go b/providers/dns/allinkl/internal/identity.go index ba8d4d90e..e95e78899 100644 --- a/providers/dns/allinkl/internal/identity.go +++ b/providers/dns/allinkl/internal/identity.go @@ -6,14 +6,14 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "strings" "time" "github.com/go-acme/lego/v4/providers/dns/internal/errutils" ) -// authEndpoint represents the Identity API endpoint to call. -const authEndpoint = "https://kasapi.kasserver.com/soap/KasAuth.php" +const authPath = "KasAuth.php" type token string @@ -24,17 +24,19 @@ type Identifier struct { login string password string - authEndpoint string - HTTPClient *http.Client + BaseURL *url.URL + HTTPClient *http.Client } // NewIdentifier creates a new Identifier. func NewIdentifier(login, password string) *Identifier { + baseURL, _ := url.Parse(defaultBaseURL) + return &Identifier{ - login: login, - password: password, - authEndpoint: authEndpoint, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, + login: login, + password: password, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, } } @@ -62,7 +64,9 @@ func (c *Identifier) Authentication(ctx context.Context, sessionLifetime int, se payload := []byte(strings.TrimSpace(fmt.Sprintf(kasAuthEnvelope, body))) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.authEndpoint, bytes.NewReader(payload)) + endpoint := c.BaseURL.JoinPath(authPath) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), bytes.NewReader(payload)) if err != nil { return "", fmt.Errorf("unable to create request: %w", err) } diff --git a/providers/dns/allinkl/internal/identity_test.go b/providers/dns/allinkl/internal/identity_test.go index 7b93b7688..41d092b13 100644 --- a/providers/dns/allinkl/internal/identity_test.go +++ b/providers/dns/allinkl/internal/identity_test.go @@ -3,6 +3,7 @@ package internal import ( "context" "net/http/httptest" + "net/url" "testing" "github.com/go-acme/lego/v4/platform/tester/servermock" @@ -12,7 +13,7 @@ import ( func setupIdentifierClient(server *httptest.Server) (*Identifier, error) { client := NewIdentifier("user", "secret") - client.authEndpoint = server.URL + client.BaseURL, _ = url.Parse(server.URL) client.HTTPClient = server.Client() return client, nil @@ -26,10 +27,13 @@ func mockContext(t *testing.T) context.Context { func TestIdentifier_Authentication(t *testing.T) { client := servermock.NewBuilder[*Identifier](setupIdentifierClient). - Route("POST /", servermock.ResponseFromFixture("auth.xml")). + Route("POST /KasAuth.php", + servermock.ResponseFromFixture("auth.xml"), + servermock.CheckRequestBodyFromFixture("auth-request.xml"). + IgnoreWhitespace()). Build(t) - credentialToken, err := client.Authentication(t.Context(), 60, false) + credentialToken, err := client.Authentication(t.Context(), 60, true) require.NoError(t, err) assert.Equal(t, "593959ca04f0de9689b586c6a647d15d", credentialToken) @@ -37,7 +41,7 @@ func TestIdentifier_Authentication(t *testing.T) { func TestIdentifier_Authentication_error(t *testing.T) { client := servermock.NewBuilder[*Identifier](setupIdentifierClient). - Route("POST /", servermock.ResponseFromFixture("auth_fault.xml")). + Route("POST /KasAuth.php", servermock.ResponseFromFixture("auth_fault.xml")). Build(t) _, err := client.Authentication(t.Context(), 60, false) diff --git a/providers/dns/allinkl/internal/types.go b/providers/dns/allinkl/internal/types.go index b0aa9b4ff..51f7065b5 100644 --- a/providers/dns/allinkl/internal/types.go +++ b/providers/dns/allinkl/internal/types.go @@ -26,10 +26,11 @@ type Fault struct { Code string `xml:"faultcode"` Message string `xml:"faultstring"` Actor string `xml:"faultactor"` + Detail string `xml:"detail"` } -func (f Fault) Error() string { - return fmt.Sprintf("%s: %s: %s", f.Actor, f.Code, f.Message) +func (f *Fault) Error() string { + return fmt.Sprintf("%s: %s: %s: %s", f.Actor, f.Code, f.Message, f.Detail) } // KasResponse a KAS SOAP response. diff --git a/providers/dns/allinkl/internal/types_api.go b/providers/dns/allinkl/internal/types_api.go index 22f2c32ed..a11f3aac0 100644 --- a/providers/dns/allinkl/internal/types_api.go +++ b/providers/dns/allinkl/internal/types_api.go @@ -53,8 +53,8 @@ type DNSRequest struct { // --- -type GetDNSSettingsAPIResponse struct { - Response GetDNSSettingsResponse `json:"Response" mapstructure:"Response"` +type APIResponse[T any] struct { + Response T `json:"Response" mapstructure:"Response"` } type GetDNSSettingsResponse struct { @@ -73,20 +73,12 @@ type ReturnInfo struct { Aux int `json:"record_aux,omitempty" mapstructure:"record_aux"` } -type AddDNSSettingsAPIResponse struct { - Response AddDNSSettingsResponse `json:"Response" mapstructure:"Response"` -} - type AddDNSSettingsResponse struct { KasFloodDelay float64 `json:"KasFloodDelay" mapstructure:"KasFloodDelay"` ReturnInfo string `json:"ReturnInfo" mapstructure:"ReturnInfo"` ReturnString string `json:"ReturnString" mapstructure:"ReturnString"` } -type DeleteDNSSettingsAPIResponse struct { - Response DeleteDNSSettingsResponse `json:"Response"` -} - type DeleteDNSSettingsResponse struct { KasFloodDelay float64 `json:"KasFloodDelay"` ReturnString string `json:"ReturnString"` From 44b89b7e929c78575d28a9c35be0427ed06b8628 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 22 Jan 2026 05:10:35 +0100 Subject: [PATCH 273/298] allinkl: factorize findZone --- providers/dns/allinkl/allinkl.go | 33 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/providers/dns/allinkl/allinkl.go b/providers/dns/allinkl/allinkl.go index 0ccce7226..376b0903c 100644 --- a/providers/dns/allinkl/allinkl.go +++ b/providers/dns/allinkl/allinkl.go @@ -131,22 +131,9 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { ctx = internal.WithContext(ctx, credential) - var authZone string - - for z := range dns01.DomainsSeq(info.EffectiveFQDN) { - _, errG := d.client.GetDNSSettings(ctx, z, "") - if errG != nil { - log.Infof("allinkl: get DNS settings zone[%q] %v", z, errG) - continue - } - - authZone = z - - break - } - - if authZone == "" { - return fmt.Errorf("allinkl: unable to find auth zone for '%s'", info.EffectiveFQDN) + authZone, err := d.findZone(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("allinkl: %w", err) } subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) @@ -206,3 +193,17 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } + +func (d *DNSProvider) findZone(ctx context.Context, fqdn string) (string, error) { + for z := range dns01.DomainsSeq(fqdn) { + _, errG := d.client.GetDNSSettings(ctx, z, "") + if errG != nil { + log.Infof("get DNS settings zone[%q] %v", z, errG) + continue + } + + return z, nil + } + + return "", fmt.Errorf("unable to find auth zone for '%s'", fqdn) +} From 2ce04a6586ea27253975e10d3ab7d7bb6214c79d Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 25 Jan 2026 14:51:12 +0100 Subject: [PATCH 274/298] alidns: add line record option (#2814) --- cmd/zz_gen_cmd_dnshelp.go | 2 ++ docs/content/dns/zz_gen_alidns.md | 2 ++ providers/dns/alidns/alidns.go | 13 +++++++++++-- providers/dns/alidns/alidns.toml | 2 ++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 600e49753..357834a3c 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -263,8 +263,10 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(`Additional Configuration:`) ew.writeln(` - "ALICLOUD_HTTP_TIMEOUT": API request timeout in seconds (Default: 10)`) + ew.writeln(` - "ALICLOUD_LINE": Line (Default: default)`) ew.writeln(` - "ALICLOUD_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "ALICLOUD_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "ALICLOUD_REGION_ID": Region ID (Default: cn-hangzhou)`) ew.writeln(` - "ALICLOUD_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)`) ew.writeln() diff --git a/docs/content/dns/zz_gen_alidns.md b/docs/content/dns/zz_gen_alidns.md index e498a31dd..4ded782ab 100644 --- a/docs/content/dns/zz_gen_alidns.md +++ b/docs/content/dns/zz_gen_alidns.md @@ -58,8 +58,10 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | Environment Variable Name | Description | |--------------------------------|-------------| | `ALICLOUD_HTTP_TIMEOUT` | API request timeout in seconds (Default: 10) | +| `ALICLOUD_LINE` | Line (Default: default) | | `ALICLOUD_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `ALICLOUD_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `ALICLOUD_REGION_ID` | Region ID (Default: cn-hangzhou) | | `ALICLOUD_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 600) | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/providers/dns/alidns/alidns.go b/providers/dns/alidns/alidns.go index a5c883fcb..cdd8e75e0 100644 --- a/providers/dns/alidns/alidns.go +++ b/providers/dns/alidns/alidns.go @@ -27,6 +27,7 @@ const ( EnvSecretKey = envNamespace + "SECRET_KEY" EnvSecurityToken = envNamespace + "SECURITY_TOKEN" EnvRegionID = envNamespace + "REGION_ID" + EnvLine = envNamespace + "LINE" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -45,6 +46,7 @@ type Config struct { SecretKey string SecurityToken string RegionID string + Line string PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -74,6 +76,7 @@ type DNSProvider struct { func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() config.RegionID = env.GetOrFile(EnvRegionID) + config.Line = env.GetOrFile(EnvLine) values, err := env.Get(EnvRAMRole) if err == nil { @@ -254,12 +257,18 @@ func (d *DNSProvider) newTxtRecord(zone, fqdn, value string) (*alidns.AddDomainR return nil, err } - return new(alidns.AddDomainRecordRequest). + adrr := new(alidns.AddDomainRecordRequest). SetType("TXT"). SetDomainName(zone). SetRR(rr). SetValue(value). - SetTTL(int64(d.config.TTL)), nil + SetTTL(int64(d.config.TTL)) + + if d.config.Line != "" { + adrr.SetLine(d.config.Line) + } + + return adrr, nil } func (d *DNSProvider) findTxtRecords(ctx context.Context, fqdn string) ([]*alidns.DescribeDomainRecordsResponseBodyDomainRecordsRecord, error) { diff --git a/providers/dns/alidns/alidns.toml b/providers/dns/alidns/alidns.toml index 9a93bd24f..b78e1859d 100644 --- a/providers/dns/alidns/alidns.toml +++ b/providers/dns/alidns/alidns.toml @@ -23,6 +23,8 @@ lego --dns alidns - -d '*.example.com' -d example.com run ALICLOUD_SECRET_KEY = "Access Key secret" ALICLOUD_SECURITY_TOKEN = "STS Security Token (optional)" [Configuration.Additional] + ALICLOUD_REGION_ID = "Region ID (Default: cn-hangzhou)" + ALICLOUD_LINE = "Line (Default: default)" ALICLOUD_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" ALICLOUD_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" ALICLOUD_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)" From a7145a29ac5efc83c670248641ae25ff824876b3 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 28 Jan 2026 18:41:23 +0100 Subject: [PATCH 275/298] fix: use IPs to define the main domain (#2817) --- certcrypto/crypto.go | 14 +++++++++----- cmd/cmd_list.go | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/certcrypto/crypto.go b/certcrypto/crypto.go index 00f0654b9..800bb3f5b 100644 --- a/certcrypto/crypto.go +++ b/certcrypto/crypto.go @@ -242,15 +242,15 @@ func ParsePEMCertificate(cert []byte) (*x509.Certificate, error) { } func GetCertificateMainDomain(cert *x509.Certificate) (string, error) { - return getMainDomain(cert.Subject, cert.DNSNames) + return getMainDomain(cert.Subject, cert.DNSNames, cert.IPAddresses) } func GetCSRMainDomain(cert *x509.CertificateRequest) (string, error) { - return getMainDomain(cert.Subject, cert.DNSNames) + return getMainDomain(cert.Subject, cert.DNSNames, cert.IPAddresses) } -func getMainDomain(subject pkix.Name, dnsNames []string) (string, error) { - if subject.CommonName == "" && len(dnsNames) == 0 { +func getMainDomain(subject pkix.Name, dnsNames []string, ips []net.IP) (string, error) { + if subject.CommonName == "" && len(dnsNames) == 0 && len(ips) == 0 { return "", errors.New("missing domain") } @@ -258,7 +258,11 @@ func getMainDomain(subject pkix.Name, dnsNames []string) (string, error) { return subject.CommonName, nil } - return dnsNames[0], nil + if len(dnsNames) > 0 { + return dnsNames[0], nil + } + + return ips[0].String(), nil } func ExtractDomains(cert *x509.Certificate) []string { diff --git a/cmd/cmd_list.go b/cmd/cmd_list.go index 483592d47..53cd12c3c 100644 --- a/cmd/cmd_list.go +++ b/cmd/cmd_list.go @@ -3,6 +3,7 @@ package cmd import ( "encoding/json" "fmt" + "net" "net/url" "os" "path/filepath" @@ -100,6 +101,11 @@ func listCertificates(ctx *cli.Context) error { } else { fmt.Println(" Certificate Name:", name) fmt.Println(" Domains:", strings.Join(pCert.DNSNames, ", ")) + + if len(pCert.IPAddresses) > 0 { + fmt.Println(" IPs:", formatIPAddresses(pCert.IPAddresses)) + } + fmt.Println(" Expiry Date:", pCert.NotAfter) fmt.Println(" Certificate Path:", filename) fmt.Println() @@ -150,3 +156,12 @@ func listAccount(ctx *cli.Context) error { return nil } + +func formatIPAddresses(ipAddresses []net.IP) string { + var ips []string + for _, ip := range ipAddresses { + ips = append(ips, ip.String()) + } + + return strings.Join(ips, ", ") +} From fac5c39f5f9d36798a270af2d71578334001c2cf Mon Sep 17 00:00:00 2001 From: Mortie Torabi Date: Fri, 30 Jan 2026 19:36:46 +0000 Subject: [PATCH 276/298] fix: implement parsing for Retry-After header according to RFC 7231 (#2830) Co-authored-by: Fernandez Ludovic --- acme/api/service.go | 29 ++++++++++++++++++++++ acme/api/service_test.go | 37 ++++++++++++++++++++++++++++ certificate/renewal.go | 5 ++-- certificate/renewal_test.go | 36 +++++++++++++++++++++++++++ challenge/resolver/solver_manager.go | 17 ++++++------- 5 files changed, 112 insertions(+), 12 deletions(-) diff --git a/acme/api/service.go b/acme/api/service.go index 65518e1d9..22ce05124 100644 --- a/acme/api/service.go +++ b/acme/api/service.go @@ -1,8 +1,11 @@ package api import ( + "fmt" "net/http" "regexp" + "strconv" + "time" ) type service struct { @@ -56,3 +59,29 @@ func getRetryAfter(resp *http.Response) string { return resp.Header.Get("Retry-After") } + +// ParseRetryAfter parses the Retry-After header value according to RFC 7231. +// The header can be either delay-seconds (numeric) or HTTP-date (RFC 1123 format). +// https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3 +// Returns the duration until the retry time. +// TODO(ldez): unexposed this function in v5. +func ParseRetryAfter(value string) (time.Duration, error) { + if value == "" { + return 0, nil + } + + if seconds, err := strconv.ParseInt(value, 10, 64); err == nil { + return time.Duration(seconds) * time.Second, nil + } + + if retryTime, err := time.Parse(time.RFC1123, value); err == nil { + duration := time.Until(retryTime) + if duration < 0 { + return 0, nil + } + + return duration, nil + } + + return 0, fmt.Errorf("invalid Retry-After value: %q", value) +} diff --git a/acme/api/service_test.go b/acme/api/service_test.go index 2dbd795c9..57ea45708 100644 --- a/acme/api/service_test.go +++ b/acme/api/service_test.go @@ -3,8 +3,10 @@ package api import ( "net/http" "testing" + "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func Test_getLink(t *testing.T) { @@ -53,3 +55,38 @@ func Test_getLink(t *testing.T) { }) } } + +func TestParseRetryAfter(t *testing.T) { + testCases := []struct { + desc string + value string + expected time.Duration + }{ + { + desc: "empty header value", + value: "", + expected: time.Duration(0), + }, + { + desc: "delay-seconds", + value: "123", + expected: 123 * time.Second, + }, + { + desc: "HTTP-date", + value: time.Now().Add(3 * time.Second).Format(time.RFC1123), + expected: 3 * time.Second, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + rt, err := ParseRetryAfter(test.value) + require.NoError(t, err) + + assert.InDelta(t, test.expected.Seconds(), rt.Seconds(), 1) + }) + } +} diff --git a/certificate/renewal.go b/certificate/renewal.go index 15e804745..59d31cfb5 100644 --- a/certificate/renewal.go +++ b/certificate/renewal.go @@ -11,6 +11,7 @@ import ( "time" "github.com/go-acme/lego/v4/acme" + "github.com/go-acme/lego/v4/acme/api" ) // RenewalInfoRequest contains the necessary renewal information. @@ -92,9 +93,9 @@ func (c *Certifier) GetRenewalInfo(req RenewalInfoRequest) (*RenewalInfoResponse } if retry := resp.Header.Get("Retry-After"); retry != "" { - info.RetryAfter, err = time.ParseDuration(retry + "s") + info.RetryAfter, err = api.ParseRetryAfter(retry) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to parse Retry-After header: %w", err) } } diff --git a/certificate/renewal_test.go b/certificate/renewal_test.go index 6ce43e0aa..23209638a 100644 --- a/certificate/renewal_test.go +++ b/certificate/renewal_test.go @@ -74,6 +74,42 @@ func TestCertifier_GetRenewalInfo(t *testing.T) { assert.Equal(t, time.Duration(21600000000000), ri.RetryAfter) } +func TestCertifier_GetRenewalInfo_retryAfter(t *testing.T) { + leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM)) + require.NoError(t, err) + + server := tester.MockACMEServer(). + Route("GET /renewalInfo/"+ariLeafCertID, + servermock.RawStringResponse(`{ + "suggestedWindow": { + "start": "2020-03-17T17:51:09Z", + "end": "2020-03-17T18:21:09Z" + }, + "explanationUrl": "https://aricapable.ca.example/docs/renewal-advice/" + } + }`). + WithHeader("Content-Type", "application/json"). + WithHeader("Retry-After", time.Now().UTC().Add(6*time.Hour).Format(time.RFC1123))). + BuildHTTPS(t) + + key, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err, "Could not generate test key") + + core, err := api.New(server.Client(), "lego-test", server.URL+"/dir", "", key) + require.NoError(t, err) + + certifier := NewCertifier(core, &resolverMock{}, CertifierOptions{KeyType: certcrypto.RSA2048}) + + ri, err := certifier.GetRenewalInfo(RenewalInfoRequest{leaf}) + require.NoError(t, err) + require.NotNil(t, ri) + assert.Equal(t, "2020-03-17T17:51:09Z", ri.SuggestedWindow.Start.Format(time.RFC3339)) + assert.Equal(t, "2020-03-17T18:21:09Z", ri.SuggestedWindow.End.Format(time.RFC3339)) + assert.Equal(t, "https://aricapable.ca.example/docs/renewal-advice/", ri.ExplanationURL) + + assert.InDelta(t, 6, ri.RetryAfter.Hours(), 0.001) +} + func TestCertifier_GetRenewalInfo_errors(t *testing.T) { leaf, err := certcrypto.ParsePEMCertificate([]byte(ariLeafPEM)) require.NoError(t, err) diff --git a/challenge/resolver/solver_manager.go b/challenge/resolver/solver_manager.go index 48d9194b9..87cf6e2d8 100644 --- a/challenge/resolver/solver_manager.go +++ b/challenge/resolver/solver_manager.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "sort" - "strconv" "time" "github.com/cenkalti/backoff/v5" @@ -94,22 +93,20 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error { return nil } - ra, err := strconv.Atoi(chlng.RetryAfter) - if err != nil { + retryAfter, err := api.ParseRetryAfter(chlng.RetryAfter) + if err != nil || retryAfter == 0 { // The ACME server MUST return a Retry-After. - // If it doesn't, we'll just poll hard. + // If it doesn't, or if it's invalid, we'll just poll hard. // Boulder does not implement the ability to retry challenges or the Retry-After header. // https://github.com/letsencrypt/boulder/blob/master/docs/acme-divergences.md#section-82 - ra = 5 + retryAfter = 5 * time.Second } - initialInterval := time.Duration(ra) * time.Second - ctx := context.Background() bo := backoff.NewExponentialBackOff() - bo.InitialInterval = initialInterval - bo.MaxInterval = 10 * initialInterval + bo.InitialInterval = retryAfter + bo.MaxInterval = 10 * retryAfter // After the path is sent, the ACME server will access our server. // Repeatedly check the server for an updated status on our request. @@ -134,7 +131,7 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error { return wait.Retry(ctx, operation, backoff.WithBackOff(bo), - backoff.WithMaxElapsedTime(100*initialInterval)) + backoff.WithMaxElapsedTime(100*retryAfter)) } func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) { From c1aaf19aac0953ddffdea549b31b176dfcdddb1f Mon Sep 17 00:00:00 2001 From: Andy Warner Date: Mon, 9 Feb 2026 18:51:54 -0700 Subject: [PATCH 277/298] docs: make it more clear that any ACME CA may be used (#2841) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6324ece67..07e4f7dd6 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # Lego -Let's Encrypt client and ACME library written in Go. +[ACME](https://www.rfc-editor.org/rfc/rfc8555.html) client and library for Let's Encrypt and other ACME CAs written in Go. [![Go Reference](https://pkg.go.dev/badge/github.com/go-acme/lego/v4.svg)](https://pkg.go.dev/github.com/go-acme/lego/v4) [![Build Status](https://github.com//go-acme/lego/workflows/Main/badge.svg?branch=master)](https://github.com//go-acme/lego/actions) From 4c6d29882e14beea0287e67f36b3d75b15f27576 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 12 Feb 2026 17:32:23 +0100 Subject: [PATCH 278/298] chore: update linter, and workflows --- .github/workflows/pr.yml | 2 +- .golangci.yml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 5df64db7f..fe3d35359 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: stable - GOLANGCI_LINT_VERSION: v2.8.0 + GOLANGCI_LINT_VERSION: v2.9.0 HUGO_VERSION: 0.148.2 CGO_ENABLED: 0 LEGO_E2E_TESTS: CI diff --git a/.golangci.yml b/.golangci.yml index b851169ff..b6ab51ccc 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -183,6 +183,9 @@ linters: - text: "var-naming: avoid meaningless package names" linters: - revive + - text: "var-naming: avoid package names that conflict with Go standard library package names" + linters: + - revive - path: certcrypto/crypto.go text: (tlsFeatureExtensionOID|ocspMustStapleFeature) is a global variable linters: From dc51d5ee65cde20de9d8a98356d629a221418acf Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 12 Feb 2026 18:01:57 +0100 Subject: [PATCH 279/298] chore: use memcache Docker image directly (#2846) --- .github/workflows/pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index fe3d35359..9c845ea62 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -50,7 +50,7 @@ jobs: run: go install github.com/letsencrypt/pebble/v2/cmd/pebble-challtestsrv@v2.9.0 - name: Set up a Memcached server - uses: niden/actions-memcached@v7 + run: docker run -d --rm -p 11211:11211 memcached:1.6-alpine - name: Make run: | From 1991339cc15bc9468e4db101db465f50e568df88 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 12 Feb 2026 18:20:05 +0100 Subject: [PATCH 280/298] timewebcloud: fix subdomain support (#2845) --- providers/dns/timewebcloud/internal/types.go | 8 +++++--- providers/dns/timewebcloud/timewebcloud.go | 7 +------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/providers/dns/timewebcloud/internal/types.go b/providers/dns/timewebcloud/internal/types.go index 81da4df5c..80cdb2c70 100644 --- a/providers/dns/timewebcloud/internal/types.go +++ b/providers/dns/timewebcloud/internal/types.go @@ -3,9 +3,11 @@ package internal import "fmt" type DNSRecord struct { - ID int `json:"id,omitempty"` - Type string `json:"type,omitempty"` - Value string `json:"value,omitempty"` + ID int `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Value string `json:"value,omitempty"` + + // SubDomain is the full name of a subdomain (not only the subdomain label). SubDomain string `json:"subdomain,omitempty"` } diff --git a/providers/dns/timewebcloud/timewebcloud.go b/providers/dns/timewebcloud/timewebcloud.go index d71beea4b..a599566e3 100644 --- a/providers/dns/timewebcloud/timewebcloud.go +++ b/providers/dns/timewebcloud/timewebcloud.go @@ -110,15 +110,10 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { return fmt.Errorf("timewebcloud: could not find zone for domain %q: %w", domain, err) } - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) - if err != nil { - return fmt.Errorf("timewebcloud: %w", err) - } - record := internal.DNSRecord{ Type: "TXT", Value: info.Value, - SubDomain: subDomain, + SubDomain: dns01.UnFqdn(info.EffectiveFQDN), } response, err := d.client.CreateRecord(context.Background(), authZone, record) From 4a61728ff0db8179e060d185b73a8c0d539d4c91 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 12 Feb 2026 19:13:49 +0100 Subject: [PATCH 281/298] fix: deduplicate authz for DNS01 challenge (#2828) --- challenge/resolver/prober.go | 60 +++++++++++++++++++++++--- challenge/resolver/prober_mock_test.go | 55 ++++++++++++++++++----- challenge/resolver/prober_test.go | 48 +++++++++++++++++++-- 3 files changed, 142 insertions(+), 21 deletions(-) diff --git a/challenge/resolver/prober.go b/challenge/resolver/prober.go index aac1016d8..66b12c7a7 100644 --- a/challenge/resolver/prober.go +++ b/challenge/resolver/prober.go @@ -98,11 +98,24 @@ func (p *Prober) Solve(authorizations []acme.Authorization) error { } func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) { + // Some CA are using the same token, + // this can be a problem with the DNS01 challenge when the DNS provider doesn't support duplicate TXT records. + // In the sequential mode, this is not a problem because we can solve the challenges in order. + // But it can reduce the number of call the DNS provider APIs. + uniq := make(map[string]struct{}) + for i, authSolver := range authSolvers { // Submit the challenge domain := challenge.GetTargetedDomain(authSolver.authz) + chlg, _ := challenge.FindChallenge(challenge.DNS01, authSolver.authz) + if solvr, ok := authSolver.solver.(preSolver); ok { + if _, ok := uniq[authSolver.authz.Identifier.Value+chlg.Token]; ok && chlg.Token != "" { + log.Infof("acme: duplicate token for %q (DNS-01); skipping pre-solve.", authSolver.authz.Identifier.Value) + continue + } + err := solvr.PreSolve(authSolver.authz) if err != nil { failures[domain] = err @@ -111,6 +124,8 @@ func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) { continue } + + uniq[authSolver.authz.Identifier.Value+chlg.Token] = struct{}{} } // Solve challenge @@ -123,22 +138,43 @@ func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) { continue } - // Clean challenge - cleanUp(authSolver.solver, authSolver.authz) + if _, ok := uniq[authSolver.authz.Identifier.Value+chlg.Token]; ok || chlg.Token == "" { + // Clean challenge + cleanUp(authSolver.solver, authSolver.authz) - if len(authSolvers)-1 > i { - solvr := authSolver.solver.(sequential) - _, interval := solvr.Sequential() - log.Infof("sequence: wait for %s", interval) - time.Sleep(interval) + if len(authSolvers)-1 > i { + solvr := authSolver.solver.(sequential) + _, interval := solvr.Sequential() + log.Infof("sequence: wait for %s", interval) + time.Sleep(interval) + } + + delete(uniq, authSolver.authz.Identifier.Value+chlg.Token) + } else { + log.Infof("acme: duplicate token for %q (DNS-01); skipping cleanup.", authSolver.authz.Identifier.Value) } } } func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) { + // Some CA are using the same token, + // this can be a problem with the DNS01 challenge when the DNS provider doesn't support duplicate TXT records. + uniq := make(map[string]struct{}) + // For all valid preSolvers, first submit the challenges, so they have max time to propagate for _, authSolver := range authSolvers { authz := authSolver.authz + + chlg, err := challenge.FindChallenge(challenge.DNS01, authz) + if err == nil { + if _, ok := uniq[authz.Identifier.Value+chlg.Token]; ok { + log.Infof("acme: duplicate token for %q (DNS-01); skipping pre-solve.", authSolver.authz.Identifier.Value) + continue + } + + uniq[authz.Identifier.Value+chlg.Token] = struct{}{} + } + if solvr, ok := authSolver.solver.(preSolver); ok { err := solvr.PreSolve(authz) if err != nil { @@ -150,6 +186,16 @@ func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) { defer func() { // Clean all created TXT records for _, authSolver := range authSolvers { + chlg, err := challenge.FindChallenge(challenge.DNS01, authSolver.authz) + if err == nil { + if _, ok := uniq[authSolver.authz.Identifier.Value+chlg.Token]; ok { + delete(uniq, authSolver.authz.Identifier.Value+chlg.Token) + } else { + log.Infof("acme: duplicate token for %q (DNS-01); skipping cleanup.", authSolver.authz.Identifier.Value) + continue + } + } + cleanUp(authSolver.solver, authSolver.authz) } }() diff --git a/challenge/resolver/prober_mock_test.go b/challenge/resolver/prober_mock_test.go index 5a91fe075..dc7ad8dec 100644 --- a/challenge/resolver/prober_mock_test.go +++ b/challenge/resolver/prober_mock_test.go @@ -1,6 +1,7 @@ package resolver import ( + "fmt" "time" "github.com/go-acme/lego/v4/acme" @@ -11,34 +12,68 @@ type preSolverMock struct { preSolve map[string]error solve map[string]error cleanUp map[string]error + + preSolveCounter int + solveCounter int + cleanUpCounter int } func (s *preSolverMock) PreSolve(authorization acme.Authorization) error { + s.preSolveCounter++ + return s.preSolve[authorization.Identifier.Value] } func (s *preSolverMock) Solve(authorization acme.Authorization) error { + s.solveCounter++ + return s.solve[authorization.Identifier.Value] } func (s *preSolverMock) CleanUp(authorization acme.Authorization) error { + s.cleanUpCounter++ + return s.cleanUp[authorization.Identifier.Value] } +func (s *preSolverMock) String() string { + return fmt.Sprintf("PreSolve: %d, Solve: %d, CleanUp: %d", s.preSolveCounter, s.solveCounter, s.cleanUpCounter) +} + func createStubAuthorizationHTTP01(domain, status string) acme.Authorization { + return createStubAuthorization(domain, status, false, acme.Challenge{ + Type: challenge.HTTP01.String(), + Validated: time.Now(), + }) +} + +func createStubAuthorizationDNS01(domain string, wildcard bool) acme.Authorization { + var chlgs []acme.Challenge + + if wildcard { + chlgs = append(chlgs, acme.Challenge{ + Type: challenge.HTTP01.String(), + Validated: time.Now(), + }) + } + + chlgs = append(chlgs, acme.Challenge{ + Type: challenge.DNS01.String(), + Validated: time.Now(), + }) + + return createStubAuthorization(domain, acme.StatusProcessing, wildcard, chlgs...) +} + +func createStubAuthorization(domain, status string, wildcard bool, chlgs ...acme.Challenge) acme.Authorization { return acme.Authorization{ - Status: status, - Expires: time.Now(), + Wildcard: wildcard, + Status: status, + Expires: time.Now(), Identifier: acme.Identifier{ - Type: challenge.HTTP01.String(), + Type: "dns", Value: domain, }, - Challenges: []acme.Challenge{ - { - Type: challenge.HTTP01.String(), - Validated: time.Now(), - Error: nil, - }, - }, + Challenges: chlgs, } } diff --git a/challenge/resolver/prober_test.go b/challenge/resolver/prober_test.go index 06ff07d2c..829b16883 100644 --- a/challenge/resolver/prober_test.go +++ b/challenge/resolver/prober_test.go @@ -2,19 +2,22 @@ package resolver import ( "errors" + "fmt" "testing" "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/challenge" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestProber_Solve(t *testing.T) { testCases := []struct { - desc string - solvers map[challenge.Type]solver - authz []acme.Authorization - expectedError string + desc string + solvers map[challenge.Type]solver + authz []acme.Authorization + expectedError string + expectedCounters map[challenge.Type]string }{ { desc: "success", @@ -30,6 +33,30 @@ func TestProber_Solve(t *testing.T) { createStubAuthorizationHTTP01("example.org", acme.StatusProcessing), createStubAuthorizationHTTP01("example.net", acme.StatusProcessing), }, + expectedCounters: map[challenge.Type]string{ + challenge.HTTP01: "PreSolve: 3, Solve: 3, CleanUp: 3", + }, + }, + { + desc: "DNS-01 deduplicate", + solvers: map[challenge.Type]solver{ + challenge.DNS01: &preSolverMock{ + preSolve: map[string]error{}, + solve: map[string]error{}, + cleanUp: map[string]error{}, + }, + }, + authz: []acme.Authorization{ + createStubAuthorizationDNS01("a.example", false), + createStubAuthorizationDNS01("a.example", true), + createStubAuthorizationDNS01("b.example", false), + createStubAuthorizationDNS01("b.example", true), + createStubAuthorizationDNS01("c.example", true), + createStubAuthorizationDNS01("d.example", false), + }, + expectedCounters: map[challenge.Type]string{ + challenge.DNS01: "PreSolve: 4, Solve: 6, CleanUp: 4", + }, }, { desc: "already valid", @@ -45,6 +72,9 @@ func TestProber_Solve(t *testing.T) { createStubAuthorizationHTTP01("example.org", acme.StatusValid), createStubAuthorizationHTTP01("example.net", acme.StatusValid), }, + expectedCounters: map[challenge.Type]string{ + challenge.HTTP01: "PreSolve: 0, Solve: 0, CleanUp: 0", + }, }, { desc: "when preSolve fail, auth is flagged as error and skipped", @@ -69,6 +99,9 @@ func TestProber_Solve(t *testing.T) { expectedError: `error: one or more domains had a problem: [example.com] preSolve error example.com `, + expectedCounters: map[challenge.Type]string{ + challenge.HTTP01: "PreSolve: 3, Solve: 2, CleanUp: 3", + }, }, { desc: "errors at different stages", @@ -95,6 +128,9 @@ func TestProber_Solve(t *testing.T) { [example.com] preSolve error example.com [example.org] solve error example.org `, + expectedCounters: map[challenge.Type]string{ + challenge.HTTP01: "PreSolve: 3, Solve: 2, CleanUp: 3", + }, }, } @@ -112,6 +148,10 @@ func TestProber_Solve(t *testing.T) { } else { require.NoError(t, err) } + + for n, s := range test.solvers { + assert.Equal(t, test.expectedCounters[n], fmt.Sprintf("%s", s)) + } }) } } From 2e095b95a57621177a10ee1be2650406d8707524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grigas=20=C5=A0ukys?= <135010329+grigassukys@users.noreply.github.com> Date: Sun, 15 Feb 2026 04:22:35 +0200 Subject: [PATCH 282/298] Add DNS provider for FusionLayer NameSurfer (#2852) Co-authored-by: Fernandez Ludovic --- README.md | 62 ++--- cmd/zz_gen_cmd_dnshelp.go | 25 ++ docs/content/dns/zz_gen_namesurfer.md | 73 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/namesurfer/internal/client.go | 226 ++++++++++++++++++ .../dns/namesurfer/internal/client_test.go | 158 ++++++++++++ .../fixtures/addDNSRecord-request.json | 16 ++ .../internal/fixtures/addDNSRecord.json | 4 + .../namesurfer/internal/fixtures/error.json | 24 ++ .../internal/fixtures/listZones-request.json | 9 + .../internal/fixtures/listZones.json | 17 ++ .../fixtures/searchDNSHosts-request.json | 9 + .../internal/fixtures/searchDNSHosts.json | 23 ++ .../fixtures/updateDNSHost-request.json | 17 ++ .../internal/fixtures/updateDNSHost.json | 4 + providers/dns/namesurfer/internal/types.go | 72 ++++++ providers/dns/namesurfer/namesurfer.go | 214 +++++++++++++++++ providers/dns/namesurfer/namesurfer.toml | 28 +++ providers/dns/namesurfer/namesurfer_test.go | 174 ++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 20 files changed, 1128 insertions(+), 32 deletions(-) create mode 100644 docs/content/dns/zz_gen_namesurfer.md create mode 100644 providers/dns/namesurfer/internal/client.go create mode 100644 providers/dns/namesurfer/internal/client_test.go create mode 100644 providers/dns/namesurfer/internal/fixtures/addDNSRecord-request.json create mode 100644 providers/dns/namesurfer/internal/fixtures/addDNSRecord.json create mode 100644 providers/dns/namesurfer/internal/fixtures/error.json create mode 100644 providers/dns/namesurfer/internal/fixtures/listZones-request.json create mode 100644 providers/dns/namesurfer/internal/fixtures/listZones.json create mode 100644 providers/dns/namesurfer/internal/fixtures/searchDNSHosts-request.json create mode 100644 providers/dns/namesurfer/internal/fixtures/searchDNSHosts.json create mode 100644 providers/dns/namesurfer/internal/fixtures/updateDNSHost-request.json create mode 100644 providers/dns/namesurfer/internal/fixtures/updateDNSHost.json create mode 100644 providers/dns/namesurfer/internal/types.go create mode 100644 providers/dns/namesurfer/namesurfer.go create mode 100644 providers/dns/namesurfer/namesurfer.toml create mode 100644 providers/dns/namesurfer/namesurfer_test.go diff --git a/README.md b/README.md index 07e4f7dd6..105ea53aa 100644 --- a/README.md +++ b/README.md @@ -141,160 +141,160 @@ If your DNS provider is not supported, please open an [issue](https://github.com F5 XC freemyip.com + FusionLayer NameSurfer G-Core Gandi Gandi Live DNS (v5) - Gigahost.no + Gigahost.no Glesys Go Daddy Google Cloud - Google Domains + Google Domains Gravity Hetzner Hosting.de - Hosting.nl + Hosting.nl Hostinger Hosttech HTTP request - http.net + http.net Huawei Cloud Hurricane Electric DNS HyperOne - IBM Cloud (SoftLayer) + IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox Infomaniak - Internet Initiative Japan + Internet Initiative Japan Internet.bs INWX Ionos - Ionos Cloud + Ionos Cloud IPv64 ISPConfig 3 ISPConfig 3 - Dynamic DNS (DDNS) Module - iwantmyname (Deprecated) + iwantmyname (Deprecated) JD Cloud Joker Joohoi's ACME-DNS - KeyHelp + KeyHelp Liara Lima-City Linode (v4) - Liquid Web + Liquid Web Loopia LuaDNS Mail-in-a-Box - ManageEngine CloudDNS + ManageEngine CloudDNS Manual Metaname Metaregistrar - mijn.host + mijn.host Mittwald myaddr.{tools,dev,io} MyDNS.jp - MythicBeasts + MythicBeasts Name.com Namecheap Namesilo - NearlyFreeSpeech.NET + NearlyFreeSpeech.NET Neodigit Netcup Netlify - Nicmanager + Nicmanager NIFCloud Njalla Nodion - NS1 + NS1 Octenium Open Telekom Cloud Oracle Cloud - OVH + OVH plesk.com Porkbun PowerDNS - Rackspace + Rackspace Rain Yun/雨云 RcodeZero reg.ru - Regfish + Regfish RFC2136 RimuHosting RU CENTER - Sakura Cloud + Sakura Cloud Scaleway Selectel Selectel v2 - SelfHost.(de|eu) + SelfHost.(de|eu) Servercow Shellrent Simply.com - Sonic + Sonic Spaceship Stackpath Syse - Technitium + Technitium Tencent Cloud DNS Tencent EdgeOne Timeweb Cloud - TodayNIC/时代互联 + TodayNIC/时代互联 TransIP UKFast SafeDNS Ultradns - United-Domains + United-Domains Variomedia VegaDNS Vercel - Versio.[nl|eu|uk] + Versio.[nl|eu|uk] VinylDNS Virtualname VK Cloud - Volcano Engine/火山引擎 + Volcano Engine/火山引擎 Vscale Vultr webnames.ca - webnames.ru + webnames.ru Websupport WEDOS West.cn/西部数码 - Yandex 360 + Yandex 360 Yandex Cloud Yandex PDD Zone.ee - ZoneEdit + ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 357834a3c..cdee65371 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -131,6 +131,7 @@ func allDNSCodes() string { "namecheap", "namedotcom", "namesilo", + "namesurfer", "nearlyfreespeech", "neodigit", "netcup", @@ -2742,6 +2743,30 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/namesilo`) + case "namesurfer": + // generated from: providers/dns/namesurfer/namesurfer.toml + ew.writeln(`Configuration for FusionLayer NameSurfer.`) + ew.writeln(`Code: 'namesurfer'`) + ew.writeln(`Since: 'v4.32.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "NAMESURFER_API_KEY": API key name`) + ew.writeln(` - "NAMESURFER_API_SECRET": API secret`) + ew.writeln(` - "NAMESURFER_BASE_URL": The base URL of NameSurfer API (jsonrpc10) endpoint URL (e.g., https://foo.example.com:8443/API/NSService_10)`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "NAMESURFER_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "NAMESURFER_INSECURE_SKIP_VERIFY": Whether to verify the API certificate`) + ew.writeln(` - "NAMESURFER_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "NAMESURFER_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 120)`) + ew.writeln(` - "NAMESURFER_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)`) + ew.writeln(` - "NAMESURFER_VIEW": DNS view name (optional, default: empty string)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/namesurfer`) + case "nearlyfreespeech": // generated from: providers/dns/nearlyfreespeech/nearlyfreespeech.toml ew.writeln(`Configuration for NearlyFreeSpeech.NET.`) diff --git a/docs/content/dns/zz_gen_namesurfer.md b/docs/content/dns/zz_gen_namesurfer.md new file mode 100644 index 000000000..9a2802d0e --- /dev/null +++ b/docs/content/dns/zz_gen_namesurfer.md @@ -0,0 +1,73 @@ +--- +title: "FusionLayer NameSurfer" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: namesurfer +dnsprovider: + since: "v4.32.0" + code: "namesurfer" + url: "https://www.fusionlayer.com/" +--- + + + + + + +Configuration for [FusionLayer NameSurfer](https://www.fusionlayer.com/). + + + + +- Code: `namesurfer` +- Since: v4.32.0 + + +Here is an example bash command using the FusionLayer NameSurfer provider: + +```bash +NAMESURFER_BASE_URL=https://foo.example.com:8443/API/NSService_10 \ +NAMESURFER_API_KEY=xxx \ +NAMESURFER_API_SECRET=yyy \ +lego --dns namesurfer -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `NAMESURFER_API_KEY` | API key name | +| `NAMESURFER_API_SECRET` | API secret | +| `NAMESURFER_BASE_URL` | The base URL of NameSurfer API (jsonrpc10) endpoint URL (e.g., https://foo.example.com:8443/API/NSService_10) | + +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 | +|--------------------------------|-------------| +| `NAMESURFER_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `NAMESURFER_INSECURE_SKIP_VERIFY` | Whether to verify the API certificate | +| `NAMESURFER_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `NAMESURFER_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 120) | +| `NAMESURFER_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 300) | +| `NAMESURFER_VIEW` | DNS view name (optional, default: empty string) | + +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://web.archive.org/web/20260213170737/http://95.128.3.201:8053/API/NSService_10) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index e31633567..759b8e84f 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bluecatv2, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, ddnss, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bluecatv2, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, ddnss, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, namesurfer, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/namesurfer/internal/client.go b/providers/dns/namesurfer/internal/client.go new file mode 100644 index 000000000..e40a7988c --- /dev/null +++ b/providers/dns/namesurfer/internal/client.go @@ -0,0 +1,226 @@ +package internal + +import ( + "bytes" + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "slices" + "strconv" + "strings" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +type Client struct { + apiKey string + apiSecret string + + BaseURL *url.URL + HTTPClient *http.Client +} + +func NewClient(baseURL, apiKey, apiSecret string) (*Client, error) { + if apiKey == "" || apiSecret == "" { + return nil, errors.New("credentials missing") + } + + if baseURL == "" { + return nil, errors.New("base URL missing") + } + + apiEndpoint, err := url.Parse(baseURL) + if err != nil { + return nil, err + } + + return &Client{ + apiKey: apiKey, + apiSecret: apiSecret, + BaseURL: apiEndpoint.JoinPath("jsonrpc10"), + HTTPClient: &http.Client{ + Timeout: 5 * time.Second, + }, + }, nil +} + +// AddDNSRecord adds a DNS record. +// http://95.128.3.201:8053/API/NSService_10#addDNSRecord +func (d *Client) AddDNSRecord(ctx context.Context, zoneName, viewName string, record DNSNode) error { + digest := d.computeDigest( + zoneName, + viewName, + record.Name, + record.Type, + strconv.Itoa(record.TTL), + record.Data, + ) + + // JSON-RPC 1.0 requires positional parameters array + params := []any{ + digest, + zoneName, + viewName, + record, + } + + var ok bool + + err := d.doRequest(ctx, "addDNSRecord", params, &ok) + if err != nil { + return err + } + + if !ok { + return errors.New("addDNSRecord failed") + } + + return nil +} + +// UpdateDNSHost updates a DNS host record. +// Passing an empty newNode removes the oldNode. +// http://95.128.3.201:8053/API/NSService_10#updateDNSHost +func (d *Client) UpdateDNSHost(ctx context.Context, zoneName, viewName string, oldNode, newNode DNSNode) error { + digest := d.computeDigest(zoneName, viewName) + + // JSON-RPC 1.0 requires positional parameters array + params := []any{ + digest, + zoneName, + viewName, + oldNode, + newNode, + } + + var ok bool + + err := d.doRequest(ctx, "updateDNSHost", params, &ok) + if err != nil { + return err + } + + if !ok { + return errors.New("updateDNSHost failed") + } + + return nil +} + +// SearchDNSHosts searches for DNS host records. +// http://95.128.3.201:8053/API/NSService_10#searchDNSHosts +func (d *Client) SearchDNSHosts(ctx context.Context, pattern string) ([]DNSNode, error) { + digest := d.computeDigest(pattern) + + // JSON-RPC 1.0 requires positional parameters array + params := []any{ + digest, + pattern, + } + + var nodes []DNSNode + + err := d.doRequest(ctx, "searchDNSHosts", params, &nodes) + if err != nil { + return nil, err + } + + return nodes, nil +} + +// ListZones lists DNS zones. +// http://95.128.3.201:8053/API/NSService_10#listZones +func (d *Client) ListZones(ctx context.Context, mode string) ([]DNSZone, error) { + digest := d.computeDigest() + + // JSON-RPC 1.0 requires positional parameters array + params := []any{ + digest, + mode, + } + + var zones []DNSZone + + err := d.doRequest(ctx, "listZones", params, &zones) + if err != nil { + return nil, err + } + + return zones, nil +} + +func (d *Client) doRequest(ctx context.Context, method string, params []any, result any) error { + payload := APIRequest{ + ID: 1, + Method: method, + Params: slices.Concat([]any{d.apiKey}, params), + } + + buf := new(bytes.Buffer) + + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return fmt.Errorf("failed to create request JSON body: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, d.BaseURL.String(), buf) + if err != nil { + return fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + + resp, err := d.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + if resp.StatusCode/100 != 2 { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + var rpcResp APIResponse + + err = json.Unmarshal(raw, &rpcResp) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + if rpcResp.Error != nil { + return rpcResp.Error + } + + err = json.Unmarshal(rpcResp.Result, result) + if err != nil { + return fmt.Errorf("unable to unmarshal response: %w: %s", err, rpcResp.Result) + } + + return nil +} + +func (d *Client) computeDigest(parts ...string) string { + params := []string{d.apiKey} + params = append(params, parts...) + params = append(params, d.apiSecret) + + mac := hmac.New(sha256.New, []byte(d.apiSecret)) + mac.Write([]byte(strings.Join(params, "&"))) + + return hex.EncodeToString(mac.Sum(nil)) +} diff --git a/providers/dns/namesurfer/internal/client_test.go b/providers/dns/namesurfer/internal/client_test.go new file mode 100644 index 000000000..9e8f917bc --- /dev/null +++ b/providers/dns/namesurfer/internal/client_test.go @@ -0,0 +1,158 @@ +package internal + +import ( + "net/http/httptest" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(server.URL, "user", "secret") + if err != nil { + return nil, err + } + + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(), + ) +} + +func TestClient_AddDNSRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /jsonrpc10", + servermock.ResponseFromFixture("addDNSRecord.json"), + servermock.CheckRequestJSONBodyFromFixture("addDNSRecord-request.json"), + ). + Build(t) + + record := DNSNode{ + Name: "_acme-challenge", + Type: "TXT", + Data: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 300, + } + + err := client.AddDNSRecord(t.Context(), "example.com", "viewA", record) + require.NoError(t, err) +} + +func TestClient_AddDNSRecord_error(t *testing.T) { + client := mockBuilder(). + Route("POST /jsonrpc10", + servermock.ResponseFromFixture("error.json"), + ). + Build(t) + + record := DNSNode{ + Name: "_acme-challenge", + Type: "TXT", + Data: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 300, + } + + err := client.AddDNSRecord(t.Context(), "example.com", "viewA", record) + require.EqualError(t, err, "code: Server.Keyfailure, "+ + "filename: service, line: 13, "+ + "message: Unknown keyname user, "+ + `detail: Traceback (most recent call last): File "/usr/local/namesurfer/python/lib/python2.6/site-packages/ladon/server/dispatcher.py", line 159, in dispatch_request result = self.call_method(method,req_dict,tc,export_dict,log_line) File "/usr/local/namesurfer/python/lib/python2.6/site-packages/ladon/server/dispatcher.py", line 96, in call_method result = getattr(service_class_instance,req_dict['methodname'])(*args) File "/usr/local/namesurfer/python/lib/python2.6/site-packages/ladon/ladonizer/decorator.py", line 77, in injector res = f(*args,**kw) File "/usr/local/namesurfer/webui2/webui/service/service10/NSService_10.py", line 502, in addDNSRecord key = validate_key(keyname, digest, [zonename, viewname, record.name, record.type, str(record.ttl), record.data]) File "/usr/local/namesurfer/webui2/webui/service/base/implementation.py", line 63, in validate_key raise ApiFault('Server.Keyfailure', 'Unknown keyname %s' % keyname) ApiFault: service(13): Unknown keyname user `) +} + +func TestClient_UpdateDNSHost(t *testing.T) { + client := mockBuilder(). + Route("POST /jsonrpc10", + servermock.ResponseFromFixture("updateDNSHost.json"), + servermock.CheckRequestJSONBodyFromFixture("updateDNSHost-request.json"), + ). + Build(t) + + record := DNSNode{ + Name: "_acme-challenge", + Type: "TXT", + Data: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 300, + } + + err := client.UpdateDNSHost(t.Context(), "example.com", "viewA", record, DNSNode{}) + require.NoError(t, err) +} + +func TestClient_SearchDNSHosts(t *testing.T) { + client := mockBuilder(). + Route("POST /jsonrpc10", + servermock.ResponseFromFixture("searchDNSHosts.json"), + servermock.CheckRequestJSONBodyFromFixture("searchDNSHosts-request.json"), + ). + Build(t) + + records, err := client.SearchDNSHosts(t.Context(), "value") + require.NoError(t, err) + + expected := []DNSNode{ + {Name: "foo", Type: "TXT", Data: "xxx", TTL: 300}, + {Name: "_acme-challenge", Type: "TXT", Data: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", TTL: 300}, + {Name: "bar", Type: "A", Data: "yyy", TTL: 300}, + } + + assert.Equal(t, expected, records) +} + +func TestClient_ListZones(t *testing.T) { + client := mockBuilder(). + Route("POST /jsonrpc10", + servermock.ResponseFromFixture("listZones.json"), + servermock.CheckRequestJSONBodyFromFixture("listZones-request.json"), + ). + Build(t) + + zones, err := client.ListZones(t.Context(), "value") + require.NoError(t, err) + + expected := []DNSZone{ + {Name: "example.com", View: "viewA"}, + {Name: "example.org", View: "viewB"}, + {Name: "example.net", View: "viewC"}, + } + + assert.Equal(t, expected, zones) +} + +func TestClient_computeDigest(t *testing.T) { + client, err := NewClient("https://test.example.com", "testkey", "testsecret") + require.NoError(t, err) + + testCases := []struct { + desc string + parts []string + expected string + }{ + { + desc: "no parts", + parts: []string{}, + expected: "99b5dcdc19bfc0ce2af3fe848f4bcb6f7beb352e9599e8ba50544d86de567282", + }, + { + desc: "parts", + parts: []string{"zone.example.com", "default"}, + expected: "94efef76383889b1ae620582a25d1c3aa9bd9ba9ac4bdccdf4aefbc3ae6e8329", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + digest := client.computeDigest(test.parts...) + + assert.Equal(t, test.expected, digest) + }) + } +} diff --git a/providers/dns/namesurfer/internal/fixtures/addDNSRecord-request.json b/providers/dns/namesurfer/internal/fixtures/addDNSRecord-request.json new file mode 100644 index 000000000..660109aae --- /dev/null +++ b/providers/dns/namesurfer/internal/fixtures/addDNSRecord-request.json @@ -0,0 +1,16 @@ +{ + "id": 1, + "method": "addDNSRecord", + "params": [ + "user", + "4fcc5fa29531709b0381c8debea127a6a26e71cb9491727876819cf5805c4990", + "example.com", + "viewA", + { + "name": "_acme-challenge", + "type": "TXT", + "data": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "ttl": 300 + } + ] +} diff --git a/providers/dns/namesurfer/internal/fixtures/addDNSRecord.json b/providers/dns/namesurfer/internal/fixtures/addDNSRecord.json new file mode 100644 index 000000000..f41779e30 --- /dev/null +++ b/providers/dns/namesurfer/internal/fixtures/addDNSRecord.json @@ -0,0 +1,4 @@ +{ + "id": 1, + "result": true +} diff --git a/providers/dns/namesurfer/internal/fixtures/error.json b/providers/dns/namesurfer/internal/fixtures/error.json new file mode 100644 index 000000000..8ddf8df25 --- /dev/null +++ b/providers/dns/namesurfer/internal/fixtures/error.json @@ -0,0 +1,24 @@ +{ + "result": null, + "error": { + "filename": "service", + "lineno": 13, + "code": "Server.Keyfailure", + "string": "Unknown keyname user", + "detail": [ + "Traceback (most recent call last):", + " File \"/usr/local/namesurfer/python/lib/python2.6/site-packages/ladon/server/dispatcher.py\", line 159, in dispatch_request", + " result = self.call_method(method,req_dict,tc,export_dict,log_line)", + " File \"/usr/local/namesurfer/python/lib/python2.6/site-packages/ladon/server/dispatcher.py\", line 96, in call_method", + " result = getattr(service_class_instance,req_dict['methodname'])(*args)", + " File \"/usr/local/namesurfer/python/lib/python2.6/site-packages/ladon/ladonizer/decorator.py\", line 77, in injector", + " res = f(*args,**kw)", + " File \"/usr/local/namesurfer/webui2/webui/service/service10/NSService_10.py\", line 502, in addDNSRecord", + " key = validate_key(keyname, digest, [zonename, viewname, record.name, record.type, str(record.ttl), record.data])", + " File \"/usr/local/namesurfer/webui2/webui/service/base/implementation.py\", line 63, in validate_key", + " raise ApiFault('Server.Keyfailure', 'Unknown keyname %s' % keyname)", + "ApiFault: service(13): Unknown keyname user", + "" + ] + } +} diff --git a/providers/dns/namesurfer/internal/fixtures/listZones-request.json b/providers/dns/namesurfer/internal/fixtures/listZones-request.json new file mode 100644 index 000000000..06689de7a --- /dev/null +++ b/providers/dns/namesurfer/internal/fixtures/listZones-request.json @@ -0,0 +1,9 @@ +{ + "id": 1, + "method": "listZones", + "params": [ + "user", + "2739461ea1a3dc51302993f724f40228409c53b78025d8d7b1d7bba3c1bf2d66", + "value" + ] +} diff --git a/providers/dns/namesurfer/internal/fixtures/listZones.json b/providers/dns/namesurfer/internal/fixtures/listZones.json new file mode 100644 index 000000000..37fa2053b --- /dev/null +++ b/providers/dns/namesurfer/internal/fixtures/listZones.json @@ -0,0 +1,17 @@ +{ + "id": 1, + "result": [ + { + "name": "example.com", + "view": "viewA" + }, + { + "name": "example.org", + "view": "viewB" + }, + { + "name": "example.net", + "view": "viewC" + } + ] +} diff --git a/providers/dns/namesurfer/internal/fixtures/searchDNSHosts-request.json b/providers/dns/namesurfer/internal/fixtures/searchDNSHosts-request.json new file mode 100644 index 000000000..4a88340e2 --- /dev/null +++ b/providers/dns/namesurfer/internal/fixtures/searchDNSHosts-request.json @@ -0,0 +1,9 @@ +{ + "id": 1, + "method": "searchDNSHosts", + "params": [ + "user", + "02cf1a2f6e124507d16738d595f583932185313fc96afc2d8404960acaec29b4", + "value" + ] +} diff --git a/providers/dns/namesurfer/internal/fixtures/searchDNSHosts.json b/providers/dns/namesurfer/internal/fixtures/searchDNSHosts.json new file mode 100644 index 000000000..822459148 --- /dev/null +++ b/providers/dns/namesurfer/internal/fixtures/searchDNSHosts.json @@ -0,0 +1,23 @@ +{ + "id": 1, + "result": [ + { + "name": "foo", + "type": "TXT", + "data": "xxx", + "ttl": 300 + }, + { + "name": "_acme-challenge", + "type": "TXT", + "data": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "ttl": 300 + }, + { + "name": "bar", + "type": "A", + "data": "yyy", + "ttl": 300 + } + ] +} diff --git a/providers/dns/namesurfer/internal/fixtures/updateDNSHost-request.json b/providers/dns/namesurfer/internal/fixtures/updateDNSHost-request.json new file mode 100644 index 000000000..c99218ec5 --- /dev/null +++ b/providers/dns/namesurfer/internal/fixtures/updateDNSHost-request.json @@ -0,0 +1,17 @@ +{ + "id": 1, + "method": "updateDNSHost", + "params": [ + "user", + "510e63288ac874c1d5ba313a9411591daa346e5621fb0153263adc278794e378", + "example.com", + "viewA", + { + "name": "_acme-challenge", + "type": "TXT", + "data": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "ttl": 300 + }, + {} + ] +} diff --git a/providers/dns/namesurfer/internal/fixtures/updateDNSHost.json b/providers/dns/namesurfer/internal/fixtures/updateDNSHost.json new file mode 100644 index 000000000..f41779e30 --- /dev/null +++ b/providers/dns/namesurfer/internal/fixtures/updateDNSHost.json @@ -0,0 +1,4 @@ +{ + "id": 1, + "result": true +} diff --git a/providers/dns/namesurfer/internal/types.go b/providers/dns/namesurfer/internal/types.go new file mode 100644 index 000000000..f95593745 --- /dev/null +++ b/providers/dns/namesurfer/internal/types.go @@ -0,0 +1,72 @@ +package internal + +import ( + "encoding/json" + "fmt" + "strings" +) + +// DNSNode represents a DNS record. +// http://95.128.3.201:8053/API/NSService_10#DNSNode +type DNSNode struct { + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Data string `json:"data,omitempty"` + TTL int `json:"ttl,omitempty"` +} + +// DNSZone represents a DNS zone. +// http://95.128.3.201:8053/API/NSService_10#DNSZone +type DNSZone struct { + Name string `json:"name,omitempty"` + View string `json:"view,omitempty"` +} + +// APIRequest represents a JSON-RPC request. +// https://www.jsonrpc.org/specification_v1#a1.1Requestmethodinvocation +type APIRequest struct { + ID any `json:"id"` // Can be int or string depending on API + Method string `json:"method"` + Params []any `json:"params"` +} + +// APIResponse represents a JSON-RPC response. +// https://www.jsonrpc.org/specification_v1#a1.2Response +type APIResponse struct { + ID any `json:"id"` // Can be int or string depending on API + Result json.RawMessage `json:"result"` + Error *APIError `json:"error"` +} + +// APIError represents an error. +type APIError struct { + Code any `json:"code"` // Can be int or string depending on API + Filename string `json:"filename"` + LineNumber int `json:"lineno"` + Message string `json:"string"` + Detail []string `json:"detail"` +} + +func (e *APIError) Error() string { + var msg strings.Builder + + msg.WriteString(fmt.Sprintf("code: %v", e.Code)) + + if e.Filename != "" { + msg.WriteString(fmt.Sprintf(", filename: %s", e.Filename)) + } + + if e.LineNumber > 0 { + msg.WriteString(fmt.Sprintf(", line: %d", e.LineNumber)) + } + + if e.Message != "" { + msg.WriteString(fmt.Sprintf(", message: %s", e.Message)) + } + + if len(e.Detail) > 0 { + msg.WriteString(fmt.Sprintf(", detail: %v", strings.Join(e.Detail, " "))) + } + + return msg.String() +} diff --git a/providers/dns/namesurfer/namesurfer.go b/providers/dns/namesurfer/namesurfer.go new file mode 100644 index 000000000..6b7f48402 --- /dev/null +++ b/providers/dns/namesurfer/namesurfer.go @@ -0,0 +1,214 @@ +// Package namesurfer implements a DNS provider for solving the DNS-01 challenge using FusionLayer NameSurfer API. +package namesurfer + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net/http" + "strings" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/namesurfer/internal" +) + +// Environment variables names. +const ( + envNamespace = "NAMESURFER_" + + EnvBaseURL = envNamespace + "BASE_URL" + EnvAPIKey = envNamespace + "API_KEY" + EnvAPISecret = envNamespace + "API_SECRET" + EnvView = envNamespace + "VIEW" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" + EnvInsecureSkipVerify = envNamespace + "INSECURE_SKIP_VERIFY" +) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + BaseURL string + APIKey string + APISecret string + View 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, 300), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), + 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 *internal.Client + + zones map[string]string + zonesMu sync.Mutex +} + +// NewDNSProvider returns a DNSProvider instance configured for FusionLayer NameSurfer. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvBaseURL, EnvAPIKey, EnvAPISecret) + if err != nil { + return nil, fmt.Errorf("namesurfer: %w", err) + } + + config := NewDefaultConfig() + config.BaseURL = values[EnvBaseURL] + config.APIKey = values[EnvAPIKey] + config.APISecret = values[EnvAPISecret] + config.View = env.GetOrDefaultString(EnvView, "") + + if env.GetOrDefaultBool(EnvInsecureSkipVerify, false) { + config.HTTPClient.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + } + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for FusionLayer NameSurfer. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("namesurfer: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.BaseURL, config.APIKey, config.APISecret) + if err != nil { + return nil, fmt.Errorf("namesurfer: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + zones: make(map[string]string), + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + zone, err := d.findZone(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("namesurfer: %w", err) + } + + d.zonesMu.Lock() + d.zones[token] = zone + d.zonesMu.Unlock() + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone) + if err != nil { + return fmt.Errorf("namesurfer: %w", err) + } + + record := internal.DNSNode{ + Name: subDomain, + Type: "TXT", + TTL: d.config.TTL, + Data: info.Value, + } + + err = d.client.AddDNSRecord(ctx, zone, d.config.View, record) + if err != nil { + return fmt.Errorf("namesurfer: add DNS record: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + d.zonesMu.Lock() + zone, ok := d.zones[token] + d.zonesMu.Unlock() + + if !ok { + return fmt.Errorf("namesurfer: unknown zone for '%s'", info.EffectiveFQDN) + } + + d.zonesMu.Lock() + delete(d.zones, token) + d.zonesMu.Unlock() + + existing, err := d.client.SearchDNSHosts(ctx, dns01.UnFqdn(info.EffectiveFQDN)) + if err != nil { + return fmt.Errorf("namesurfer: search DNS hosts: %w", err) + } + + for _, node := range existing { + if node.Type != "TXT" || node.Data != info.Value { + continue + } + + err = d.client.UpdateDNSHost(ctx, zone, d.config.View, node, internal.DNSNode{}) + if err != nil { + return fmt.Errorf("namesurfer: update DNS host: %w", err) + } + } + + return nil +} + +// Timeout returns the timeout and interval to use when checking for DNS propagation. +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +func (d *DNSProvider) findZone(ctx context.Context, fqdn string) (string, error) { + zones, err := d.client.ListZones(ctx, "forward") + if err != nil { + return "", fmt.Errorf("list zones: %w", err) + } + + domain := dns01.UnFqdn(fqdn) + + var zoneName string + + for _, zone := range zones { + if strings.HasSuffix(domain, zone.Name) && len(zone.Name) > len(zoneName) { + zoneName = zone.Name + } + } + + if zoneName == "" { + return "", fmt.Errorf("no zone found for %s", fqdn) + } + + return zoneName, nil +} diff --git a/providers/dns/namesurfer/namesurfer.toml b/providers/dns/namesurfer/namesurfer.toml new file mode 100644 index 000000000..fd914ec0c --- /dev/null +++ b/providers/dns/namesurfer/namesurfer.toml @@ -0,0 +1,28 @@ +Name = "FusionLayer NameSurfer" +Description = '''''' +URL = "https://www.fusionlayer.com/" +Code = "namesurfer" +Since = "v4.32.0" + +Example = ''' +NAMESURFER_BASE_URL=https://foo.example.com:8443/API/NSService_10 \ +NAMESURFER_API_KEY=xxx \ +NAMESURFER_API_SECRET=yyy \ +lego --dns namesurfer -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + NAMESURFER_BASE_URL = "The base URL of NameSurfer API (jsonrpc10) endpoint URL (e.g., https://foo.example.com:8443/API/NSService_10)" + NAMESURFER_API_KEY = "API key name" + NAMESURFER_API_SECRET = "API secret" + [Configuration.Additional] + NAMESURFER_VIEW = "DNS view name (optional, default: empty string)" + NAMESURFER_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + NAMESURFER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)" + NAMESURFER_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 300)" + NAMESURFER_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + NAMESURFER_INSECURE_SKIP_VERIFY = "Whether to verify the API certificate" + +[Links] + API = "https://web.archive.org/web/20260213170737/http://95.128.3.201:8053/API/NSService_10" diff --git a/providers/dns/namesurfer/namesurfer_test.go b/providers/dns/namesurfer/namesurfer_test.go new file mode 100644 index 000000000..ce3aa37af --- /dev/null +++ b/providers/dns/namesurfer/namesurfer_test.go @@ -0,0 +1,174 @@ +package namesurfer + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest( + EnvBaseURL, + EnvAPIKey, + EnvAPISecret, + EnvView, +).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvBaseURL: "https://example.com", + EnvAPIKey: "user", + EnvAPISecret: "secret", + }, + }, + { + desc: "missing base URL", + envVars: map[string]string{ + EnvBaseURL: "", + EnvAPIKey: "user", + EnvAPISecret: "secret", + }, + expected: "namesurfer: some credentials information are missing: NAMESURFER_BASE_URL", + }, + { + desc: "missing API key", + envVars: map[string]string{ + EnvBaseURL: "https://example.com", + EnvAPIKey: "", + EnvAPISecret: "secret", + }, + expected: "namesurfer: some credentials information are missing: NAMESURFER_API_KEY", + }, + { + desc: "missing API secret", + envVars: map[string]string{ + EnvBaseURL: "https://example.com", + EnvAPIKey: "user", + EnvAPISecret: "", + }, + expected: "namesurfer: some credentials information are missing: NAMESURFER_API_SECRET", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "namesurfer: some credentials information are missing: NAMESURFER_BASE_URL,NAMESURFER_API_KEY,NAMESURFER_API_SECRET", + }, + } + + 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 + baseURL string + apiKey string + apiSecret string + expected string + }{ + { + desc: "success", + baseURL: "https://example.com", + apiKey: "user", + apiSecret: "secret", + }, + { + desc: "missing base URL", + apiKey: "user", + apiSecret: "secret", + expected: "namesurfer: base URL missing", + }, + { + desc: "missing API key", + baseURL: "https://example.com", + apiSecret: "secret", + expected: "namesurfer: credentials missing", + }, + { + desc: "missing API secret", + baseURL: "https://example.com", + apiKey: "user", + expected: "namesurfer: credentials missing", + }, + { + desc: "missing credentials", + expected: "namesurfer: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.BaseURL = test.baseURL + config.APIKey = test.apiKey + config.APISecret = test.apiSecret + + 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 ae41f6a20..10fda2df1 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -125,6 +125,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/namecheap" "github.com/go-acme/lego/v4/providers/dns/namedotcom" "github.com/go-acme/lego/v4/providers/dns/namesilo" + "github.com/go-acme/lego/v4/providers/dns/namesurfer" "github.com/go-acme/lego/v4/providers/dns/nearlyfreespeech" "github.com/go-acme/lego/v4/providers/dns/neodigit" "github.com/go-acme/lego/v4/providers/dns/netcup" @@ -434,6 +435,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return namedotcom.NewDNSProvider() case "namesilo": return namesilo.NewDNSProvider() + case "namesurfer": + return namesurfer.NewDNSProvider() case "nearlyfreespeech": return nearlyfreespeech.NewDNSProvider() case "neodigit": From c06f378f0ed626fe9f8edddfbe8647d50f4f36f3 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 15 Feb 2026 10:42:48 +0100 Subject: [PATCH 283/298] namesurfer: fix updateDNSHost (#2854) --- .../internal/fixtures/updateDNSHost-request.json | 7 ++++++- providers/dns/namesurfer/internal/types.go | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/providers/dns/namesurfer/internal/fixtures/updateDNSHost-request.json b/providers/dns/namesurfer/internal/fixtures/updateDNSHost-request.json index c99218ec5..494de20c6 100644 --- a/providers/dns/namesurfer/internal/fixtures/updateDNSHost-request.json +++ b/providers/dns/namesurfer/internal/fixtures/updateDNSHost-request.json @@ -12,6 +12,11 @@ "data": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", "ttl": 300 }, - {} + { + "name": "", + "type": "", + "data": "", + "ttl": 0 + } ] } diff --git a/providers/dns/namesurfer/internal/types.go b/providers/dns/namesurfer/internal/types.go index f95593745..7b3f08013 100644 --- a/providers/dns/namesurfer/internal/types.go +++ b/providers/dns/namesurfer/internal/types.go @@ -9,10 +9,10 @@ import ( // DNSNode represents a DNS record. // http://95.128.3.201:8053/API/NSService_10#DNSNode type DNSNode struct { - Name string `json:"name,omitempty"` - Type string `json:"type,omitempty"` - Data string `json:"data,omitempty"` - TTL int `json:"ttl,omitempty"` + Name string `json:"name"` + Type string `json:"type"` + Data string `json:"data"` + TTL int `json:"ttl"` } // DNSZone represents a DNS zone. From 94e3bfb96af1e16a7defa7795e33d5185ed20e0f Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Tue, 17 Feb 2026 23:34:27 +0100 Subject: [PATCH 284/298] chore: update linter (#2857) --- .github/workflows/pr.yml | 2 +- acme/errors.go | 10 +++++----- providers/dns/binarylane/internal/types.go | 6 +++--- providers/dns/cloudflare/internal/types.go | 6 +++--- providers/dns/dnsexit/internal/types.go | 6 +++--- providers/dns/godaddy/internal/types.go | 4 ++-- providers/dns/gravity/internal/types.go | 8 ++++---- .../dns/hetzner/internal/hetznerv1/internal/types.go | 10 +++++----- providers/dns/hostinger/internal/types.go | 6 +++--- providers/dns/hosttech/internal/types.go | 6 +++--- providers/dns/mittwald/internal/types.go | 8 ++++---- providers/dns/namesurfer/internal/types.go | 12 ++++++------ 12 files changed, 42 insertions(+), 42 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 9c845ea62..33ca106cc 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: stable - GOLANGCI_LINT_VERSION: v2.9.0 + GOLANGCI_LINT_VERSION: v2.10 HUGO_VERSION: 0.148.2 CGO_ENABLED: 0 LEGO_E2E_TESTS: CI diff --git a/acme/errors.go b/acme/errors.go index be4721c9d..cd447d7b4 100644 --- a/acme/errors.go +++ b/acme/errors.go @@ -29,18 +29,18 @@ type ProblemDetails struct { } func (p *ProblemDetails) Error() string { - var msg strings.Builder + msg := new(strings.Builder) - msg.WriteString(fmt.Sprintf("acme: error: %d", p.HTTPStatus)) + _, _ = fmt.Fprintf(msg, "acme: error: %d", p.HTTPStatus) if p.Method != "" || p.URL != "" { - msg.WriteString(fmt.Sprintf(" :: %s :: %s", p.Method, p.URL)) + _, _ = fmt.Fprintf(msg, " :: %s :: %s", p.Method, p.URL) } - msg.WriteString(fmt.Sprintf(" :: %s :: %s", p.Type, p.Detail)) + _, _ = fmt.Fprintf(msg, " :: %s :: %s", p.Type, p.Detail) for _, sub := range p.SubProblems { - msg.WriteString(fmt.Sprintf(", problem: %q :: %s", sub.Type, sub.Detail)) + _, _ = fmt.Fprintf(msg, ", problem: %q :: %s", sub.Type, sub.Detail) } if p.Instance != "" { diff --git a/providers/dns/binarylane/internal/types.go b/providers/dns/binarylane/internal/types.go index 987e5c356..06d4be5c0 100644 --- a/providers/dns/binarylane/internal/types.go +++ b/providers/dns/binarylane/internal/types.go @@ -15,12 +15,12 @@ type APIError struct { } func (a *APIError) Error() string { - var msg strings.Builder + msg := new(strings.Builder) - msg.WriteString(fmt.Sprintf("%d: %s: %s: %s: %s", a.Status, a.Type, a.Title, a.Detail, a.Instance)) + _, _ = fmt.Fprintf(msg, "%d: %s: %s: %s: %s", a.Status, a.Type, a.Title, a.Detail, a.Instance) for s, values := range a.Errors { - msg.WriteString(fmt.Sprintf(": %s: %s", s, strings.Join(values, ", "))) + _, _ = fmt.Fprintf(msg, ": %s: %s", s, strings.Join(values, ", ")) } return msg.String() diff --git a/providers/dns/cloudflare/internal/types.go b/providers/dns/cloudflare/internal/types.go index 4a7f9e031..50a7bbbf9 100644 --- a/providers/dns/cloudflare/internal/types.go +++ b/providers/dns/cloudflare/internal/types.go @@ -42,13 +42,13 @@ type ErrorChain struct { type Errors []Message func (e Errors) Error() string { - var msg strings.Builder + msg := new(strings.Builder) for _, item := range e { - msg.WriteString(fmt.Sprintf("%d: %s", item.Code, item.Message)) + _, _ = fmt.Fprintf(msg, "%d: %s", item.Code, item.Message) for _, link := range item.ErrorChain { - msg.WriteString(fmt.Sprintf("; %d: %s", link.Code, link.Message)) + _, _ = fmt.Fprintf(msg, "; %d: %s", link.Code, link.Message) } } diff --git a/providers/dns/dnsexit/internal/types.go b/providers/dns/dnsexit/internal/types.go index 060dd883e..db254549f 100644 --- a/providers/dns/dnsexit/internal/types.go +++ b/providers/dns/dnsexit/internal/types.go @@ -29,12 +29,12 @@ type APIResponse struct { } func (a APIResponse) Error() string { - var msg strings.Builder + msg := new(strings.Builder) - msg.WriteString(fmt.Sprintf("%s (code=%d)", a.Message, a.Code)) + _, _ = fmt.Fprintf(msg, "%s (code=%d)", a.Message, a.Code) for _, detail := range a.Details { - msg.WriteString(fmt.Sprintf(", %s", detail)) + _, _ = fmt.Fprintf(msg, ", %s", detail) } return msg.String() diff --git a/providers/dns/godaddy/internal/types.go b/providers/dns/godaddy/internal/types.go index c1e6d6638..3bd5c9560 100644 --- a/providers/dns/godaddy/internal/types.go +++ b/providers/dns/godaddy/internal/types.go @@ -26,9 +26,9 @@ type APIError struct { } func (a APIError) Error() string { - var msg strings.Builder + msg := new(strings.Builder) - msg.WriteString(fmt.Sprintf("%s: %s", a.Code, a.Message)) + _, _ = fmt.Fprintf(msg, "%s: %s", a.Code, a.Message) for _, field := range a.Fields { msg.WriteString(" ") diff --git a/providers/dns/gravity/internal/types.go b/providers/dns/gravity/internal/types.go index cb6e99083..872bc070f 100644 --- a/providers/dns/gravity/internal/types.go +++ b/providers/dns/gravity/internal/types.go @@ -13,17 +13,17 @@ type APIError struct { } func (a *APIError) Error() string { - var msg strings.Builder + msg := new(strings.Builder) - msg.WriteString(fmt.Sprintf("status: %s, error: %s", a.Status, a.ErrorMsg)) + _, _ = fmt.Fprintf(msg, "status: %s, error: %s", a.Status, a.ErrorMsg) if a.Code != 0 { - msg.WriteString(fmt.Sprintf(", code: %d", a.Code)) + _, _ = fmt.Fprintf(msg, ", code: %d", a.Code) } if len(a.Context) != 0 { for k, v := range a.Context { - msg.WriteString(fmt.Sprintf(", %s: %s", k, v)) + _, _ = fmt.Fprintf(msg, ", %s: %s", k, v) } } diff --git a/providers/dns/hetzner/internal/hetznerv1/internal/types.go b/providers/dns/hetzner/internal/hetznerv1/internal/types.go index 47e8a3f91..2b38a8a8c 100644 --- a/providers/dns/hetzner/internal/hetznerv1/internal/types.go +++ b/providers/dns/hetzner/internal/hetznerv1/internal/types.go @@ -16,20 +16,20 @@ type ErrorInfo struct { } func (i *ErrorInfo) Error() string { - var msg strings.Builder + msg := new(strings.Builder) - msg.WriteString(fmt.Sprintf("%s: %s", i.Code, i.Message)) + _, _ = fmt.Fprintf(msg, "%s: %s", i.Code, i.Message) if i.Details.Announcement != "" { - msg.WriteString(fmt.Sprintf(": %s", i.Details.Announcement)) + _, _ = fmt.Fprintf(msg, ": %s", i.Details.Announcement) } for _, limit := range i.Details.Limits { - msg.WriteString(fmt.Sprintf("limit: %s", limit.Name)) + _, _ = fmt.Fprintf(msg, "limit: %s", limit.Name) } for _, field := range i.Details.Fields { - msg.WriteString(fmt.Sprintf("field: %s: %s", field.Name, strings.Join(field.Messages, ", "))) + _, _ = fmt.Fprintf(msg, "field: %s: %s", field.Name, strings.Join(field.Messages, ", ")) } return msg.String() diff --git a/providers/dns/hostinger/internal/types.go b/providers/dns/hostinger/internal/types.go index 534dfa5e5..c1a02ff8c 100644 --- a/providers/dns/hostinger/internal/types.go +++ b/providers/dns/hostinger/internal/types.go @@ -12,12 +12,12 @@ type APIError struct { } func (a *APIError) Error() string { - var msg strings.Builder + msg := new(strings.Builder) - msg.WriteString(fmt.Sprintf("%s: %s", a.CorrelationID, a.Message)) + _, _ = fmt.Fprintf(msg, "%s: %s", a.CorrelationID, a.Message) for field, values := range a.Errors { - msg.WriteString(fmt.Sprintf(": %s: %s", field, strings.Join(values, ", "))) + _, _ = fmt.Fprintf(msg, ": %s: %s", field, strings.Join(values, ", ")) } return msg.String() diff --git a/providers/dns/hosttech/internal/types.go b/providers/dns/hosttech/internal/types.go index 854fc4883..a4b5b564d 100644 --- a/providers/dns/hosttech/internal/types.go +++ b/providers/dns/hosttech/internal/types.go @@ -16,12 +16,12 @@ type APIError struct { } func (a APIError) Error() string { - var msg strings.Builder + msg := new(strings.Builder) - msg.WriteString(fmt.Sprintf("%d: %s", a.StatusCode, a.Message)) + _, _ = fmt.Fprintf(msg, "%d: %s", a.StatusCode, a.Message) for k, v := range a.Errors { - msg.WriteString(fmt.Sprintf(" %s: %v", k, v)) + _, _ = fmt.Fprintf(msg, " %s: %v", k, v) } return msg.String() diff --git a/providers/dns/mittwald/internal/types.go b/providers/dns/mittwald/internal/types.go index ce49cb820..86cdf065c 100644 --- a/providers/dns/mittwald/internal/types.go +++ b/providers/dns/mittwald/internal/types.go @@ -61,14 +61,14 @@ type APIError struct { } func (a APIError) Error() string { - var msg strings.Builder + msg := new(strings.Builder) - msg.WriteString(fmt.Sprintf("%s: %s", a.Type, a.Message)) + _, _ = fmt.Fprintf(msg, "%s: %s", a.Type, a.Message) if len(a.ValidationErrors) > 0 { for _, validationError := range a.ValidationErrors { - msg.WriteString(fmt.Sprintf(" [%s: %s (%s, %s)]", - validationError.Type, validationError.Message, validationError.Path, validationError.Context.Format)) + _, _ = fmt.Fprintf(msg, " [%s: %s (%s, %s)]", + validationError.Type, validationError.Message, validationError.Path, validationError.Context.Format) } } diff --git a/providers/dns/namesurfer/internal/types.go b/providers/dns/namesurfer/internal/types.go index 7b3f08013..d364c1876 100644 --- a/providers/dns/namesurfer/internal/types.go +++ b/providers/dns/namesurfer/internal/types.go @@ -48,24 +48,24 @@ type APIError struct { } func (e *APIError) Error() string { - var msg strings.Builder + msg := new(strings.Builder) - msg.WriteString(fmt.Sprintf("code: %v", e.Code)) + _, _ = fmt.Fprintf(msg, "code: %v", e.Code) if e.Filename != "" { - msg.WriteString(fmt.Sprintf(", filename: %s", e.Filename)) + _, _ = fmt.Fprintf(msg, ", filename: %s", e.Filename) } if e.LineNumber > 0 { - msg.WriteString(fmt.Sprintf(", line: %d", e.LineNumber)) + _, _ = fmt.Fprintf(msg, ", line: %d", e.LineNumber) } if e.Message != "" { - msg.WriteString(fmt.Sprintf(", message: %s", e.Message)) + _, _ = fmt.Fprintf(msg, ", message: %s", e.Message) } if len(e.Detail) > 0 { - msg.WriteString(fmt.Sprintf(", detail: %v", strings.Join(e.Detail, " "))) + _, _ = fmt.Fprintf(msg, ", detail: %v", strings.Join(e.Detail, " ")) } return msg.String() From 84f3be40f0cfa471abae47db3d9e11a54ff1ae34 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 18 Feb 2026 23:26:31 +0100 Subject: [PATCH 285/298] chore: update dependencies (#2860) --- go.mod | 109 ++++++++++++++------------- go.sum | 233 +++++++++++++++++++++++++++++---------------------------- 2 files changed, 174 insertions(+), 168 deletions(-) diff --git a/go.mod b/go.mod index 42e11820b..b8e88428e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.0 require ( cloud.google.com/go/compute/metadata v0.9.0 github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 @@ -15,28 +15,28 @@ require ( github.com/Azure/go-autorest/autorest/to v0.4.1 github.com/BurntSushi/toml v1.6.0 github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 - github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 + github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.15 github.com/alibabacloud-go/tea v1.4.0 github.com/aliyun/credentials-go v1.4.7 - github.com/aws/aws-sdk-go-v2 v1.41.0 - github.com/aws/aws-sdk-go-v2/config v1.32.6 - github.com/aws/aws-sdk-go-v2/credentials v1.19.6 - github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.10 - github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 - github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 + github.com/aws/aws-sdk-go-v2 v1.41.1 + github.com/aws/aws-sdk-go-v2/config v1.32.8 + github.com/aws/aws-sdk-go-v2/credentials v1.19.8 + github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.11 + github.com/aws/aws-sdk-go-v2/service/route53 v1.62.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 github.com/aziontech/azionapi-go-sdk v0.144.0 - github.com/baidubce/bce-sdk-go v0.9.256 + github.com/baidubce/bce-sdk-go v0.9.260 github.com/cenkalti/backoff/v5 v5.0.3 github.com/dnsimple/dnsimple-go/v4 v4.0.0 github.com/exoscale/egoscale/v3 v3.1.33 github.com/go-acme/alidns-20150109/v4 v4.7.0 - github.com/go-acme/esa-20240910/v2 v2.44.0 + github.com/go-acme/esa-20240910/v2 v2.48.0 github.com/go-acme/jdcloud-sdk-go v1.64.0 - github.com/go-acme/tencentclouddnspod v1.1.25 - github.com/go-acme/tencentedgdeone v1.1.48 + github.com/go-acme/tencentclouddnspod v1.3.24 + github.com/go-acme/tencentedgdeone v1.3.38 github.com/go-jose/go-jose/v4 v4.1.3 - github.com/go-viper/mapstructure/v2 v2.4.0 + github.com/go-viper/mapstructure/v2 v2.5.0 github.com/google/go-cmp v0.7.0 github.com/google/go-querystring v1.2.0 github.com/google/uuid v1.6.0 @@ -44,18 +44,18 @@ require ( github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/go-version v1.8.0 - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.182 + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.187 github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 github.com/labbsr0x/bindman-dns-webhook v1.0.2 github.com/ldez/grignotin v0.10.1 - github.com/linode/linodego v1.64.0 + github.com/linode/linodego v1.65.0 github.com/liquidweb/liquidweb-go v1.6.4 github.com/mattn/go-isatty v0.0.20 - github.com/miekg/dns v1.1.69 + github.com/miekg/dns v1.1.72 github.com/mimuret/golang-iij-dpf v0.9.1 github.com/namedotcom/go/v4 v4.0.2 - github.com/nrdcg/auroradns v1.1.0 + github.com/nrdcg/auroradns v1.2.0 github.com/nrdcg/bunny-go v0.1.0 github.com/nrdcg/desec v0.11.1 github.com/nrdcg/dnspod-go v0.4.0 @@ -65,8 +65,8 @@ require ( github.com/nrdcg/mailinabox v0.3.0 github.com/nrdcg/namesilo v0.5.0 github.com/nrdcg/nodion v0.1.0 - github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2 - github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.2 + github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2 + github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 github.com/nrdcg/porkbun v0.4.0 github.com/nrdcg/vegadns v0.3.0 github.com/nzdjb/go-metaname v1.0.0 @@ -81,29 +81,29 @@ require ( github.com/selectel/go-selvpcclient/v4 v4.1.0 github.com/softlayer/softlayer-go v1.2.1 github.com/stretchr/testify v1.11.1 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.28 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.48 github.com/transip/gotransip/v6 v6.26.1 github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 github.com/urfave/cli/v2 v2.27.7 github.com/vinyldns/go-vinyldns v0.9.17 - github.com/volcengine/volc-sdk-golang v1.0.233 - github.com/vultr/govultr/v3 v3.26.1 - github.com/yandex-cloud/go-genproto v0.43.0 - github.com/yandex-cloud/go-sdk/services/dns v0.0.25 - github.com/yandex-cloud/go-sdk/v2 v2.37.0 - golang.org/x/crypto v0.46.0 - golang.org/x/net v0.48.0 - golang.org/x/oauth2 v0.34.0 - golang.org/x/text v0.32.0 + github.com/volcengine/volc-sdk-golang v1.0.237 + github.com/vultr/govultr/v3 v3.27.0 + github.com/yandex-cloud/go-genproto v0.54.0 + github.com/yandex-cloud/go-sdk/services/dns v0.0.36 + github.com/yandex-cloud/go-sdk/v2 v2.56.0 + golang.org/x/crypto v0.48.0 + golang.org/x/net v0.50.0 + golang.org/x/oauth2 v0.35.0 + golang.org/x/text v0.34.0 golang.org/x/time v0.14.0 - google.golang.org/api v0.259.0 - gopkg.in/ns1/ns1-go.v2 v2.16.0 + google.golang.org/api v0.267.0 + gopkg.in/ns1/ns1-go.v2 v2.17.2 gopkg.in/yaml.v2 v2.4.0 software.sslmate.com/src/go-pkcs12 v0.7.0 ) require ( - cloud.google.com/go/auth v0.18.0 // indirect + cloud.google.com/go/auth v0.18.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect @@ -119,22 +119,23 @@ require ( github.com/alibabacloud-go/openapi-util v0.1.1 // indirect github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 // indirect github.com/aws/smithy-go v1.24.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -160,8 +161,8 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect - github.com/googleapis/gax-go/v2 v2.16.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect + github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -205,23 +206,23 @@ require ( go.mongodb.org/mongo-driver v1.13.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.3.1 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect - golang.org/x/mod v0.30.0 // indirect + golang.org/x/mod v0.32.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/tools v0.41.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect google.golang.org/grpc v1.78.0 // indirect google.golang.org/protobuf v1.36.11 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/ini.v1 v1.67.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 889e6b5b5..f5b87c9fe 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,8 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0= -cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= +cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs= +cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -42,8 +42,8 @@ github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYs github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= @@ -121,8 +121,10 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= -github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 h1:Q00FU3H94Ts0ZIHDmY+fYGgB7dV9D/YX6FGsgorQPgw= github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.14/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.15 h1:Mubp9hXZMTPWZK+WxrR+kKOVFp4Q/PDZrIIM7ByXI9Y= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.15/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE= github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= @@ -169,54 +171,54 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4= -github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= +github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4= -github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8= -github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI= -github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE= -github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc= +github.com/aws/aws-sdk-go-v2/config v1.32.8 h1:iu+64gwDKEoKnyTQskSku72dAwggKI5sV6rNvgSMpMs= +github.com/aws/aws-sdk-go-v2/config v1.32.8/go.mod h1:MI2XvA+qDi3i9AJxX1E2fu730syEBzp/jnXrjxuHwgI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.8 h1:Jp2JYH1lRT3KhX4mshHPvVYsR5qqRec3hGvEarNYoR0= +github.com/aws/aws-sdk-go-v2/credentials v1.19.8/go.mod h1:fZG9tuvyVfxknv1rKibIz3DobRaFw1Poe8IKtXB3XYY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 h1:CjMzUs78RDDv4ROu3JnJn/Ig1r6ZD7/T2DXLLRpejic= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16/go.mod h1:uVW4OLBqbJXSHJYA9svT9BluSvvwbzLQ2Crf6UPzR3c= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 h1:JqcdRG//czea7Ppjb+g/n4o8i/R50aTBHkA7vu0lK+k= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17/go.mod h1:CO+WeGmIdj/MlPel2KwID9Gt7CNq4M65HUfBW97liM0= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 h1:DIBqIrJ7hv+e4CmIk2z3pyKT+3B6qVMgRsawHiR3qso= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7/go.mod h1:vLm00xmBke75UmpNvOcZQ/Q30ZFjbczeLFqGx5urmGo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.10 h1:MQuZZ6Tq1qQabPlkVxrCMdyVl70Ogl4AERZKo+y9Wzo= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.10/go.mod h1:U5C3JME1ibKESmpzBAqlRpTYZfVbTqrb5ICJm+sVVd8= -github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0 h1:80pDB3Tpmb2RCSZORrK9/3iQxsd+w6vSzVqpT1FGiwE= -github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0/go.mod h1:6EZUGGNLPLh5Unt30uEoA+KQcByERfXIkax9qrc80nA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 h1:MIWra+MSq53CFaXXAywB2qg9YvVZifkk6vEGl/1Qor0= -github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 h1:Z5EiPIzXKewUQK0QTMkutjiaPVeVYXX7KIqhXu/0fXs= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8/go.mod h1:FsTpJtvC4U1fyDXk7c71XoDv3HlRm8V3NiYLeYLh5YE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 h1:bGeHBsGZx0Dvu/eJC0Lh9adJa3M1xREcndxLNZlve2U= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17/go.mod h1:dcW24lbU0CzHusTE8LLHhRLI42ejmINN8Lcr22bwh/g= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.11 h1:VM5e5M39zRSs+aT0O9SoxHjUXqXxhbw3Yi0FdMQWPIc= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.11/go.mod h1:0jvzYPIQGCpnY/dmdaotTk2JH4QuBlnW0oeyrcGLWJ4= +github.com/aws/aws-sdk-go-v2/service/route53 v1.62.1 h1:1jIdwWOulae7bBLIgB36OZ0DINACb1wxM6wdGlx4eHE= +github.com/aws/aws-sdk-go-v2/service/route53 v1.62.1/go.mod h1:tE2zGlMIlxWv+7Otap7ctRp3qeKqtnja7DZguj3Vu/Y= +github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0 h1:oeu8VPlOre74lBA/PMhxa5vewaMIMmILM+RraSyB8KA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0/go.mod h1:5jggDlZ2CLQhwJBiZJb4vfk4f0GxWdEDruWKEJ1xOdo= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 h1:0jbJeuEHlwKJ9PfXtpSFc4MF+WIWORdhN1n30ITZGFM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/aziontech/azionapi-go-sdk v0.144.0 h1:T+/w18o+FCiZsk3Z0ACBVVe7c/5EGLG15S3P8JfuPfo= github.com/aziontech/azionapi-go-sdk v0.144.0/go.mod h1:OKxP/R0iVXnJJakYwMhh2BGAXnud8Ruy55Ak9ANuWoU= -github.com/baidubce/bce-sdk-go v0.9.256 h1:/6UwBzDp+dRFpKRIb5WsvxfSiG4SLOIOghvagOK/q4Y= -github.com/baidubce/bce-sdk-go v0.9.256/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/baidubce/bce-sdk-go v0.9.260 h1:1v1+2GTP+NGK3L24rJ+bnoiTaDaIy2YoaUM+ot2GTcw= +github.com/baidubce/bce-sdk-go v0.9.260/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -239,6 +241,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -315,14 +319,14 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-acme/alidns-20150109/v4 v4.7.0 h1:PqJ/wR0JTpL4v0Owu1uM7bPQ1Yww0eQLAuuSdLjjQaQ= github.com/go-acme/alidns-20150109/v4 v4.7.0/go.mod h1:btQvB6xZoN6ykKB74cPhiR+uvhrEE2AFVXm6RDmCHm0= -github.com/go-acme/esa-20240910/v2 v2.44.0 h1:ACi2uFb7ig4ousFs/YiFBR+aw3A4SHtOxvkMWB2Hbcs= -github.com/go-acme/esa-20240910/v2 v2.44.0/go.mod h1:ZYdN9EN9ikn26SNapxCVjZ65pHT/1qm4fzuJ7QGVX6g= +github.com/go-acme/esa-20240910/v2 v2.48.0 h1:muSDyhjDTejxUGe3FTthCPCqRaEdYY9cG3N/AmU52Lc= +github.com/go-acme/esa-20240910/v2 v2.48.0/go.mod h1:shPb6hzc1rJL15IJBY8HQ4GZk4E8RC52+52twutEwIg= github.com/go-acme/jdcloud-sdk-go v1.64.0 h1:AW9j5khk8tRYbpBJPxKmqdwIqgLs2Fz3HUK3hn2YXjs= github.com/go-acme/jdcloud-sdk-go v1.64.0/go.mod h1:qc/m8HNX1Zgd7GAv2DSEinup8fwy3Ted3/VVx7LB5bU= -github.com/go-acme/tencentclouddnspod v1.1.25 h1:7H3ZKshkaHzCXfRpAHVB5nvxeDDl2XLeNZfrNHiZj/s= -github.com/go-acme/tencentclouddnspod v1.1.25/go.mod h1:XXfzp0AYV7UAUsHKT6R0KAUJFhqAUXmWGF07Elpa5cE= -github.com/go-acme/tencentedgdeone v1.1.48 h1:WLyLBsRVhSLFmtbEFXk0naLODSQn7X6J0Fc/qR8xVUk= -github.com/go-acme/tencentedgdeone v1.1.48/go.mod h1:mu6tA+bPhlSd+CKUfzRikE0mfxmTlBI6dVTn9LY9dRI= +github.com/go-acme/tencentclouddnspod v1.3.24 h1:uCSiOW1EJttcnOON+MVVyVDJguFL/Q4NIGkq1CrT9p8= +github.com/go-acme/tencentclouddnspod v1.3.24/go.mod h1:RKcB2wSoZncjBA0OEFj59s1ko1XDy+ZsAtk+9uMxUF0= +github.com/go-acme/tencentedgdeone v1.3.38 h1:5YsVl0H4A+cwtiUqR1eZbKFdr4OWfYp2KYJopifzKyQ= +github.com/go-acme/tencentedgdeone v1.3.38/go.mod h1:yyjTKVmGpMtFv5HqGODqehHnZJ4KWAbG6dAiwWDgCDY= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -366,8 +370,8 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8Wd github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= @@ -467,12 +471,12 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= +github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y= -github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= +github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= +github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw= github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= @@ -537,8 +541,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.182 h1:B3W9acgpqu5XsN8v+W8SOTfqn/6n4JsjgoKBsm30HFY= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.182/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.187 h1:J+U6+eUjIsBhefolFdZW5hQNJbkMj+7msxZrv56Cg2g= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.187/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -612,8 +616,8 @@ github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufp github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/linode/linodego v1.64.0 h1:If6pULIwHuQytgogtpQaBdVLX7z2TTHUF5u1tj2TPiY= -github.com/linode/linodego v1.64.0/go.mod h1:GoiwLVuLdBQcAebxAVKVL3mMYUgJZR/puOUSla04xBE= +github.com/linode/linodego v1.65.0 h1:SdsuGD8VSsPWeShXpE7ihl5vec+fD3MgwhnfYC/rj7k= +github.com/linode/linodego v1.65.0/go.mod h1:tOFiTErdjkbVnV+4S0+NmIE9dqqZUEM2HsJaGu8wMh8= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= @@ -649,8 +653,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc= -github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g= +github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= +github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34= github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= @@ -691,8 +695,8 @@ github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1t github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo= -github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk= +github.com/nrdcg/auroradns v1.2.0 h1:Jg407vTdXZvZKsART9CNWMp8rQOyhBk04q0MsOf0YR4= +github.com/nrdcg/auroradns v1.2.0/go.mod h1:hnByA4Z7MOmV4EPRw5eOmEaNRFavcCIz6kONpNxp9LI= github.com/nrdcg/bunny-go v0.1.0 h1:GAHTRpHaG/TxfLZlqoJ8OJFzw8rI74+jOTkzxWh0uHA= github.com/nrdcg/bunny-go v0.1.0/go.mod h1:u+C9dgsspgtWVaAz6QkyV17s9fxD8viwwKoxb9XMz1A= github.com/nrdcg/desec v0.11.1 h1:ilpKmCr4gGsLcyq3RHfHNmlRzm9fzT2XbWxoVaUCS0s= @@ -711,10 +715,10 @@ github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE= github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2 h1:l0tH15ACQADZAzC+LZ+mo2tIX4H6uZu0ulrVmG5Tqz0= -github.com/nrdcg/oci-go-sdk/common/v1065 v1065.105.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.2 h1:gzB4c6ztb38C/jYiqEaFC+mCGcWFHDji9e6jwymY9d4= -github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.105.2/go.mod h1:l1qIPIq2uRV5WTSvkbhbl/ndbeOu7OCb3UZ+0+2ZSb8= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2 h1:OWijzl3nHUApvTivl+3+78dbBwmyEHOnb+W9m6ixGbk= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 h1:9LsjN/zaIN7H8JE61NHpbWhxF0UGY96+kMlk3g8OvGU= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2/go.mod h1:32vZH06TuwZSn+IDMO1qcDvC2vHVlzUALCwXGWPA+dc= github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= github.com/nrdcg/vegadns v0.3.0 h1:11FQMw7xVIRUWO9o5+Z/5YZhmPWlm4oxUUH3F6EVqQU= @@ -904,10 +908,10 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.25/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.48/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.28 h1:Rj1WXXNPm9AsPf0PJhWCvlsqfcKPUYdyVnkmEc3O8sI= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.28/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.24/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.38/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.48 h1:bCs+z6dxRaHWm/C1D/XkSOcCZ0+W2+/6HmIXjpAj+fY= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.48/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= @@ -922,10 +926,10 @@ github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/vinyldns/go-vinyldns v0.9.17 h1:hfPZfCaxcRBX6Gsgl42rLCeoal58/BH8kkvJShzjjdI= github.com/vinyldns/go-vinyldns v0.9.17/go.mod h1:pwWhE9K/leGDOIduVhRGvQ3ecVMHWRfEnKYUTEU3gB4= -github.com/volcengine/volc-sdk-golang v1.0.233 h1:Hh2pzwu/Wq19rsZgNo3HdpjQB28D/F0+m6EjLVggmhM= -github.com/volcengine/volc-sdk-golang v1.0.233/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= -github.com/vultr/govultr/v3 v3.26.1 h1:G/M0rMQKwVSmL+gb0UgETbW5mcQi0Vf/o/ZSGdBCxJw= -github.com/vultr/govultr/v3 v3.26.1/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= +github.com/volcengine/volc-sdk-golang v1.0.237 h1:hpLKiS2BwDcSBtZWSz034foCbd0h3FrHTKlUMqHIdc4= +github.com/volcengine/volc-sdk-golang v1.0.237/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= +github.com/vultr/govultr/v3 v3.27.0 h1:J8etMyu/Jh5+idMsu2YZpOWmDXXHeW4VZnkYXmJYHx8= +github.com/vultr/govultr/v3 v3.27.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= @@ -934,12 +938,12 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yandex-cloud/go-genproto v0.43.0 h1:HjBesEmCN8ZOhjjh8gs605vvi9/MBJAW3P20OJ4iQnw= -github.com/yandex-cloud/go-genproto v0.43.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= -github.com/yandex-cloud/go-sdk/services/dns v0.0.25 h1:BcGEuOnwq2X3LS2kvFC6BOdZkOq4Lc7XAYvzap/SJJY= -github.com/yandex-cloud/go-sdk/services/dns v0.0.25/go.mod h1:B4QHijALUHIjRxL3aqmOwDrHYUI2XdeeG4WKItth3jI= -github.com/yandex-cloud/go-sdk/v2 v2.37.0 h1:WvttW6p9xcWag9j+GQv+GJXPggggXGwOlIJNfkWmFWw= -github.com/yandex-cloud/go-sdk/v2 v2.37.0/go.mod h1:Dt4a81enjRsm4xMJyW5E1Y/vaUYwXJvUGRdDLuM2k6I= +github.com/yandex-cloud/go-genproto v0.54.0 h1:LjEwDPBAtF39HvcPQe8I+ImCnFasCPCOVh2b2Sr2eAg= +github.com/yandex-cloud/go-genproto v0.54.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-sdk/services/dns v0.0.36 h1:sD622+baDvJ2ujhCfoFsCH0XeNsaZNW6loRqvRavjtE= +github.com/yandex-cloud/go-sdk/services/dns v0.0.36/go.mod h1:Hh7IKJxULaRzmyM19lQZw+yUDyMM8M3Qrk1LbWqhCkc= +github.com/yandex-cloud/go-sdk/v2 v2.56.0 h1:rihPAZbPbHU/BKTLuT64nU1uhbBrO20HhdlLR3Hyoz0= +github.com/yandex-cloud/go-sdk/v2 v2.56.0/go.mod h1:jzVBQgamNHoiDsmjog2dPZHMXuGZqmxf/epH+Qb7Emc= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= @@ -969,16 +973,16 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -1031,8 +1035,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1076,8 +1080,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1135,16 +1139,16 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1248,8 +1252,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1264,8 +1268,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1284,8 +1288,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1351,8 +1355,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1381,8 +1385,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.259.0 h1:90TaGVIxScrh1Vn/XI2426kRpBqHwWIzVBzJsVZ5XrQ= -google.golang.org/api v0.259.0/go.mod h1:LC2ISWGWbRoyQVpxGntWwLWN/vLNxxKBK9KuJRI8Te4= +google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE= +google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1421,12 +1425,12 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934= -google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= +google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1475,11 +1479,12 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k= +gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/ns1/ns1-go.v2 v2.16.0 h1:mUczKFnrCystSV7yIODzVSbENoud3T7DwstmyVZfqg4= -gopkg.in/ns1/ns1-go.v2 v2.16.0/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= +gopkg.in/ns1/ns1-go.v2 v2.17.2 h1:x8YKHqCJWkC/hddfUhw7FRqTG0x3fr/0ZnWYN+i4THs= +gopkg.in/ns1/ns1-go.v2 v2.17.2/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= From d896c1f0366a5b60112c9a8b87861acacf037417 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 19 Feb 2026 12:25:10 +0100 Subject: [PATCH 286/298] fix: preserve domain order (#2862) --- acme/api/identifier.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/acme/api/identifier.go b/acme/api/identifier.go index 42a8fd391..245ed8515 100644 --- a/acme/api/identifier.go +++ b/acme/api/identifier.go @@ -2,7 +2,6 @@ package api import ( "cmp" - "maps" "net" "slices" @@ -10,7 +9,9 @@ import ( ) func createIdentifiers(domains []string) []acme.Identifier { - uniqIdentifiers := make(map[string]acme.Identifier) + uniqIdentifiers := make(map[string]struct{}) + + var identifiers []acme.Identifier for _, domain := range domains { if _, ok := uniqIdentifiers[domain]; ok { @@ -23,10 +24,12 @@ func createIdentifiers(domains []string) []acme.Identifier { ident.Type = "ip" } - uniqIdentifiers[domain] = ident + identifiers = append(identifiers, ident) + + uniqIdentifiers[domain] = struct{}{} } - return slices.AppendSeq(make([]acme.Identifier, 0, len(uniqIdentifiers)), maps.Values(uniqIdentifiers)) + return identifiers } // compareIdentifiers compares 2 slices of [acme.Identifier]. From 078a1889c87c750f6051a3dd9dc1e5e24e690ec8 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 19 Feb 2026 12:26:53 +0100 Subject: [PATCH 287/298] Add DNS provider for ArtFiles (#2859) --- README.md | 90 +++---- cmd/zz_gen_cmd_dnshelp.go | 22 ++ docs/content/dns/zz_gen_artfiles.md | 69 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/artfiles/artfiles.go | 204 ++++++++++++++++ providers/dns/artfiles/artfiles.toml | 24 ++ providers/dns/artfiles/artfiles_test.go | 228 ++++++++++++++++++ providers/dns/artfiles/internal/client.go | 133 ++++++++++ .../dns/artfiles/internal/client_test.go | 89 +++++++ .../artfiles/internal/fixtures/domains.txt | 3 + .../artfiles/internal/fixtures/get_dns.json | 16 ++ .../artfiles/internal/fixtures/set_dns.json | 4 + .../internal/fixtures/txt_record-multiple.txt | 8 + .../artfiles/internal/fixtures/txt_record.txt | 7 + providers/dns/artfiles/internal/types.go | 109 +++++++++ providers/dns/artfiles/internal/types_test.go | 183 ++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 17 files changed, 1148 insertions(+), 46 deletions(-) create mode 100644 docs/content/dns/zz_gen_artfiles.md create mode 100644 providers/dns/artfiles/artfiles.go create mode 100644 providers/dns/artfiles/artfiles.toml create mode 100644 providers/dns/artfiles/artfiles_test.go create mode 100644 providers/dns/artfiles/internal/client.go create mode 100644 providers/dns/artfiles/internal/client_test.go create mode 100644 providers/dns/artfiles/internal/fixtures/domains.txt create mode 100644 providers/dns/artfiles/internal/fixtures/get_dns.json create mode 100644 providers/dns/artfiles/internal/fixtures/set_dns.json create mode 100644 providers/dns/artfiles/internal/fixtures/txt_record-multiple.txt create mode 100644 providers/dns/artfiles/internal/fixtures/txt_record.txt create mode 100644 providers/dns/artfiles/internal/types.go create mode 100644 providers/dns/artfiles/internal/types_test.go diff --git a/README.md b/README.md index 105ea53aa..7e015e70f 100644 --- a/README.md +++ b/README.md @@ -73,228 +73,228 @@ If your DNS provider is not supported, please open an [issue](https://github.com Amazon Route 53 Anexia CloudDNS + ArtFiles ArvanCloud - Aurora DNS + Aurora DNS Autodns Axelname Azion - Azure (deprecated) + Azure (deprecated) Azure DNS Baidu Cloud Beget.com - Binary Lane + Binary Lane Bindman Bluecat Bluecat v2 - BookMyName + BookMyName Brandit (deprecated) Bunny Checkdomain - Civo + Civo Cloud.ru CloudDNS Cloudflare - ClouDNS + ClouDNS CloudXNS (Deprecated) ConoHa v2 ConoHa v3 - Constellix + Constellix Core-Networks CPanel/WHM DDnss (DynDNS Service) - Derak Cloud + Derak Cloud deSEC.io Designate DNSaaS for Openstack Digital Ocean - DirectAdmin + DirectAdmin DNS Made Easy DNSExit dnsHome.de - DNSimple + DNSimple DNSPod (deprecated) Domain Offensive (do.de) Domeneshop - DreamHost + DreamHost Duck DNS Dyn DynDnsFree.de - Dynu + Dynu EasyDNS EdgeCenter Efficient IP - Epik + Epik Exoscale External program F5 XC - freemyip.com + freemyip.com FusionLayer NameSurfer G-Core Gandi - Gandi Live DNS (v5) + Gandi Live DNS (v5) Gigahost.no Glesys Go Daddy - Google Cloud + Google Cloud Google Domains Gravity Hetzner - Hosting.de + Hosting.de Hosting.nl Hostinger Hosttech - HTTP request + HTTP request http.net Huawei Cloud Hurricane Electric DNS - HyperOne + HyperOne IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox - Infomaniak + Infomaniak Internet Initiative Japan Internet.bs INWX - Ionos + Ionos Ionos Cloud IPv64 ISPConfig 3 - ISPConfig 3 - Dynamic DNS (DDNS) Module + ISPConfig 3 - Dynamic DNS (DDNS) Module iwantmyname (Deprecated) JD Cloud Joker - Joohoi's ACME-DNS + Joohoi's ACME-DNS KeyHelp Liara Lima-City - Linode (v4) + Linode (v4) Liquid Web Loopia LuaDNS - Mail-in-a-Box + Mail-in-a-Box ManageEngine CloudDNS Manual Metaname - Metaregistrar + Metaregistrar mijn.host Mittwald myaddr.{tools,dev,io} - MyDNS.jp + MyDNS.jp MythicBeasts Name.com Namecheap - Namesilo + Namesilo NearlyFreeSpeech.NET Neodigit Netcup - Netlify + Netlify Nicmanager NIFCloud Njalla - Nodion + Nodion NS1 Octenium Open Telekom Cloud - Oracle Cloud + Oracle Cloud OVH plesk.com Porkbun - PowerDNS + PowerDNS Rackspace Rain Yun/雨云 RcodeZero - reg.ru + reg.ru Regfish RFC2136 RimuHosting - RU CENTER + RU CENTER Sakura Cloud Scaleway Selectel - Selectel v2 + Selectel v2 SelfHost.(de|eu) Servercow Shellrent - Simply.com + Simply.com Sonic Spaceship Stackpath - Syse + Syse Technitium Tencent Cloud DNS Tencent EdgeOne - Timeweb Cloud + Timeweb Cloud TodayNIC/时代互联 TransIP UKFast SafeDNS - Ultradns + Ultradns United-Domains Variomedia VegaDNS - Vercel + Vercel Versio.[nl|eu|uk] VinylDNS Virtualname - VK Cloud + VK Cloud Volcano Engine/火山引擎 Vscale Vultr - webnames.ca + webnames.ca webnames.ru Websupport WEDOS - West.cn/西部数码 + West.cn/西部数码 Yandex 360 Yandex Cloud Yandex PDD - Zone.ee + Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index cdee65371..9e83a7b25 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -19,6 +19,7 @@ func allDNSCodes() string { "allinkl", "alwaysdata", "anexia", + "artfiles", "arvancloud", "auroradns", "autodns", @@ -358,6 +359,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/anexia`) + case "artfiles": + // generated from: providers/dns/artfiles/artfiles.toml + ew.writeln(`Configuration for ArtFiles.`) + ew.writeln(`Code: 'artfiles'`) + ew.writeln(`Since: 'v4.32.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "ARTFILES_PASSWORD": API password`) + ew.writeln(` - "ARTFILES_USERNAME": API username`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "ARTFILES_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "ARTFILES_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "ARTFILES_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 360)`) + ew.writeln(` - "ARTFILES_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/artfiles`) + case "arvancloud": // generated from: providers/dns/arvancloud/arvancloud.toml ew.writeln(`Configuration for ArvanCloud.`) diff --git a/docs/content/dns/zz_gen_artfiles.md b/docs/content/dns/zz_gen_artfiles.md new file mode 100644 index 000000000..15ac2d964 --- /dev/null +++ b/docs/content/dns/zz_gen_artfiles.md @@ -0,0 +1,69 @@ +--- +title: "ArtFiles" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: artfiles +dnsprovider: + since: "v4.32.0" + code: "artfiles" + url: "https://www.artfiles.de/extras/domains/" +--- + + + + + + +Configuration for [ArtFiles](https://www.artfiles.de/extras/domains/). + + + + +- Code: `artfiles` +- Since: v4.32.0 + + +Here is an example bash command using the ArtFiles provider: + +```bash +ARTFILES_USERNAME="xxx" \ +ARTFILES_PASSWORD="yyy" \ +lego --dns artfiles -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `ARTFILES_PASSWORD` | API password | +| `ARTFILES_USERNAME` | API username | + +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 | +|--------------------------------|-------------| +| `ARTFILES_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `ARTFILES_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `ARTFILES_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 360) | +| `ARTFILES_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://support.artfiles.de/DCP-API#dns) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 759b8e84f..c6d845bf2 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bluecatv2, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, ddnss, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, namesurfer, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, artfiles, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bluecatv2, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, ddnss, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, namesurfer, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/artfiles/artfiles.go b/providers/dns/artfiles/artfiles.go new file mode 100644 index 000000000..c918d77f6 --- /dev/null +++ b/providers/dns/artfiles/artfiles.go @@ -0,0 +1,204 @@ +// Package artfiles implements a DNS provider for solving the DNS-01 challenge using ArtFiles. +package artfiles + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "slices" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/artfiles/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" +) + +// Environment variables names. +const ( + envNamespace = "ARTFILES_" + + EnvUsername = envNamespace + "USERNAME" + EnvPassword = envNamespace + "PASSWORD" + + 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 + + PropagationTimeout time.Duration + PollingInterval time.Duration + HTTPClient *http.Client +} + +// NewDefaultConfig returns a default configuration for the DNSProvider. +func NewDefaultConfig() *Config { + return &Config{ + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 6*time.Minute), + 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for ArtFiles. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvUsername, EnvPassword) + if err != nil { + return nil, fmt.Errorf("artfiles: %w", err) + } + + config := NewDefaultConfig() + config.Username = values[EnvUsername] + config.Password = values[EnvPassword] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for ArtFiles. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("artfiles: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.Username, config.Password) + if err != nil { + return nil, fmt.Errorf("artfiles: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + zone, err := d.findZone(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("artfiles: %w", err) + } + + records, err := d.client.GetRecords(ctx, zone) + if err != nil { + return fmt.Errorf("artfiles: get records: %w", err) + } + + rv := internal.RecordValue{} + + if len(records["TXT"]) > 0 { + var raw string + + err = json.Unmarshal(records["TXT"], &raw) + if err != nil { + return fmt.Errorf("artfiles: unmarshal TXT records: %w", err) + } + + rv = internal.ParseRecordValue(raw) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone) + if err != nil { + return fmt.Errorf("artfiles: %w", err) + } + + rv.Add(subDomain, info.Value) + + err = d.client.SetRecords(ctx, zone, "TXT", rv) + if err != nil { + return fmt.Errorf("artfiles: set TXT records: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + zone, err := d.findZone(ctx, info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("artfiles: %w", err) + } + + records, err := d.client.GetRecords(ctx, zone) + if err != nil { + return fmt.Errorf("artfiles: get records: %w", err) + } + + var raw string + + err = json.Unmarshal(records["TXT"], &raw) + if err != nil { + return fmt.Errorf("artfiles: unmarshal TXT records: %w", err) + } + + rv := internal.ParseRecordValue(raw) + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, zone) + if err != nil { + return fmt.Errorf("artfiles: %w", err) + } + + rv.RemoveValue(subDomain, info.Value) + + err = d.client.SetRecords(ctx, zone, "TXT", rv) + if err != nil { + return fmt.Errorf("artfiles: set TXT records: %w", err) + } + + 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 +} + +func (d *DNSProvider) findZone(ctx context.Context, fqdn string) (string, error) { + domains, err := d.client.GetDomains(ctx) + if err != nil { + return "", fmt.Errorf("artfiles: get domains: %w", err) + } + + var zone string + + for s := range dns01.UnFqdnDomainsSeq(fqdn) { + if slices.Contains(domains, s) { + zone = s + } + } + + if zone == "" { + return "", fmt.Errorf("artfiles: could not find the zone for domain %q", fqdn) + } + + return zone, nil +} diff --git a/providers/dns/artfiles/artfiles.toml b/providers/dns/artfiles/artfiles.toml new file mode 100644 index 000000000..00ff12342 --- /dev/null +++ b/providers/dns/artfiles/artfiles.toml @@ -0,0 +1,24 @@ +Name = "ArtFiles" +Description = '''''' +URL = "https://www.artfiles.de/extras/domains/" +Code = "artfiles" +Since = "v4.32.0" + +Example = ''' +ARTFILES_USERNAME="xxx" \ +ARTFILES_PASSWORD="yyy" \ +lego --dns artfiles -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + ARTFILES_USERNAME = "API username" + ARTFILES_PASSWORD = "API password" + [Configuration.Additional] + ARTFILES_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + ARTFILES_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 360)" + ARTFILES_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + ARTFILES_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://support.artfiles.de/DCP-API#dns" diff --git a/providers/dns/artfiles/artfiles_test.go b/providers/dns/artfiles/artfiles_test.go new file mode 100644 index 000000000..42490f10d --- /dev/null +++ b/providers/dns/artfiles/artfiles_test.go @@ -0,0 +1,228 @@ +package artfiles + +import ( + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "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: "user", + EnvPassword: "secret", + }, + }, + { + desc: "missing username", + envVars: map[string]string{ + EnvUsername: "", + EnvPassword: "secret", + }, + expected: "artfiles: some credentials information are missing: ARTFILES_USERNAME", + }, + { + desc: "missing password", + envVars: map[string]string{ + EnvUsername: "user", + EnvPassword: "", + }, + expected: "artfiles: some credentials information are missing: ARTFILES_PASSWORD", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "artfiles: some credentials information are missing: ARTFILES_USERNAME,ARTFILES_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 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 + username string + password string + expected string + }{ + { + desc: "success", + username: "user", + password: "secret", + }, + { + desc: "missing username", + password: "secret", + expected: "artfiles: credentials missing", + }, + { + desc: "missing Example", + username: "user", + expected: "artfiles: credentials missing", + }, + { + desc: "missing credentials", + expected: "artfiles: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Username = test.username + config.Password = test.password + + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.Username = "user" + config.Password = "secret" + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckHeader(). + WithBasicAuth("user", "secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("GET /domain/get_domains.html", + servermock.ResponseFromInternal("domains.txt"), + ). + Route("GET /dns/get_dns.html", + servermock.ResponseFromInternal("get_dns.json"), + servermock.CheckQueryParameter().Strict(). + With("domain", "example.com"), + ). + Route("POST /dns/set_dns.html", + servermock.ResponseFromInternal("set_dns.json"), + servermock.CheckQueryParameter().Strict(). + With("TXT", `@ "v=spf1 a mx ~all" +_acme-challenge "TheAcmeChallenge" +_acme-challenge "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" +_dmarc "v=DMARC1;p=reject;sp=reject;adkim=r;aspf=r;pct=100;rua=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;ri=86400;ruf=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;fo=1;rf=afrf" +_mta-sts "v=STSv1;id=yyyymmddTHHMMSS;" +_smtp._tls "v=TLSRPTv1;rua=mailto:someone@in.mailhardener.com" +selector._domainkey "v=DKIM1;k=rsa;p=Base64Stuff" "MoreBase64Stuff" "Even++MoreBase64Stuff" "YesMoreBase64Stuff" "And+Yes+Even+MoreBase64Stuff" "Sure++MoreBase64Stuff" "LastBase64Stuff" +selectorecc._domainkey "v=DKIM1;k=ed25519;p=Base64Stuff"`). + With("domain", "example.com"), + ). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("GET /domain/get_domains.html", + servermock.ResponseFromInternal("domains.txt"), + ). + Route("GET /dns/get_dns.html", + servermock.ResponseFromInternal("get_dns.json"), + servermock.CheckQueryParameter().Strict(). + With("domain", "example.com"), + ). + Route("POST /dns/set_dns.html", + servermock.ResponseFromInternal("set_dns.json"), + servermock.CheckQueryParameter().Strict(). + With("TXT", `@ "v=spf1 a mx ~all" +_acme-challenge "TheAcmeChallenge" +_dmarc "v=DMARC1;p=reject;sp=reject;adkim=r;aspf=r;pct=100;rua=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;ri=86400;ruf=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;fo=1;rf=afrf" +_mta-sts "v=STSv1;id=yyyymmddTHHMMSS;" +_smtp._tls "v=TLSRPTv1;rua=mailto:someone@in.mailhardener.com" +selector._domainkey "v=DKIM1;k=rsa;p=Base64Stuff" "MoreBase64Stuff" "Even++MoreBase64Stuff" "YesMoreBase64Stuff" "And+Yes+Even+MoreBase64Stuff" "Sure++MoreBase64Stuff" "LastBase64Stuff" +selectorecc._domainkey "v=DKIM1;k=ed25519;p=Base64Stuff"`). + With("domain", "example.com"), + ). + Build(t) + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/artfiles/internal/client.go b/providers/dns/artfiles/internal/client.go new file mode 100644 index 000000000..61b350511 --- /dev/null +++ b/providers/dns/artfiles/internal/client.go @@ -0,0 +1,133 @@ +package internal + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" +) + +const defaultBaseURL = "https://dcp.c.artfiles.de/api/" + +// Client the ArtFiles API client. +type Client struct { + username string + password string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(username, password string) (*Client, error) { + if username == "" || password == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + username: username, + password: password, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) GetDomains(ctx context.Context) ([]string, error) { + endpoint := c.BaseURL.JoinPath("domain", "get_domains.html") + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + raw, err := c.do(req) + if err != nil { + return nil, err + } + + return parseDomains(string(raw)) +} + +func (c *Client) GetRecords(ctx context.Context, domain string) (map[string]json.RawMessage, error) { + endpoint := c.BaseURL.JoinPath("dns", "get_dns.html") + + query := endpoint.Query() + query.Set("domain", domain) + + endpoint.RawQuery = query.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + raw, err := c.do(req) + if err != nil { + return nil, err + } + + var result Records + + err = json.Unmarshal(raw, &result) + if err != nil { + return nil, errutils.NewUnmarshalError(req, http.StatusOK, raw, err) + } + + return result.Data, nil +} + +func (c *Client) SetRecords(ctx context.Context, domain, rType string, value RecordValue) error { + endpoint := c.BaseURL.JoinPath("dns", "set_dns.html") + + query := endpoint.Query() + query.Set("domain", domain) + query.Set(rType, value.String()) + + endpoint.RawQuery = query.Encode() + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), nil) + if err != nil { + return fmt.Errorf("unable to create request: %w", err) + } + + _, err = c.do(req) + + return err +} + +func (c *Client) do(req *http.Request) ([]byte, error) { + useragent.SetHeader(req.Header) + + req.SetBasicAuth(c.username, c.password) + + if req.Method == http.MethodPost { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return nil, errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return nil, errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + if resp.StatusCode/100 != 2 { + return nil, errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return raw, nil +} diff --git a/providers/dns/artfiles/internal/client_test.go b/providers/dns/artfiles/internal/client_test.go new file mode 100644 index 000000000..cc76f06f5 --- /dev/null +++ b/providers/dns/artfiles/internal/client_test.go @@ -0,0 +1,89 @@ +package internal + +import ( + "encoding/json" + "net/http/httptest" + "net/url" + "strconv" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("user", "secret") + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + WithBasicAuth("user", "secret"), + ) +} + +func TestClient_GetDomains(t *testing.T) { + client := mockBuilder(). + Route("GET /domain/get_domains.html", + servermock.ResponseFromFixture("domains.txt"), + ). + Build(t) + + zones, err := client.GetDomains(t.Context()) + require.NoError(t, err) + + expected := []string{"example.com", "example.org", "example.net"} + + assert.Equal(t, expected, zones) +} + +func TestClient_GetRecords(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/get_dns.html", + servermock.ResponseFromFixture("get_dns.json"), + servermock.CheckQueryParameter().Strict(). + With("domain", "example.com"), + ). + Build(t) + + records, err := client.GetRecords(t.Context(), "example.com") + require.NoError(t, err) + + expected := map[string]json.RawMessage{ + "A": json.RawMessage(strconv.Quote("sub1 1.2.3.4\nsub2 1.2.3.4\nsub3 1.2.3.4\nsub4 1.2.3.4\nsub5 1.2.3.4\nsub6 1.2.3.4\nsub7 1.2.3.4\nsub8 1.2.3.4\nsub9 1.2.3.4\nsub10 1.2.3.4\nsub11 1.2.3.4\nsub12 1.2.3.4\nsub13 1.2.3.4\nsub14 1.2.3.4\nsub15 1.2.3.4\nsub16 1.2.3.4\nsub17 1.2.3.4\nsub18 1.2.3.4\n@ 1.2.3.4")), + "AAAA": json.RawMessage(strconv.Quote("")), + "CAA": json.RawMessage(strconv.Quote("@ 128 iodef \"mailto:someone@example.tld\"\n@ 128 issue \"letsencrypt.org\"\n@ 128 issuewild \"letsencrypt.org\"")), + "CName": json.RawMessage(strconv.Quote("some cname.to.example.tld.")), + "MX": json.RawMessage(strconv.Quote("10 mail.example.tld.")), + "SRV": json.RawMessage(strconv.Quote("_imap._tcp 0 0 0 .\n_imaps._tcp 0 1 993 mail.example.tld.\n_pop3._tcp 0 0 0 .\n_pop3s._tcp 0 0 0 .")), + "TLSA": json.RawMessage(strconv.Quote("_25._tcp.mail.example.tld. 2 1 1 CBBC559B44D524D6A132BDAC672744DA3407F12AAE5D5F722C5F6C7913871C75\n_25._tcp.mail.example.tld. 2 1 1 885BF0572252C6741DC9A52F5044487FEF2A93B811CDEDFAD7624CC283B7CDD5\n_25._tcp.mail.example.tld. 2 1 1 F1440A9B76E1E41E53A4CB461329BF6337B419726BE513E42E19F1C691C5D4B2\n_465._tcp.mail.example.tld. 2 1 1 CBBC559B44D524D6A132BDAC672744DA3407F12AAE5D5F722C5F6C7913871C75\n_465._tcp.mail.example.tld. 2 1 1 885BF0572252C6741DC9A52F5044487FEF2A93B811CDEDFAD7624CC283B7CDD5\n_465._tcp.mail.example.tld. 2 1 1 F1440A9B76E1E41E53A4CB461329BF6337B419726BE513E42E19F1C691C5D4B2\n_587._tcp.mail.example.tld. 2 1 1 CBBC559B44D524D6A132BDAC672744DA3407F12AAE5D5F722C5F6C7913871C75\n_587._tcp.mail.example.tld. 2 1 1 885BF0572252C6741DC9A52F5044487FEF2A93B811CDEDFAD7624CC283B7CDD5\n_587._tcp.mail.example.tld. 2 1 1 F1440A9B76E1E41E53A4CB461329BF6337B419726BE513E42E19F1C691C5D4B2")), + "TXT": json.RawMessage(strconv.Quote("_dmarc \"v=DMARC1;p=reject;sp=reject;adkim=r;aspf=r;pct=100;rua=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;ri=86400;ruf=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;fo=1;rf=afrf\"\n_mta-sts \"v=STSv1;id=yyyymmddTHHMMSS;\"\n_smtp._tls \"v=TLSRPTv1;rua=mailto:someone@in.mailhardener.com\"\n@ \"v=spf1 a mx ~all\"\nselector._domainkey \"v=DKIM1;k=rsa;p=Base64Stuff\" \"MoreBase64Stuff\" \"Even++MoreBase64Stuff\" \"YesMoreBase64Stuff\" \"And+Yes+Even+MoreBase64Stuff\" \"Sure++MoreBase64Stuff\" \"LastBase64Stuff\"\nselectorecc._domainkey \"v=DKIM1;k=ed25519;p=Base64Stuff\"\n_acme-challenge \"TheAcmeChallenge\"")), + "TTL": json.RawMessage("3600"), + "comment": json.RawMessage(strconv.Quote("TLSA RR:\nInfo -> https://dnssec-stats.ant.isi.edu/~viktor/x3hosts.html\nTest 1 -> https://stats.dnssec-tools.org/explore/?example.tld\nTest 2 -> https://dane.sys4.de/smtp/example.tld\n\nSMIMEA RR:\nGenerator -> https://www.smimea.info/smimea-generator.php\nTest -> https://www.smimea.info/smimea-test.php")), + "nameserver": json.RawMessage(strconv.Quote("auth1.artfiles.de.\nauth2.artfiles.de.")), + } + + assert.Equal(t, expected, records) +} + +func TestClient_SetRecords(t *testing.T) { + client := mockBuilder(). + Route("POST /dns/set_dns.html", + servermock.ResponseFromFixture("set_dns.json"), + servermock.CheckQueryParameter().Strict(). + With("TXT", "a b\nc \"d\""). + With("domain", "example.com"), + ). + Build(t) + + err := client.SetRecords(t.Context(), "example.com", "TXT", RecordValue{"c": []string{`"d"`}, "a": []string{"b"}}) + require.NoError(t, err) +} diff --git a/providers/dns/artfiles/internal/fixtures/domains.txt b/providers/dns/artfiles/internal/fixtures/domains.txt new file mode 100644 index 000000000..b8a1247d2 --- /dev/null +++ b/providers/dns/artfiles/internal/fixtures/domains.txt @@ -0,0 +1,3 @@ +example.com normal 2026-10-01 2017-09-18 163477 +example.org normal 2026-08-01 2016-07-07 156216 +example.net normal 2026-07-01 2017-06-06 162462 diff --git a/providers/dns/artfiles/internal/fixtures/get_dns.json b/providers/dns/artfiles/internal/fixtures/get_dns.json new file mode 100644 index 000000000..fa672e0e1 --- /dev/null +++ b/providers/dns/artfiles/internal/fixtures/get_dns.json @@ -0,0 +1,16 @@ +{ + "data": { + "SRV": "_imap._tcp 0 0 0 .\n_imaps._tcp 0 1 993 mail.example.tld.\n_pop3._tcp 0 0 0 .\n_pop3s._tcp 0 0 0 .", + "AAAA": "", + "MX": "10 mail.example.tld.", + "CAA": "@ 128 iodef \"mailto:someone@example.tld\"\n@ 128 issue \"letsencrypt.org\"\n@ 128 issuewild \"letsencrypt.org\"", + "TTL": 3600, + "comment": "TLSA RR:\nInfo -> https://dnssec-stats.ant.isi.edu/~viktor/x3hosts.html\nTest 1 -> https://stats.dnssec-tools.org/explore/?example.tld\nTest 2 -> https://dane.sys4.de/smtp/example.tld\n\nSMIMEA RR:\nGenerator -> https://www.smimea.info/smimea-generator.php\nTest -> https://www.smimea.info/smimea-test.php", + "TXT": "_dmarc \"v=DMARC1;p=reject;sp=reject;adkim=r;aspf=r;pct=100;rua=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;ri=86400;ruf=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;fo=1;rf=afrf\"\n_mta-sts \"v=STSv1;id=yyyymmddTHHMMSS;\"\n_smtp._tls \"v=TLSRPTv1;rua=mailto:someone@in.mailhardener.com\"\n@ \"v=spf1 a mx ~all\"\nselector._domainkey \"v=DKIM1;k=rsa;p=Base64Stuff\" \"MoreBase64Stuff\" \"Even++MoreBase64Stuff\" \"YesMoreBase64Stuff\" \"And+Yes+Even+MoreBase64Stuff\" \"Sure++MoreBase64Stuff\" \"LastBase64Stuff\"\nselectorecc._domainkey \"v=DKIM1;k=ed25519;p=Base64Stuff\"\n_acme-challenge \"TheAcmeChallenge\"", + "A": "sub1 1.2.3.4\nsub2 1.2.3.4\nsub3 1.2.3.4\nsub4 1.2.3.4\nsub5 1.2.3.4\nsub6 1.2.3.4\nsub7 1.2.3.4\nsub8 1.2.3.4\nsub9 1.2.3.4\nsub10 1.2.3.4\nsub11 1.2.3.4\nsub12 1.2.3.4\nsub13 1.2.3.4\nsub14 1.2.3.4\nsub15 1.2.3.4\nsub16 1.2.3.4\nsub17 1.2.3.4\nsub18 1.2.3.4\n@ 1.2.3.4", + "nameserver": "auth1.artfiles.de.\nauth2.artfiles.de.", + "CName": "some cname.to.example.tld.", + "TLSA": "_25._tcp.mail.example.tld. 2 1 1 CBBC559B44D524D6A132BDAC672744DA3407F12AAE5D5F722C5F6C7913871C75\n_25._tcp.mail.example.tld. 2 1 1 885BF0572252C6741DC9A52F5044487FEF2A93B811CDEDFAD7624CC283B7CDD5\n_25._tcp.mail.example.tld. 2 1 1 F1440A9B76E1E41E53A4CB461329BF6337B419726BE513E42E19F1C691C5D4B2\n_465._tcp.mail.example.tld. 2 1 1 CBBC559B44D524D6A132BDAC672744DA3407F12AAE5D5F722C5F6C7913871C75\n_465._tcp.mail.example.tld. 2 1 1 885BF0572252C6741DC9A52F5044487FEF2A93B811CDEDFAD7624CC283B7CDD5\n_465._tcp.mail.example.tld. 2 1 1 F1440A9B76E1E41E53A4CB461329BF6337B419726BE513E42E19F1C691C5D4B2\n_587._tcp.mail.example.tld. 2 1 1 CBBC559B44D524D6A132BDAC672744DA3407F12AAE5D5F722C5F6C7913871C75\n_587._tcp.mail.example.tld. 2 1 1 885BF0572252C6741DC9A52F5044487FEF2A93B811CDEDFAD7624CC283B7CDD5\n_587._tcp.mail.example.tld. 2 1 1 F1440A9B76E1E41E53A4CB461329BF6337B419726BE513E42E19F1C691C5D4B2" + }, + "status": "OK" +} diff --git a/providers/dns/artfiles/internal/fixtures/set_dns.json b/providers/dns/artfiles/internal/fixtures/set_dns.json new file mode 100644 index 000000000..7cacb33e5 --- /dev/null +++ b/providers/dns/artfiles/internal/fixtures/set_dns.json @@ -0,0 +1,4 @@ +{ + "status": "OK", + "error": "" +} diff --git a/providers/dns/artfiles/internal/fixtures/txt_record-multiple.txt b/providers/dns/artfiles/internal/fixtures/txt_record-multiple.txt new file mode 100644 index 000000000..461489c77 --- /dev/null +++ b/providers/dns/artfiles/internal/fixtures/txt_record-multiple.txt @@ -0,0 +1,8 @@ +_dmarc "v=DMARC1;p=reject;sp=reject;adkim=r;aspf=r;pct=100;rua=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;ri=86400;ruf=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;fo=1;rf=afrf" +_mta-sts "v=STSv1;id=yyyymmddTHHMMSS;" +_smtp._tls "v=TLSRPTv1;rua=mailto:someone@in.mailhardener.com" +@ "v=spf1 a mx ~all" +selector._domainkey "v=DKIM1;k=rsa;p=Base64Stuff" "MoreBase64Stuff" "Even++MoreBase64Stuff" "YesMoreBase64Stuff" "And+Yes+Even+MoreBase64Stuff" "Sure++MoreBase64Stuff" "LastBase64Stuff" +selectorecc._domainkey "v=DKIM1;k=ed25519;p=Base64Stuff" +_acme-challenge "xxx" +_acme-challenge "yyy" diff --git a/providers/dns/artfiles/internal/fixtures/txt_record.txt b/providers/dns/artfiles/internal/fixtures/txt_record.txt new file mode 100644 index 000000000..5a6259b14 --- /dev/null +++ b/providers/dns/artfiles/internal/fixtures/txt_record.txt @@ -0,0 +1,7 @@ +_dmarc "v=DMARC1;p=reject;sp=reject;adkim=r;aspf=r;pct=100;rua=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;ri=86400;ruf=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;fo=1;rf=afrf" +_mta-sts "v=STSv1;id=yyyymmddTHHMMSS;" +_smtp._tls "v=TLSRPTv1;rua=mailto:someone@in.mailhardener.com" +@ "v=spf1 a mx ~all" +selector._domainkey "v=DKIM1;k=rsa;p=Base64Stuff" "MoreBase64Stuff" "Even++MoreBase64Stuff" "YesMoreBase64Stuff" "And+Yes+Even+MoreBase64Stuff" "Sure++MoreBase64Stuff" "LastBase64Stuff" +selectorecc._domainkey "v=DKIM1;k=ed25519;p=Base64Stuff" +_acme-challenge "TheAcmeChallenge" diff --git a/providers/dns/artfiles/internal/types.go b/providers/dns/artfiles/internal/types.go new file mode 100644 index 000000000..c70ab34da --- /dev/null +++ b/providers/dns/artfiles/internal/types.go @@ -0,0 +1,109 @@ +package internal + +import ( + "encoding/csv" + "encoding/json" + "errors" + "io" + "maps" + "slices" + "strconv" + "strings" + "unicode" +) + +type Records struct { + Data map[string]json.RawMessage `json:"data"` + Status string `json:"status"` +} + +type RecordValue map[string][]string + +func (r RecordValue) Set(key, value string) { + r[key] = []string{strconv.Quote(value)} +} + +func (r RecordValue) Add(key, value string) { + r[key] = append(r[key], strconv.Quote(value)) +} + +func (r RecordValue) Delete(key string) { + delete(r, key) +} + +func (r RecordValue) RemoveValue(key, value string) { + if len(r[key]) == 0 { + return + } + + quotedValue := strconv.Quote(value) + + var data []string + + for _, s := range r[key] { + if s != quotedValue { + data = append(data, s) + } + } + + r[key] = data + + if len(r[key]) == 0 { + r.Delete(key) + } +} + +func (r RecordValue) String() string { + var parts []string + + for _, key := range slices.Sorted(maps.Keys(r)) { + for _, s := range r[key] { + parts = append(parts, key+" "+s) + } + } + + return strings.Join(parts, "\n") +} + +func ParseRecordValue(lines string) RecordValue { + data := make(RecordValue) + + for line := range strings.Lines(lines) { + line = strings.TrimSpace(line) + + idx := strings.IndexFunc(line, unicode.IsSpace) + + data[line[:idx]] = append(data[line[:idx]], line[idx+1:]) + } + + return data +} + +func parseDomains(input string) ([]string, error) { + reader := csv.NewReader(strings.NewReader(input)) + reader.Comma = '\t' + reader.TrimLeadingSpace = true + reader.LazyQuotes = true + + var data []string + + for { + record, err := reader.Read() + if errors.Is(err, io.EOF) { + break + } + + if err != nil { + return nil, err + } + + if len(record) < 1 { + // Malformed line + continue + } + + data = append(data, record[0]) + } + + return data, nil +} diff --git a/providers/dns/artfiles/internal/types_test.go b/providers/dns/artfiles/internal/types_test.go new file mode 100644 index 000000000..3b219f39f --- /dev/null +++ b/providers/dns/artfiles/internal/types_test.go @@ -0,0 +1,183 @@ +package internal + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRecordValue_Set(t *testing.T) { + rv := make(RecordValue) + + rv.Set("a", "1") + rv.Set("b", "2") + rv.Set("b", "3") + + assert.Equal(t, "a \"1\"\nb \"3\"", rv.String()) +} + +func TestRecordValue_Add(t *testing.T) { + rv := make(RecordValue) + + rv.Add("a", "1") + rv.Add("b", "2") + rv.Add("b", "3") + + assert.Equal(t, "a \"1\"\nb \"2\"\nb \"3\"", rv.String()) +} + +func TestRecordValue_Delete(t *testing.T) { + rv := make(RecordValue) + + rv.Set("a", "1") + rv.Add("b", "2") + + rv.Delete("b") + + assert.Equal(t, "a \"1\"", rv.String()) +} + +func TestRecordValue_RemoveValue(t *testing.T) { + testCases := []struct { + desc string + data map[string][]string + toRemove map[string][]string + expected string + }{ + { + desc: "remove the only value", + data: map[string][]string{ + "a": {"1"}, + }, + toRemove: map[string][]string{ + "a": {"1"}, + }, + expected: ``, + }, + { + desc: "remove value in the middle", + data: map[string][]string{ + "a": {"1", "2", "3"}, + }, + toRemove: map[string][]string{ + "a": {"2"}, + }, + expected: "a \"1\"\na \"3\"", + }, + { + desc: "remove value at the beginning", + data: map[string][]string{ + "a": {"1", "2", "3"}, + }, + toRemove: map[string][]string{ + "a": {"1"}, + }, + expected: "a \"2\"\na \"3\"", + }, + { + desc: "remove value at the end", + data: map[string][]string{ + "a": {"1", "2", "3"}, + }, + toRemove: map[string][]string{ + "a": {"3"}, + }, + expected: "a \"1\"\na \"2\"", + }, + { + desc: "remove all (delete)", + data: map[string][]string{ + "a": {"1", "2", "3"}, + }, + toRemove: map[string][]string{ + "a": {"1", "2", "3"}, + }, + expected: ``, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + rv := make(RecordValue) + + for k, values := range test.data { + for _, v := range values { + rv.Add(k, v) + } + } + + for k, values := range test.toRemove { + for _, v := range values { + rv.RemoveValue(k, v) + } + } + + assert.Equal(t, test.expected, rv.String()) + }) + } +} + +func TestParseRecordValue(t *testing.T) { + testCases := []struct { + desc string + filename string + expected RecordValue + }{ + { + desc: "simple", + filename: "txt_record.txt", + expected: RecordValue{ + "@": []string{"\"v=spf1 a mx ~all\""}, + "_acme-challenge": []string{"\"TheAcmeChallenge\""}, + "_dmarc": []string{"\"v=DMARC1;p=reject;sp=reject;adkim=r;aspf=r;pct=100;rua=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;ri=86400;ruf=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;fo=1;rf=afrf\""}, + "_mta-sts": []string{"\"v=STSv1;id=yyyymmddTHHMMSS;\""}, + "_smtp._tls": []string{"\"v=TLSRPTv1;rua=mailto:someone@in.mailhardener.com\""}, + "selector._domainkey": []string{"\"v=DKIM1;k=rsa;p=Base64Stuff\" \"MoreBase64Stuff\" \"Even++MoreBase64Stuff\" \"YesMoreBase64Stuff\" \"And+Yes+Even+MoreBase64Stuff\" \"Sure++MoreBase64Stuff\" \"LastBase64Stuff\""}, + "selectorecc._domainkey": []string{"\"v=DKIM1;k=ed25519;p=Base64Stuff\""}, + }, + }, + { + desc: "multiple values with the same key", + filename: "txt_record-multiple.txt", + expected: RecordValue{ + "@": []string{"\"v=spf1 a mx ~all\""}, + "_acme-challenge": []string{"\"xxx\"", "\"yyy\""}, + "_dmarc": []string{"\"v=DMARC1;p=reject;sp=reject;adkim=r;aspf=r;pct=100;rua=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;ri=86400;ruf=mailto:someone@in.mailhardener.com,mailto:postmaster@example.tld;fo=1;rf=afrf\""}, + "_mta-sts": []string{"\"v=STSv1;id=yyyymmddTHHMMSS;\""}, + "_smtp._tls": []string{"\"v=TLSRPTv1;rua=mailto:someone@in.mailhardener.com\""}, + "selector._domainkey": []string{"\"v=DKIM1;k=rsa;p=Base64Stuff\" \"MoreBase64Stuff\" \"Even++MoreBase64Stuff\" \"YesMoreBase64Stuff\" \"And+Yes+Even+MoreBase64Stuff\" \"Sure++MoreBase64Stuff\" \"LastBase64Stuff\""}, + "selectorecc._domainkey": []string{"\"v=DKIM1;k=ed25519;p=Base64Stuff\""}, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + file, err := os.ReadFile(filepath.Join("fixtures", test.filename)) + require.NoError(t, err) + + data := ParseRecordValue(string(file)) + + assert.Equal(t, test.expected, data) + }) + } +} + +func Test_parseDomains(t *testing.T) { + file, err := os.ReadFile(filepath.FromSlash("./fixtures/domains.txt")) + require.NoError(t, err) + + domains, err := parseDomains(string(file)) + require.NoError(t, err) + + expected := []string{"example.com", "example.org", "example.net"} + + assert.Equal(t, expected, domains) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 10fda2df1..24474cf2f 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -13,6 +13,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/allinkl" "github.com/go-acme/lego/v4/providers/dns/alwaysdata" "github.com/go-acme/lego/v4/providers/dns/anexia" + "github.com/go-acme/lego/v4/providers/dns/artfiles" "github.com/go-acme/lego/v4/providers/dns/arvancloud" "github.com/go-acme/lego/v4/providers/dns/auroradns" "github.com/go-acme/lego/v4/providers/dns/autodns" @@ -211,6 +212,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return alwaysdata.NewDNSProvider() case "anexia": return anexia.NewDNSProvider() + case "artfiles": + return artfiles.NewDNSProvider() case "arvancloud": return arvancloud.NewDNSProvider() case "auroradns": From dd1ea80c08bb2a3551590f64ca40fc1fb2a7eb21 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 19 Feb 2026 12:52:04 +0100 Subject: [PATCH 288/298] Add DNS provider for Leaseweb (#2856) --- README.md | 44 ++-- cmd/zz_gen_cmd_dnshelp.go | 21 ++ docs/content/dns/zz_gen_leaseweb.md | 67 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/leaseweb/internal/client.go | 216 ++++++++++++++++++ .../dns/leaseweb/internal/client_test.go | 149 ++++++++++++ .../createResourceRecordSet-request.json | 8 + .../fixtures/createResourceRecordSet.json | 17 ++ .../leaseweb/internal/fixtures/error_400.json | 6 + .../leaseweb/internal/fixtures/error_401.json | 5 + .../leaseweb/internal/fixtures/error_404.json | 5 + .../fixtures/getResourceRecordSet.json | 18 ++ .../fixtures/getResourceRecordSet2.json | 17 ++ .../updateResourceRecordSet-request.json | 8 + .../updateResourceRecordSet-request2.json | 6 + .../fixtures/updateResourceRecordSet.json | 19 ++ providers/dns/leaseweb/internal/types.go | 35 +++ providers/dns/leaseweb/leaseweb.go | 187 +++++++++++++++ providers/dns/leaseweb/leaseweb.toml | 22 ++ providers/dns/leaseweb/leaseweb_test.go | 204 +++++++++++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 21 files changed, 1036 insertions(+), 23 deletions(-) create mode 100644 docs/content/dns/zz_gen_leaseweb.md create mode 100644 providers/dns/leaseweb/internal/client.go create mode 100644 providers/dns/leaseweb/internal/client_test.go create mode 100644 providers/dns/leaseweb/internal/fixtures/createResourceRecordSet-request.json create mode 100644 providers/dns/leaseweb/internal/fixtures/createResourceRecordSet.json create mode 100644 providers/dns/leaseweb/internal/fixtures/error_400.json create mode 100644 providers/dns/leaseweb/internal/fixtures/error_401.json create mode 100644 providers/dns/leaseweb/internal/fixtures/error_404.json create mode 100644 providers/dns/leaseweb/internal/fixtures/getResourceRecordSet.json create mode 100644 providers/dns/leaseweb/internal/fixtures/getResourceRecordSet2.json create mode 100644 providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet-request.json create mode 100644 providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet-request2.json create mode 100644 providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet.json create mode 100644 providers/dns/leaseweb/internal/types.go create mode 100644 providers/dns/leaseweb/leaseweb.go create mode 100644 providers/dns/leaseweb/leaseweb.toml create mode 100644 providers/dns/leaseweb/leaseweb_test.go diff --git a/README.md b/README.md index 7e015e70f..9925979bd 100644 --- a/README.md +++ b/README.md @@ -188,113 +188,113 @@ If your DNS provider is not supported, please open an [issue](https://github.com Joohoi's ACME-DNS KeyHelp + Leaseweb Liara - Lima-City + Lima-City Linode (v4) Liquid Web Loopia - LuaDNS + LuaDNS Mail-in-a-Box ManageEngine CloudDNS Manual - Metaname + Metaname Metaregistrar mijn.host Mittwald - myaddr.{tools,dev,io} + myaddr.{tools,dev,io} MyDNS.jp MythicBeasts Name.com - Namecheap + Namecheap Namesilo NearlyFreeSpeech.NET Neodigit - Netcup + Netcup Netlify Nicmanager NIFCloud - Njalla + Njalla Nodion NS1 Octenium - Open Telekom Cloud + Open Telekom Cloud Oracle Cloud OVH plesk.com - Porkbun + Porkbun PowerDNS Rackspace Rain Yun/雨云 - RcodeZero + RcodeZero reg.ru Regfish RFC2136 - RimuHosting + RimuHosting RU CENTER Sakura Cloud Scaleway - Selectel + Selectel Selectel v2 SelfHost.(de|eu) Servercow - Shellrent + Shellrent Simply.com Sonic Spaceship - Stackpath + Stackpath Syse Technitium Tencent Cloud DNS - Tencent EdgeOne + Tencent EdgeOne Timeweb Cloud TodayNIC/时代互联 TransIP - UKFast SafeDNS + UKFast SafeDNS Ultradns United-Domains Variomedia - VegaDNS + VegaDNS Vercel Versio.[nl|eu|uk] VinylDNS - Virtualname + Virtualname VK Cloud Volcano Engine/火山引擎 Vscale - Vultr + Vultr webnames.ca webnames.ru Websupport - WEDOS + WEDOS West.cn/西部数码 Yandex 360 Yandex Cloud - Yandex PDD + Yandex PDD Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 9e83a7b25..161729c79 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -112,6 +112,7 @@ func allDNSCodes() string { "jdcloud", "joker", "keyhelp", + "leaseweb", "liara", "lightsail", "limacity", @@ -2358,6 +2359,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/keyhelp`) + case "leaseweb": + // generated from: providers/dns/leaseweb/leaseweb.toml + ew.writeln(`Configuration for Leaseweb.`) + ew.writeln(`Code: 'leaseweb'`) + ew.writeln(`Since: 'v4.32.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "LEASEWEB_API_KEY": API key`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "LEASEWEB_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "LEASEWEB_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "LEASEWEB_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "LEASEWEB_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/leaseweb`) + case "liara": // generated from: providers/dns/liara/liara.toml ew.writeln(`Configuration for Liara.`) diff --git a/docs/content/dns/zz_gen_leaseweb.md b/docs/content/dns/zz_gen_leaseweb.md new file mode 100644 index 000000000..13ded490a --- /dev/null +++ b/docs/content/dns/zz_gen_leaseweb.md @@ -0,0 +1,67 @@ +--- +title: "Leaseweb" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: leaseweb +dnsprovider: + since: "v4.32.0" + code: "leaseweb" + url: "https://www.leaseweb.com/en/" +--- + + + + + + +Configuration for [Leaseweb](https://www.leaseweb.com/en/). + + + + +- Code: `leaseweb` +- Since: v4.32.0 + + +Here is an example bash command using the Leaseweb provider: + +```bash +LEASEWEB_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ +lego --dns leaseweb -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `LEASEWEB_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 | +|--------------------------------|-------------| +| `LEASEWEB_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `LEASEWEB_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `LEASEWEB_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `LEASEWEB_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://developer.leaseweb.com/docs/#tag/DNS) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index c6d845bf2..925ef0b21 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, artfiles, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bluecatv2, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, ddnss, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, namesurfer, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, artfiles, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bluecatv2, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, ddnss, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, leaseweb, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, namesurfer, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/leaseweb/internal/client.go b/providers/dns/leaseweb/internal/client.go new file mode 100644 index 000000000..01619d49b --- /dev/null +++ b/providers/dns/leaseweb/internal/client.go @@ -0,0 +1,216 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" +) + +const defaultBaseURL = "https://api.leaseweb.com/hosting/v2" + +const AuthHeader = "X-LSW-Auth" + +// Client the Leaseweb API client. +type Client struct { + apiKey string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(apiKey string) (*Client, error) { + if apiKey == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + apiKey: apiKey, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +// CreateRRSet creates a resource record set. +// https://developer.leaseweb.com/docs/#tag/DNS/operation/createResourceRecordSet +func (c *Client) CreateRRSet(ctx context.Context, domainName string, rrset RRSet) (*RRSet, error) { + endpoint := c.BaseURL.JoinPath("domains", domainName, "resourceRecordSets") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, rrset) + if err != nil { + return nil, err + } + + result := &RRSet{} + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result, nil +} + +// GetRRSet gets a resource record set. +// https://developer.leaseweb.com/docs/#tag/DNS/operation/getResourceRecordSet +func (c *Client) GetRRSet(ctx context.Context, domainName, name, rType string) (*RRSet, error) { + endpoint := c.BaseURL.JoinPath("domains", domainName, "resourceRecordSets", name, rType) + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + result := &RRSet{} + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result, nil +} + +// UpdateRRSet updates a resource record set. +// https://developer.leaseweb.com/docs/#tag/DNS/operation/updateResourceRecordSet +func (c *Client) UpdateRRSet(ctx context.Context, domainName string, rrset RRSet) (*RRSet, error) { + endpoint := c.BaseURL.JoinPath("domains", domainName, "resourceRecordSets", rrset.Name, rrset.Type) + + // Reset values that are not allowed to be updated. + rrset.Name = "" + rrset.Type = "" + rrset.Editable = false + + req, err := newJSONRequest(ctx, http.MethodPut, endpoint, rrset) + if err != nil { + return nil, err + } + + result := &RRSet{} + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result, nil +} + +// DeleteRRSet deletes a resource record set. +// https://developer.leaseweb.com/docs/#tag/DNS/operation/deleteResourceRecordSet +func (c *Client) DeleteRRSet(ctx context.Context, domainName, name, rType string) error { + endpoint := c.BaseURL.JoinPath("domains", domainName, "resourceRecordSets", name, rType) + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) do(req *http.Request, result any) error { + useragent.SetHeader(req.Header) + + req.Header.Add(AuthHeader, c.apiKey) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + + err := json.Unmarshal(raw, &errAPI) + if err != nil { + if resp.StatusCode == http.StatusNotFound { + return &NotFoundError{APIError{ + CorrelationID: resp.Header.Get("Correlation-Id"), + ErrorCode: strconv.Itoa(http.StatusNotFound), + ErrorMessage: string(raw), + }} + } + + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + if errAPI.ErrorCode == strconv.Itoa(http.StatusNotFound) { + return &NotFoundError{APIError: errAPI} + } + + return &errAPI +} + +// TTLRounder rounds the given TTL in seconds to the next accepted value. +// Accepted TTL values are: 60, 300, 1800, 3600, 14400, 28800, 43200, 86400. +func TTLRounder(ttl int) int { + for _, validTTL := range []int{60, 300, 1800, 3600, 14400, 28800, 43200, 86400} { + if ttl <= validTTL { + return validTTL + } + } + + return 3600 +} diff --git a/providers/dns/leaseweb/internal/client_test.go b/providers/dns/leaseweb/internal/client_test.go new file mode 100644 index 000000000..5762aad4b --- /dev/null +++ b/providers/dns/leaseweb/internal/client_test.go @@ -0,0 +1,149 @@ +package internal + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("secret") + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With(AuthHeader, "secret"), + ) +} + +func TestClient_CreateRRSet(t *testing.T) { + client := mockBuilder(). + Route("POST /domains/example.com/resourceRecordSets", + servermock.ResponseFromFixture("createResourceRecordSet.json"), + servermock.CheckRequestJSONBodyFromFixture("createResourceRecordSet-request.json"), + ). + Build(t) + + rrset := RRSet{ + Content: []string{"ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"}, + Name: "_acme-challenge.example.com.", + TTL: 300, + Type: "TXT", + } + + result, err := client.CreateRRSet(t.Context(), "example.com", rrset) + require.NoError(t, err) + + expected := &RRSet{ + Content: []string{"ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"}, + Name: "_acme-challenge.example.com.", + Editable: true, + TTL: 300, + Type: "TXT", + } + + assert.Equal(t, expected, result) +} + +func TestClient_GetRRSet(t *testing.T) { + client := mockBuilder(). + Route("GET /domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT", + servermock.ResponseFromFixture("getResourceRecordSet.json"), + ). + Build(t) + + result, err := client.GetRRSet(t.Context(), "example.com", "_acme-challenge.example.com.", "TXT") + require.NoError(t, err) + + expected := &RRSet{ + Content: []string{"foo", "Now36o-3BmlB623-0c1qCIUmgWVVmDJb88KGl24pqpo"}, + Name: "_acme-challenge.example.com.", + Editable: true, + TTL: 3600, + Type: "TXT", + } + + assert.Equal(t, expected, result) +} + +func TestClient_GetRRSet_error_404(t *testing.T) { + client := mockBuilder(). + Route("GET /domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT", + servermock.ResponseFromFixture("error_404.json"). + WithStatusCode(http.StatusNotFound), + ). + Build(t) + + _, err := client.GetRRSet(t.Context(), "example.com", "_acme-challenge.example.com.", "TXT") + require.EqualError(t, err, "404: Resource not found (289346a1-3eaf-4da4-b707-62ef12eb08be)") + + target := &NotFoundError{} + require.ErrorAs(t, err, &target) +} + +func TestClient_UpdateRRSet(t *testing.T) { + client := mockBuilder(). + Route("PUT /domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT", + servermock.ResponseFromFixture("updateResourceRecordSet.json"), + servermock.CheckRequestJSONBodyFromFixture("updateResourceRecordSet-request.json"), + ). + Build(t) + + rrset := RRSet{ + Content: []string{"foo", "Now36o-3BmlB623-0c1qCIUmgWVVmDJb88KGl24pqpo", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"}, + Name: "_acme-challenge.example.com.", + TTL: 3600, + Type: "TXT", + } + + result, err := client.UpdateRRSet(t.Context(), "example.com", rrset) + require.NoError(t, err) + + expected := &RRSet{ + Content: []string{"foo", "Now36o-3BmlB623-0c1qCIUmgWVVmDJb88KGl24pqpo", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"}, + Name: "_acme-challenge.example.com.", + Editable: true, + TTL: 3600, + Type: "TXT", + } + + assert.Equal(t, expected, result) +} + +func TestClient_DeleteRRSet(t *testing.T) { + client := mockBuilder(). + Route("DELETE /domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT", + servermock.Noop(). + WithStatusCode(http.StatusNoContent), + ). + Build(t) + + err := client.DeleteRRSet(t.Context(), "example.com", "_acme-challenge.example.com.", "TXT") + require.NoError(t, err) +} + +func TestClient_DeleteRRSet_error(t *testing.T) { + client := mockBuilder(). + Route("DELETE /domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT", + servermock.ResponseFromFixture("error_401.json"). + WithStatusCode(http.StatusUnauthorized), + ). + Build(t) + + err := client.DeleteRRSet(t.Context(), "example.com", "_acme-challenge.example.com.", "TXT") + require.EqualError(t, err, "401: You are not authorized to view this resource. (289346a1-3eaf-4da4-b707-62ef12eb08be)") +} diff --git a/providers/dns/leaseweb/internal/fixtures/createResourceRecordSet-request.json b/providers/dns/leaseweb/internal/fixtures/createResourceRecordSet-request.json new file mode 100644 index 000000000..af53fcf04 --- /dev/null +++ b/providers/dns/leaseweb/internal/fixtures/createResourceRecordSet-request.json @@ -0,0 +1,8 @@ +{ + "content": [ + "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" + ], + "name": "_acme-challenge.example.com.", + "ttl": 300, + "type": "TXT" +} diff --git a/providers/dns/leaseweb/internal/fixtures/createResourceRecordSet.json b/providers/dns/leaseweb/internal/fixtures/createResourceRecordSet.json new file mode 100644 index 000000000..8ca040d63 --- /dev/null +++ b/providers/dns/leaseweb/internal/fixtures/createResourceRecordSet.json @@ -0,0 +1,17 @@ +{ + "_links": { + "self": { + "href": "/domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT" + }, + "collection": { + "href": "/domains/example.com/resourceRecordSets" + } + }, + "content": [ + "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" + ], + "editable": true, + "name": "_acme-challenge.example.com.", + "ttl": 300, + "type": "TXT" +} diff --git a/providers/dns/leaseweb/internal/fixtures/error_400.json b/providers/dns/leaseweb/internal/fixtures/error_400.json new file mode 100644 index 000000000..1a980b6bb --- /dev/null +++ b/providers/dns/leaseweb/internal/fixtures/error_400.json @@ -0,0 +1,6 @@ +{ + "correlationId": "289346a1-3eaf-4da4-b707-62ef12eb08be", + "errorCode": "400", + "errorDetails": {}, + "errorMessage": "The API could not interpret your request correctly." +} diff --git a/providers/dns/leaseweb/internal/fixtures/error_401.json b/providers/dns/leaseweb/internal/fixtures/error_401.json new file mode 100644 index 000000000..47d8a311d --- /dev/null +++ b/providers/dns/leaseweb/internal/fixtures/error_401.json @@ -0,0 +1,5 @@ +{ + "correlationId": "289346a1-3eaf-4da4-b707-62ef12eb08be", + "errorCode": "401", + "errorMessage": "You are not authorized to view this resource." +} diff --git a/providers/dns/leaseweb/internal/fixtures/error_404.json b/providers/dns/leaseweb/internal/fixtures/error_404.json new file mode 100644 index 000000000..1deaf5606 --- /dev/null +++ b/providers/dns/leaseweb/internal/fixtures/error_404.json @@ -0,0 +1,5 @@ +{ + "correlationId": "289346a1-3eaf-4da4-b707-62ef12eb08be", + "errorCode": "404", + "errorMessage": "Resource not found" +} diff --git a/providers/dns/leaseweb/internal/fixtures/getResourceRecordSet.json b/providers/dns/leaseweb/internal/fixtures/getResourceRecordSet.json new file mode 100644 index 000000000..fd48f60c6 --- /dev/null +++ b/providers/dns/leaseweb/internal/fixtures/getResourceRecordSet.json @@ -0,0 +1,18 @@ +{ + "_links": { + "self": { + "href": "/domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT" + }, + "collection": { + "href": "/domains/example.com/resourceRecordSets" + } + }, + "content": [ + "foo", + "Now36o-3BmlB623-0c1qCIUmgWVVmDJb88KGl24pqpo" + ], + "editable": true, + "name": "_acme-challenge.example.com.", + "ttl": 3600, + "type": "TXT" +} diff --git a/providers/dns/leaseweb/internal/fixtures/getResourceRecordSet2.json b/providers/dns/leaseweb/internal/fixtures/getResourceRecordSet2.json new file mode 100644 index 000000000..abf3fb4c3 --- /dev/null +++ b/providers/dns/leaseweb/internal/fixtures/getResourceRecordSet2.json @@ -0,0 +1,17 @@ +{ + "_links": { + "self": { + "href": "/domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT" + }, + "collection": { + "href": "/domains/example.com/resourceRecordSets" + } + }, + "content": [ + "Now36o-3BmlB623-0c1qCIUmgWVVmDJb88KGl24pqpo" + ], + "editable": true, + "name": "_acme-challenge.example.com.", + "ttl": 3600, + "type": "TXT" +} diff --git a/providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet-request.json b/providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet-request.json new file mode 100644 index 000000000..e781958c8 --- /dev/null +++ b/providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet-request.json @@ -0,0 +1,8 @@ +{ + "content": [ + "foo", + "Now36o-3BmlB623-0c1qCIUmgWVVmDJb88KGl24pqpo", + "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" + ], + "ttl": 3600 +} diff --git a/providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet-request2.json b/providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet-request2.json new file mode 100644 index 000000000..0acc314de --- /dev/null +++ b/providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet-request2.json @@ -0,0 +1,6 @@ +{ + "content": [ + "foo" + ], + "ttl": 3600 +} diff --git a/providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet.json b/providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet.json new file mode 100644 index 000000000..2b877982c --- /dev/null +++ b/providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet.json @@ -0,0 +1,19 @@ +{ + "_links": { + "self": { + "href": "/domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT" + }, + "collection": { + "href": "/domains/example.com/resourceRecordSets" + } + }, + "content": [ + "foo", + "Now36o-3BmlB623-0c1qCIUmgWVVmDJb88KGl24pqpo", + "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY" + ], + "editable": true, + "name": "_acme-challenge.example.com.", + "ttl": 3600, + "type": "TXT" +} diff --git a/providers/dns/leaseweb/internal/types.go b/providers/dns/leaseweb/internal/types.go new file mode 100644 index 000000000..7a4547584 --- /dev/null +++ b/providers/dns/leaseweb/internal/types.go @@ -0,0 +1,35 @@ +package internal + +import ( + "encoding/json" + "fmt" +) + +type NotFoundError struct { + APIError +} + +type APIError struct { + CorrelationID string `json:"correlationId,omitempty"` + ErrorCode string `json:"errorCode,omitempty"` + ErrorMessage string `json:"errorMessage,omitempty"` + ErrorDetails json.RawMessage `json:"errorDetails,omitempty"` +} + +func (a *APIError) Error() string { + msg := fmt.Sprintf("%s: %s (%s)", a.ErrorCode, a.ErrorMessage, a.CorrelationID) + + if len(a.ErrorDetails) > 0 { + msg += fmt.Sprintf(": %s", string(a.ErrorDetails)) + } + + return msg +} + +type RRSet struct { + Content []string `json:"content,omitempty"` + Name string `json:"name,omitempty"` + Editable bool `json:"editable,omitempty"` + TTL int `json:"ttl,omitempty"` + Type string `json:"type,omitempty"` +} diff --git a/providers/dns/leaseweb/leaseweb.go b/providers/dns/leaseweb/leaseweb.go new file mode 100644 index 000000000..fafaf1c4d --- /dev/null +++ b/providers/dns/leaseweb/leaseweb.go @@ -0,0 +1,187 @@ +// Package leaseweb implements a DNS provider for solving the DNS-01 challenge using Leaseweb. +package leaseweb + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" + "github.com/go-acme/lego/v4/providers/dns/leaseweb/internal" +) + +// Environment variables names. +const ( + envNamespace = "LEASEWEB_" + + 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for Leaseweb. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIKey) + if err != nil { + return nil, fmt.Errorf("leaseweb: %w", err) + } + + config := NewDefaultConfig() + config.APIKey = values[EnvAPIKey] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Leaseweb. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("leaseweb: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.APIKey) + if err != nil { + return nil, fmt.Errorf("leaseweb: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("leaseweb: could not find zone for domain %q: %w", domain, err) + } + + existingRRSet, err := d.client.GetRRSet(ctx, dns01.UnFqdn(authZone), info.EffectiveFQDN, "TXT") + if err != nil { + notfoundErr := &internal.NotFoundError{} + if !errors.As(err, ¬foundErr) { + return fmt.Errorf("leaseweb: get RRSet: %w", err) + } + + // Create the RRSet. + + rrset := internal.RRSet{ + Content: []string{info.Value}, + Name: info.EffectiveFQDN, + TTL: internal.TTLRounder(d.config.TTL), + Type: "TXT", + } + + _, err = d.client.CreateRRSet(ctx, dns01.UnFqdn(authZone), rrset) + if err != nil { + return fmt.Errorf("leaseweb: create RRSet: %w", err) + } + + return nil + } + + // Update the RRSet. + + existingRRSet.Content = append(existingRRSet.Content, info.Value) + + _, err = d.client.UpdateRRSet(ctx, dns01.UnFqdn(authZone), *existingRRSet) + if err != nil { + return fmt.Errorf("leaseweb: update RRSet: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("leaseweb: could not find zone for domain %q: %w", domain, err) + } + + existingRRSet, err := d.client.GetRRSet(ctx, dns01.UnFqdn(authZone), info.EffectiveFQDN, "TXT") + if err != nil { + return fmt.Errorf("leaseweb: get RRSet: %w", err) + } + + var content []string + + for _, s := range existingRRSet.Content { + if s != info.Value { + content = append(content, s) + } + } + + if len(content) == 0 { + err = d.client.DeleteRRSet(ctx, dns01.UnFqdn(authZone), info.EffectiveFQDN, "TXT") + if err != nil { + return fmt.Errorf("leaseweb: delete RRSet: %w", err) + } + + return nil + } + + existingRRSet.Content = content + + _, err = d.client.UpdateRRSet(ctx, dns01.UnFqdn(authZone), *existingRRSet) + if err != nil { + return fmt.Errorf("leaseweb: update RRSet: %w", err) + } + + 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/leaseweb/leaseweb.toml b/providers/dns/leaseweb/leaseweb.toml new file mode 100644 index 000000000..2c3503291 --- /dev/null +++ b/providers/dns/leaseweb/leaseweb.toml @@ -0,0 +1,22 @@ +Name = "Leaseweb" +Description = '''''' +URL = "https://www.leaseweb.com/en/" +Code = "leaseweb" +Since = "v4.32.0" + +Example = ''' +LEASEWEB_API_KEY="xxxxxxxxxxxxxxxxxxxxx" \ +lego --dns leaseweb -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + LEASEWEB_API_KEY = "API key" + [Configuration.Additional] + LEASEWEB_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + LEASEWEB_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + LEASEWEB_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + LEASEWEB_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://developer.leaseweb.com/docs/#tag/DNS" diff --git a/providers/dns/leaseweb/leaseweb_test.go b/providers/dns/leaseweb/leaseweb_test.go new file mode 100644 index 000000000..0450cd2c2 --- /dev/null +++ b/providers/dns/leaseweb/leaseweb_test.go @@ -0,0 +1,204 @@ +package leaseweb + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/go-acme/lego/v4/providers/dns/leaseweb/internal" + "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: "leaseweb: some credentials information are missing: LEASEWEB_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: "leaseweb: 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.APIKey = "secret" + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With(internal.AuthHeader, "secret"), + ) +} + +func TestDNSProvider_Present_create(t *testing.T) { + provider := mockBuilder(). + Route("GET /domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT", + servermock.ResponseFromInternal("error_404.json"). + WithStatusCode(http.StatusNotFound), + ). + Route("POST /domains/example.com/resourceRecordSets", + servermock.ResponseFromInternal("createResourceRecordSet.json"), + servermock.CheckRequestJSONBodyFromInternal("createResourceRecordSet-request.json"), + ). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_Present_update(t *testing.T) { + provider := mockBuilder(). + Route("GET /domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT", + servermock.ResponseFromInternal("getResourceRecordSet.json"), + ). + Route("PUT /domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT", + servermock.ResponseFromInternal("updateResourceRecordSet.json"), + servermock.CheckRequestJSONBodyFromInternal("updateResourceRecordSet-request.json"), + ). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp_delete(t *testing.T) { + provider := mockBuilder(). + Route("GET /domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT", + servermock.ResponseFromInternal("getResourceRecordSet2.json"), + ). + Route("DELETE /domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT", + servermock.Noop(). + WithStatusCode(http.StatusNoContent), + ). + Build(t) + + err := provider.CleanUp("example.com", "abc", "1234d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp_update(t *testing.T) { + provider := mockBuilder(). + Route("GET /domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT", + servermock.ResponseFromInternal("getResourceRecordSet.json"), + ). + Route("PUT /domains/example.com/resourceRecordSets/_acme-challenge.example.com./TXT", + servermock.ResponseFromInternal("updateResourceRecordSet.json"), + servermock.CheckRequestJSONBodyFromInternal("updateResourceRecordSet-request2.json"), + ). + Build(t) + + err := provider.CleanUp("example.com", "abc", "1234d==") + require.NoError(t, err) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 24474cf2f..e1b2cc989 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -106,6 +106,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/jdcloud" "github.com/go-acme/lego/v4/providers/dns/joker" "github.com/go-acme/lego/v4/providers/dns/keyhelp" + "github.com/go-acme/lego/v4/providers/dns/leaseweb" "github.com/go-acme/lego/v4/providers/dns/liara" "github.com/go-acme/lego/v4/providers/dns/lightsail" "github.com/go-acme/lego/v4/providers/dns/limacity" @@ -398,6 +399,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return joker.NewDNSProvider() case "keyhelp": return keyhelp.NewDNSProvider() + case "leaseweb": + return leaseweb.NewDNSProvider() case "liara": return liara.NewDNSProvider() case "lightsail": From c50918c54ee9d6797587158241d5faf40513fec4 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 19 Feb 2026 12:55:08 +0100 Subject: [PATCH 289/298] Prepare release v4.32.0 --- CHANGELOG.md | 30 +++++++++++++++++++ acme/api/internal/sender/useragent.go | 4 +-- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 4 +-- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee191cdb4..ae73f70f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,36 @@ Everybody thinks that the others will donate, but in the end, nobody does. So if you think that lego is worth it, please consider [donating](https://donate.ldez.dev). +## v4.32.0 + +- Release date: 2026-02-19 +- Tag: [v4.32.0](https://github.com/go-acme/lego/releases/tag/v4.32.0) + +### Added + +- **[dnsprovider]** Add DNS provider for ArtFiles +- **[dnsprovider]** Add DNS provider for Leaseweb +- **[dnsprovider]** Add DNS provider for FusionLayer NameSurfer +- **[dnsprovider]** Add DNS provider for DDNSS +- **[dnsprovider]** Add DNS provider for Bluecat v2 +- **[dnsprovider]** Add DNS provider for TodayNIC/时代互联 +- **[dnsprovider]** Add DNS provider for DNSExit +- **[dnsprovider]** alidns: add line record option + +### Changed + +- **[dnsprovider]** azure: reinforces deprecation +- **[dnsprovider]** allinkl: detect zone through API + +### Fixed + +- **[ari]** fix: implement parsing for Retry-After header according to RFC 7231 +- **[dnsprovider]** namesurfer: fix updateDNSHost +- **[dnsprovider]** timewebcloud: fix subdomain support +- **[dnsprovider]** fix: deduplicate authz for DNS01 challenge +- **[lib,cli]** fix: use IPs to define the main domain +- **[lib]** fix: preserve domain order + ## v4.31.0 - Release date: 2026-01-08 diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 570b3b67c..feda18cc7 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -4,10 +4,10 @@ package sender const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "xenolf-acme/4.31.0" + ourUserAgent = "xenolf-acme/4.32.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index 57899e15e..f0ca21326 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.31.0+dev-detach" +const defaultVersion = "v4.32.0+dev-release" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index da24329cc..43e77b23d 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -10,12 +10,12 @@ import ( const ( // ourUserAgent is the User-Agent of this underlying library package. - ourUserAgent = "goacme-lego/4.31.0" + ourUserAgent = "goacme-lego/4.32.0" // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "detach" + ourUserAgentComment = "release" ) // Get builds and returns the User-Agent string. From 4547c4317e7a3158d1927aa0c6b887404d7e8ac9 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 19 Feb 2026 12:55:20 +0100 Subject: [PATCH 290/298] Detach v4.32.0 --- acme/api/internal/sender/useragent.go | 2 +- cmd/lego/zz_gen_version.go | 2 +- providers/dns/internal/useragent/useragent.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index feda18cc7..51a1b4770 100644 --- a/acme/api/internal/sender/useragent.go +++ b/acme/api/internal/sender/useragent.go @@ -9,5 +9,5 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index f0ca21326..cf9ad00ef 100644 --- a/cmd/lego/zz_gen_version.go +++ b/cmd/lego/zz_gen_version.go @@ -2,7 +2,7 @@ package main -const defaultVersion = "v4.32.0+dev-release" +const defaultVersion = "v4.32.0+dev-detach" var version = "" diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 43e77b23d..090c9109a 100644 --- a/providers/dns/internal/useragent/useragent.go +++ b/providers/dns/internal/useragent/useragent.go @@ -15,7 +15,7 @@ const ( // ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package. // values: detach|release // NOTE: Update this with each tagged release. - ourUserAgentComment = "release" + ourUserAgentComment = "detach" ) // Get builds and returns the User-Agent string. From 7d459b59c5882aac5cd8545cbda77e18852f5cd3 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 22 Feb 2026 14:14:18 +0100 Subject: [PATCH 291/298] liara: add support for team ID (#2867) --- cmd/zz_gen_cmd_dnshelp.go | 1 + docs/content/dns/zz_gen_liara.md | 1 + providers/dns/liara/internal/client.go | 27 +++++++--- providers/dns/liara/internal/client_test.go | 55 ++++++++++++++++++--- providers/dns/liara/liara.go | 7 ++- providers/dns/liara/liara.toml | 1 + 6 files changed, 76 insertions(+), 16 deletions(-) diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 161729c79..8e3b4ebce 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -2394,6 +2394,7 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln(` - "LIARA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) ew.writeln(` - "LIARA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) ew.writeln(` - "LIARA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "LIARA_TEAM_ID": The team ID to access services in a team`) ew.writeln(` - "LIARA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)`) ew.writeln() diff --git a/docs/content/dns/zz_gen_liara.md b/docs/content/dns/zz_gen_liara.md index 8a6ddbd99..658ce8077 100644 --- a/docs/content/dns/zz_gen_liara.md +++ b/docs/content/dns/zz_gen_liara.md @@ -50,6 +50,7 @@ More information [here]({{% ref "dns#configuration-and-credentials" %}}). | `LIARA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | | `LIARA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | | `LIARA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `LIARA_TEAM_ID` | The team ID to access services in a team | | `LIARA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600) | The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. diff --git a/providers/dns/liara/internal/client.go b/providers/dns/liara/internal/client.go index 93cdcf7c8..95c39695b 100644 --- a/providers/dns/liara/internal/client.go +++ b/providers/dns/liara/internal/client.go @@ -20,17 +20,23 @@ const defaultBaseURL = "https://dns-service.iran.liara.ir" type Client struct { baseURL *url.URL httpClient *http.Client + + teamID string } // NewClient creates a new Client. -func NewClient(hc *http.Client) *Client { +func NewClient(hc *http.Client, teamID string) *Client { baseURL, _ := url.Parse(defaultBaseURL) if hc == nil { hc = &http.Client{Timeout: 10 * time.Second} } - return &Client{httpClient: hc, baseURL: baseURL} + return &Client{ + httpClient: hc, + baseURL: baseURL, + teamID: teamID, + } } // GetRecords gets the records of a domain. @@ -38,7 +44,7 @@ func NewClient(hc *http.Client) *Client { func (c *Client) GetRecords(ctx context.Context, domainName string) ([]Record, error) { endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records") - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + req, err := c.newJSONRequest(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, fmt.Errorf("create request: %w", err) } @@ -73,7 +79,7 @@ func (c *Client) GetRecords(ctx context.Context, domainName string) ([]Record, e func (c *Client) CreateRecord(ctx context.Context, domainName string, record Record) (*Record, error) { endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records") - req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) + req, err := c.newJSONRequest(ctx, http.MethodPost, endpoint, record) if err != nil { return nil, fmt.Errorf("create request: %w", err) } @@ -108,7 +114,7 @@ func (c *Client) CreateRecord(ctx context.Context, domainName string, record Rec func (c *Client) GetRecord(ctx context.Context, domainName, recordID string) (*Record, error) { endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records", recordID) - req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + req, err := c.newJSONRequest(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, fmt.Errorf("create request: %w", err) } @@ -143,7 +149,7 @@ func (c *Client) GetRecord(ctx context.Context, domainName, recordID string) (*R func (c *Client) DeleteRecord(ctx context.Context, domainName, recordID string) error { endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records", recordID) - req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + req, err := c.newJSONRequest(ctx, http.MethodDelete, endpoint, nil) if err != nil { return fmt.Errorf("create request: %w", err) } @@ -162,7 +168,14 @@ func (c *Client) DeleteRecord(ctx context.Context, domainName, recordID string) return nil } -func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { +func (c *Client) newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + if c.teamID != "" { + query := endpoint.Query() + query.Set("teamID", c.teamID) + + endpoint.RawQuery = query.Encode() + } + buf := new(bytes.Buffer) if payload != nil { diff --git a/providers/dns/liara/internal/client_test.go b/providers/dns/liara/internal/client_test.go index 57ac7e8b3..b6d007046 100644 --- a/providers/dns/liara/internal/client_test.go +++ b/providers/dns/liara/internal/client_test.go @@ -13,10 +13,10 @@ import ( const apiKey = "key" -func mockBuilder() *servermock.Builder[*Client] { +func mockBuilder(teamID string) *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { - client := NewClient(OAuthStaticAccessToken(server.Client(), apiKey)) + client := NewClient(OAuthStaticAccessToken(server.Client(), apiKey), teamID) client.baseURL, _ = url.Parse(server.URL) return client, nil @@ -26,7 +26,7 @@ func mockBuilder() *servermock.Builder[*Client] { } func TestClient_GetRecords(t *testing.T) { - client := mockBuilder(). + client := mockBuilder(""). Route("GET /api/v1/zones/example.com/dns-records", servermock.ResponseFromFixture("RecordsResponse.json")). Build(t) @@ -50,7 +50,7 @@ func TestClient_GetRecords(t *testing.T) { } func TestClient_GetRecord(t *testing.T) { - client := mockBuilder(). + client := mockBuilder(""). Route("GET /api/v1/zones/example.com/dns-records/123", servermock.ResponseFromFixture("RecordResponse.json")). Build(t) @@ -72,7 +72,7 @@ func TestClient_GetRecord(t *testing.T) { } func TestClient_CreateRecord(t *testing.T) { - client := mockBuilder(). + client := mockBuilder(""). Route("POST /api/v1/zones/example.com/dns-records", servermock.ResponseFromFixture("RecordResponse.json"). WithStatusCode(http.StatusCreated), @@ -108,8 +108,47 @@ func TestClient_CreateRecord(t *testing.T) { assert.Equal(t, expected, record) } +func TestClient_CreateRecord_withTeamID(t *testing.T) { + client := mockBuilder("123"). + Route("POST /api/v1/zones/example.com/dns-records", + servermock.ResponseFromFixture("RecordResponse.json"). + WithStatusCode(http.StatusCreated), + servermock.CheckRequestJSONBody(`{"name":"string","type":"string","ttl":3600,"contents":[{"text":"string"}]}`), + servermock.CheckQueryParameter().Strict().With("teamID", "123"), + ). + Build(t) + + data := Record{ + Type: "string", + Name: "string", + Contents: []Content{ + { + Text: "string", + }, + }, + TTL: 3600, + } + + record, err := client.CreateRecord(t.Context(), "example.com", data) + require.NoError(t, err) + + expected := &Record{ + ID: "string", + Type: "string", + Name: "string", + Contents: []Content{ + { + Text: "string", + }, + }, + TTL: 3600, + } + + assert.Equal(t, expected, record) +} + func TestClient_DeleteRecord(t *testing.T) { - client := mockBuilder(). + client := mockBuilder(""). Route("DELETE /api/v1/zones/example.com/dns-records/123", servermock.Noop(). WithStatusCode(http.StatusNoContent)). @@ -120,7 +159,7 @@ func TestClient_DeleteRecord(t *testing.T) { } func TestClient_DeleteRecord_NotFound_Response(t *testing.T) { - client := mockBuilder(). + client := mockBuilder(""). Route("DELETE /api/v1/zones/example.com/dns-records/123", servermock.Noop(). WithStatusCode(http.StatusNotFound)). @@ -131,7 +170,7 @@ func TestClient_DeleteRecord_NotFound_Response(t *testing.T) { } func TestClient_DeleteRecord_error(t *testing.T) { - client := mockBuilder(). + client := mockBuilder(""). Route("DELETE /api/v1/zones/example.com/dns-records/123", servermock.ResponseFromFixture("error.json"). WithStatusCode(http.StatusUnauthorized)). diff --git a/providers/dns/liara/liara.go b/providers/dns/liara/liara.go index b91b004cc..c7e403eed 100644 --- a/providers/dns/liara/liara.go +++ b/providers/dns/liara/liara.go @@ -23,6 +23,7 @@ const ( envNamespace = "LIARA_" EnvAPIKey = envNamespace + "API_KEY" + EnvTeamID = envNamespace + "TEAM_ID" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -39,7 +40,9 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - APIKey string + APIKey string + TeamID string + TTL int PropagationTimeout time.Duration PollingInterval time.Duration @@ -77,6 +80,7 @@ func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() config.APIKey = values[EnvAPIKey] + config.TeamID = env.GetOrFile(EnvTeamID) return NewDNSProviderConfig(config) } @@ -112,6 +116,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { clientdebug.Wrap( internal.OAuthStaticAccessToken(retryClient.StandardClient(), config.APIKey), ), + config.TeamID, ) return &DNSProvider{ diff --git a/providers/dns/liara/liara.toml b/providers/dns/liara/liara.toml index 4ed53ec75..f471de04e 100644 --- a/providers/dns/liara/liara.toml +++ b/providers/dns/liara/liara.toml @@ -13,6 +13,7 @@ lego --dns liara -d '*.example.com' -d example.com run [Configuration.Credentials] LIARA_API_KEY = "The API key" [Configuration.Additional] + LIARA_TEAM_ID = "The team ID to access services in a team" LIARA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" LIARA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" LIARA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 3600)" From 491dcaad1d4b77f3ec703a581e9a8d900869953d Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 25 Feb 2026 23:12:43 +0100 Subject: [PATCH 292/298] feat: allow to Unwrap obtainError (#2874) --- challenge/resolver/errors.go | 6 +++ challenge/resolver/errors_test.go | 70 +++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 challenge/resolver/errors_test.go diff --git a/challenge/resolver/errors.go b/challenge/resolver/errors.go index 6a859922c..65a6ccdb7 100644 --- a/challenge/resolver/errors.go +++ b/challenge/resolver/errors.go @@ -3,6 +3,8 @@ package resolver import ( "bytes" "fmt" + "maps" + "slices" "sort" ) @@ -25,3 +27,7 @@ func (e obtainError) Error() string { return buffer.String() } + +func (e obtainError) Unwrap() []error { + return slices.AppendSeq(make([]error, 0, len(e)), maps.Values(e)) +} diff --git a/challenge/resolver/errors_test.go b/challenge/resolver/errors_test.go new file mode 100644 index 000000000..d4ab3c481 --- /dev/null +++ b/challenge/resolver/errors_test.go @@ -0,0 +1,70 @@ +package resolver + +import ( + "errors" + "testing" + + "github.com/go-acme/lego/v4/acme" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_obtainError_Error(t *testing.T) { + err := obtainError{ + "a": &acme.ProblemDetails{Type: "001"}, + "b": errors.New("oops"), + "c": errors.New("I did it again"), + } + + require.EqualError(t, err, `error: one or more domains had a problem: +[a] acme: error: 0 :: 001 :: +[b] oops +[c] I did it again +`) +} + +func Test_obtainError_Unwrap(t *testing.T) { + testCases := []struct { + desc string + err obtainError + assert assert.BoolAssertionFunc + }{ + { + desc: "one ok", + err: obtainError{ + "a": &acme.ProblemDetails{}, + "b": errors.New("oops"), + "c": errors.New("I did it again"), + }, + assert: assert.True, + }, + { + desc: "all ok", + err: obtainError{ + "a": &acme.ProblemDetails{Type: "001"}, + "b": &acme.ProblemDetails{Type: "002"}, + "c": &acme.ProblemDetails{Type: "002"}, + }, + assert: assert.True, + }, + { + desc: "nope", + err: obtainError{ + "a": errors.New("hello"), + "b": errors.New("oops"), + "c": errors.New("I did it again"), + }, + assert: assert.False, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + var pd *acme.ProblemDetails + + test.assert(t, errors.As(test.err, &pd)) + }) + } +} From da51631cd36b0640e559926d96d71a2e12555d92 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 4 Mar 2026 17:59:58 +0100 Subject: [PATCH 293/298] chore: improve issue template --- .github/ISSUE_TEMPLATE/new_dns_provider.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/new_dns_provider.yml b/.github/ISSUE_TEMPLATE/new_dns_provider.yml index cfd6e5c8c..b319bc287 100644 --- a/.github/ISSUE_TEMPLATE/new_dns_provider.yml +++ b/.github/ISSUE_TEMPLATE/new_dns_provider.yml @@ -14,9 +14,15 @@ body: required: true - label: Yes, I know that the lego maintainers don't have an account in all DNS providers in the world. required: true + + - type: checkboxes + id: pr + attributes: + label: Implementation + options: - label: Yes, I'm able to create a pull request and be able to maintain the implementation. required: false - - label: Yes, I'm able to test an implementation if someone creates a pull request to add the support of this DNS provider. + - label: Yes, I can test an implementation with the help of the maintainers if someone creates a pull request. required: false - type: dropdown From 847c763504888c511d7fcec82d65004caf25853e Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 4 Mar 2026 18:04:04 +0100 Subject: [PATCH 294/298] feat: Add DNS provider for Czechia (#2885) --- README.md | 79 +++++---- cmd/zz_gen_cmd_dnshelp.go | 21 +++ docs/content/dns/zz_gen_czechia.md | 67 +++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/czechia/czechia.go | 159 +++++++++++++++++ providers/dns/czechia/czechia.toml | 22 +++ providers/dns/czechia/czechia_test.go | 165 ++++++++++++++++++ providers/dns/czechia/internal/client.go | 124 +++++++++++++ providers/dns/czechia/internal/client_test.go | 67 +++++++ .../fixtures/add_txt_record-request.json | 6 + .../fixtures/delete_txt_record-request.json | 6 + providers/dns/czechia/internal/types.go | 8 + providers/dns/zz_gen_dns_providers.go | 3 + 13 files changed, 691 insertions(+), 38 deletions(-) create mode 100644 docs/content/dns/zz_gen_czechia.md create mode 100644 providers/dns/czechia/czechia.go create mode 100644 providers/dns/czechia/czechia.toml create mode 100644 providers/dns/czechia/czechia_test.go create mode 100644 providers/dns/czechia/internal/client.go create mode 100644 providers/dns/czechia/internal/client_test.go create mode 100644 providers/dns/czechia/internal/fixtures/add_txt_record-request.json create mode 100644 providers/dns/czechia/internal/fixtures/delete_txt_record-request.json create mode 100644 providers/dns/czechia/internal/types.go diff --git a/README.md b/README.md index 9925979bd..5f183a458 100644 --- a/README.md +++ b/README.md @@ -109,192 +109,197 @@ If your DNS provider is not supported, please open an [issue](https://github.com Constellix Core-Networks CPanel/WHM - DDnss (DynDNS Service) + Czechia + DDnss (DynDNS Service) Derak Cloud deSEC.io Designate DNSaaS for Openstack - Digital Ocean + Digital Ocean DirectAdmin DNS Made Easy DNSExit - dnsHome.de + dnsHome.de DNSimple DNSPod (deprecated) Domain Offensive (do.de) - Domeneshop + Domeneshop DreamHost Duck DNS Dyn - DynDnsFree.de + DynDnsFree.de Dynu EasyDNS EdgeCenter - Efficient IP + Efficient IP Epik Exoscale External program - F5 XC + F5 XC freemyip.com FusionLayer NameSurfer G-Core - Gandi + Gandi Gandi Live DNS (v5) Gigahost.no Glesys - Go Daddy + Go Daddy Google Cloud Google Domains Gravity - Hetzner + Hetzner Hosting.de Hosting.nl Hostinger - Hosttech + Hosttech HTTP request http.net Huawei Cloud - Hurricane Electric DNS + Hurricane Electric DNS HyperOne IBM Cloud (SoftLayer) IIJ DNS Platform Service - Infoblox + Infoblox Infomaniak Internet Initiative Japan Internet.bs - INWX + INWX Ionos Ionos Cloud IPv64 - ISPConfig 3 + ISPConfig 3 ISPConfig 3 - Dynamic DNS (DDNS) Module iwantmyname (Deprecated) JD Cloud - Joker + Joker Joohoi's ACME-DNS KeyHelp Leaseweb - Liara + Liara Lima-City Linode (v4) Liquid Web - Loopia + Loopia LuaDNS Mail-in-a-Box ManageEngine CloudDNS - Manual + Manual Metaname Metaregistrar mijn.host - Mittwald + Mittwald myaddr.{tools,dev,io} MyDNS.jp MythicBeasts - Name.com + Name.com Namecheap Namesilo NearlyFreeSpeech.NET - Neodigit + Neodigit Netcup Netlify Nicmanager - NIFCloud + NIFCloud Njalla Nodion NS1 - Octenium + Octenium Open Telekom Cloud Oracle Cloud OVH - plesk.com + plesk.com Porkbun PowerDNS Rackspace - Rain Yun/雨云 + Rain Yun/雨云 RcodeZero reg.ru Regfish - RFC2136 + RFC2136 RimuHosting RU CENTER Sakura Cloud - Scaleway + Scaleway Selectel Selectel v2 SelfHost.(de|eu) - Servercow + Servercow Shellrent Simply.com Sonic - Spaceship + Spaceship Stackpath Syse Technitium - Tencent Cloud DNS + Tencent Cloud DNS Tencent EdgeOne Timeweb Cloud TodayNIC/时代互联 - TransIP + TransIP UKFast SafeDNS Ultradns United-Domains - Variomedia + Variomedia VegaDNS Vercel Versio.[nl|eu|uk] - VinylDNS + VinylDNS Virtualname VK Cloud Volcano Engine/火山引擎 - Vscale + Vscale Vultr webnames.ca webnames.ru - Websupport + Websupport WEDOS West.cn/西部数码 Yandex 360 - Yandex Cloud + Yandex Cloud Yandex PDD Zone.ee ZoneEdit + Zonomi + + + diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 8e3b4ebce..3dff0ee7a 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -49,6 +49,7 @@ func allDNSCodes() string { "constellix", "corenetworks", "cpanel", + "czechia", "ddnss", "derak", "desec", @@ -1026,6 +1027,26 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/cpanel`) + case "czechia": + // generated from: providers/dns/czechia/czechia.toml + ew.writeln(`Configuration for Czechia.`) + ew.writeln(`Code: 'czechia'`) + ew.writeln(`Since: 'v4.33.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "CZECHIA_TOKEN": Authorization token`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "CZECHIA_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "CZECHIA_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "CZECHIA_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "CZECHIA_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/czechia`) + case "ddnss": // generated from: providers/dns/ddnss/ddnss.toml ew.writeln(`Configuration for DDnss (DynDNS Service).`) diff --git a/docs/content/dns/zz_gen_czechia.md b/docs/content/dns/zz_gen_czechia.md new file mode 100644 index 000000000..7b1cdd1ae --- /dev/null +++ b/docs/content/dns/zz_gen_czechia.md @@ -0,0 +1,67 @@ +--- +title: "Czechia" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: czechia +dnsprovider: + since: "v4.33.0" + code: "czechia" + url: "https://www.czechia.com/" +--- + + + + + + +Configuration for [Czechia](https://www.czechia.com/). + + + + +- Code: `czechia` +- Since: v4.33.0 + + +Here is an example bash command using the Czechia provider: + +```bash +CZECHIA_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ +lego --dns czechia -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `CZECHIA_TOKEN` | Authorization token | + +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 | +|--------------------------------|-------------| +| `CZECHIA_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `CZECHIA_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `CZECHIA_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `CZECHIA_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 120) | + +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://api.czechia.com/swagger/index.html) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 925ef0b21..b68c8dbf6 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, artfiles, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bluecatv2, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, ddnss, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, leaseweb, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, namesurfer, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, artfiles, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bluecatv2, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, czechia, ddnss, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, leaseweb, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, namesurfer, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/czechia/czechia.go b/providers/dns/czechia/czechia.go new file mode 100644 index 000000000..3ff397c35 --- /dev/null +++ b/providers/dns/czechia/czechia.go @@ -0,0 +1,159 @@ +// Package czechia implements a DNS provider for solving the DNS-01 challenge using Czechia. +package czechia + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/czechia/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" +) + +// Environment variables names. +const ( + envNamespace = "CZECHIA_" + + EnvToken = envNamespace + "TOKEN" + + 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 { + Token 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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for Czechia. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvToken) + if err != nil { + return nil, fmt.Errorf("czechia: %w", err) + } + + config := NewDefaultConfig() + config.Token = values[EnvToken] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Czechia. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("czechia: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.Token) + if err != nil { + return nil, fmt.Errorf("czechia: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("czechia: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("czechia: %w", err) + } + + record := internal.TXTRecord{ + Hostname: subDomain, + Text: info.Value, + TTL: d.config.TTL, + PublishZone: 1, + } + + err = d.client.AddTXTRecord(ctx, dns01.UnFqdn(authZone), record) + if err != nil { + return fmt.Errorf("czechia: add TXT record: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("czechia: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("czechia: %w", err) + } + + record := internal.TXTRecord{ + Hostname: subDomain, + Text: info.Value, + TTL: d.config.TTL, + PublishZone: 1, + } + + err = d.client.DeleteTXTRecord(ctx, dns01.UnFqdn(authZone), record) + if err != nil { + return fmt.Errorf("czechia: delete TXT record: %w", err) + } + + 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/czechia/czechia.toml b/providers/dns/czechia/czechia.toml new file mode 100644 index 000000000..2a66d2054 --- /dev/null +++ b/providers/dns/czechia/czechia.toml @@ -0,0 +1,22 @@ +Name = "Czechia" +Description = '''''' +URL = "https://www.czechia.com/" +Code = "czechia" +Since = "v4.33.0" + +Example = ''' +CZECHIA_TOKEN="xxxxxxxxxxxxxxxxxxxxx" \ +lego --dns czechia -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + CZECHIA_TOKEN = "Authorization token" + [Configuration.Additional] + CZECHIA_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + CZECHIA_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + CZECHIA_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)" + CZECHIA_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://api.czechia.com/swagger/index.html" diff --git a/providers/dns/czechia/czechia_test.go b/providers/dns/czechia/czechia_test.go new file mode 100644 index 000000000..7d9a2676c --- /dev/null +++ b/providers/dns/czechia/czechia_test.go @@ -0,0 +1,165 @@ +package czechia + +import ( + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvToken).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvToken: "secret", + }, + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "czechia: some credentials information are missing: CZECHIA_TOKEN", + }, + } + + 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 + token string + expected string + }{ + { + desc: "success", + token: "secret", + }, + { + desc: "missing credentials", + expected: "czechia: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.Token = test.token + + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.Token = "secret" + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + p.client.BaseURL, _ = url.Parse(server.URL) + + return p, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With("AuthorizationToken", "secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("POST /DNS/example.com/TXT", + servermock.Noop(), + servermock.CheckRequestJSONBodyFromInternal("add_txt_record-request.json"), + ). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("DELETE /DNS/example.com/TXT", + servermock.Noop(), + servermock.CheckRequestJSONBodyFromInternal("add_txt_record-request.json"), + ). + Build(t) + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/czechia/internal/client.go b/providers/dns/czechia/internal/client.go new file mode 100644 index 000000000..f3e0e462e --- /dev/null +++ b/providers/dns/czechia/internal/client.go @@ -0,0 +1,124 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" +) + +const defaultBaseURL = "https://api.czechia.com/api" + +const authorizationTokenHeader = "AuthorizationToken" + +// Client the Czechia API client. +type Client struct { + token string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(token string) (*Client, error) { + if token == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + token: token, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) AddTXTRecord(ctx context.Context, domain string, record TXTRecord) error { + endpoint := c.BaseURL.JoinPath("DNS", domain, "TXT") + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) DeleteTXTRecord(ctx context.Context, domain string, record TXTRecord) error { + endpoint := c.BaseURL.JoinPath("DNS", domain, "TXT") + + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, record) + if err != nil { + return err + } + + return c.do(req, nil) +} + +func (c *Client) do(req *http.Request, result any) error { + useragent.SetHeader(req.Header) + + req.Header.Set(authorizationTokenHeader, c.token) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + raw, _ := io.ReadAll(resp.Body) + + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} diff --git a/providers/dns/czechia/internal/client_test.go b/providers/dns/czechia/internal/client_test.go new file mode 100644 index 000000000..c6f1141c5 --- /dev/null +++ b/providers/dns/czechia/internal/client_test.go @@ -0,0 +1,67 @@ +package internal + +import ( + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("secret") + if err != nil { + return nil, err + } + + client.BaseURL, _ = url.Parse(server.URL) + client.HTTPClient = server.Client() + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With(authorizationTokenHeader, "secret"), + ) +} + +func TestClient_AddTXTRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /DNS/example.com/TXT", + servermock.Noop(), + servermock.CheckRequestJSONBodyFromFixture("add_txt_record-request.json"), + ). + Build(t) + + record := TXTRecord{ + Hostname: "_acme-challenge", + Text: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 120, + PublishZone: 1, + } + + err := client.AddTXTRecord(t.Context(), "example.com", record) + require.NoError(t, err) +} + +func TestClient_DeleteTXTRecord(t *testing.T) { + client := mockBuilder(). + Route("DELETE /DNS/example.com/TXT", + servermock.Noop(), + servermock.CheckRequestJSONBodyFromFixture("add_txt_record-request.json"), + ). + Build(t) + + record := TXTRecord{ + Hostname: "_acme-challenge", + Text: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 120, + PublishZone: 1, + } + + err := client.DeleteTXTRecord(t.Context(), "example.com", record) + require.NoError(t, err) +} diff --git a/providers/dns/czechia/internal/fixtures/add_txt_record-request.json b/providers/dns/czechia/internal/fixtures/add_txt_record-request.json new file mode 100644 index 000000000..ed5830093 --- /dev/null +++ b/providers/dns/czechia/internal/fixtures/add_txt_record-request.json @@ -0,0 +1,6 @@ +{ + "hostName": "_acme-challenge", + "text": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "ttl": 120, + "publishZone": 1 +} diff --git a/providers/dns/czechia/internal/fixtures/delete_txt_record-request.json b/providers/dns/czechia/internal/fixtures/delete_txt_record-request.json new file mode 100644 index 000000000..ed5830093 --- /dev/null +++ b/providers/dns/czechia/internal/fixtures/delete_txt_record-request.json @@ -0,0 +1,6 @@ +{ + "hostName": "_acme-challenge", + "text": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "ttl": 120, + "publishZone": 1 +} diff --git a/providers/dns/czechia/internal/types.go b/providers/dns/czechia/internal/types.go new file mode 100644 index 000000000..f4a9bfef7 --- /dev/null +++ b/providers/dns/czechia/internal/types.go @@ -0,0 +1,8 @@ +package internal + +type TXTRecord struct { + Hostname string `json:"hostName,omitempty"` + Text string `json:"text,omitempty"` + TTL int `json:"ttl,omitempty"` + PublishZone int `json:"publishZone,omitempty"` +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index e1b2cc989..66457c550 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -43,6 +43,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/constellix" "github.com/go-acme/lego/v4/providers/dns/corenetworks" "github.com/go-acme/lego/v4/providers/dns/cpanel" + "github.com/go-acme/lego/v4/providers/dns/czechia" "github.com/go-acme/lego/v4/providers/dns/ddnss" "github.com/go-acme/lego/v4/providers/dns/derak" "github.com/go-acme/lego/v4/providers/dns/desec" @@ -273,6 +274,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return corenetworks.NewDNSProvider() case "cpanel": return cpanel.NewDNSProvider() + case "czechia": + return czechia.NewDNSProvider() case "ddnss": return ddnss.NewDNSProvider() case "derak": From a56697ed1cd7eddeedfb459f9200b936afeeb34a Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Sun, 8 Mar 2026 23:32:56 +0100 Subject: [PATCH 295/298] Add DNS provider for EuroDNS (#2898) --- README.md | 66 ++-- cmd/zz_gen_cmd_dnshelp.go | 22 ++ docs/content/dns/zz_gen_eurodns.md | 69 ++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/eurodns/eurodns.go | 197 +++++++++++ providers/dns/eurodns/eurodns.toml | 24 ++ providers/dns/eurodns/eurodns_test.go | 215 ++++++++++++ providers/dns/eurodns/internal/client.go | 199 +++++++++++ providers/dns/eurodns/internal/client_test.go | 310 ++++++++++++++++++ .../dns/eurodns/internal/fixtures/error.json | 8 + .../eurodns/internal/fixtures/zone_add.json | 46 +++ .../fixtures/zone_add_empty_forwards.json | 28 ++ .../fixtures/zone_add_validate_ko.json | 139 ++++++++ .../fixtures/zone_add_validate_ok.json | 49 +++ .../eurodns/internal/fixtures/zone_get.json | 37 +++ .../internal/fixtures/zone_remove.json | 37 +++ providers/dns/eurodns/internal/types.go | 136 ++++++++ providers/dns/zz_gen_dns_providers.go | 3 + 18 files changed, 1553 insertions(+), 34 deletions(-) create mode 100644 docs/content/dns/zz_gen_eurodns.md create mode 100644 providers/dns/eurodns/eurodns.go create mode 100644 providers/dns/eurodns/eurodns.toml create mode 100644 providers/dns/eurodns/eurodns_test.go create mode 100644 providers/dns/eurodns/internal/client.go create mode 100644 providers/dns/eurodns/internal/client_test.go create mode 100644 providers/dns/eurodns/internal/fixtures/error.json create mode 100644 providers/dns/eurodns/internal/fixtures/zone_add.json create mode 100644 providers/dns/eurodns/internal/fixtures/zone_add_empty_forwards.json create mode 100644 providers/dns/eurodns/internal/fixtures/zone_add_validate_ko.json create mode 100644 providers/dns/eurodns/internal/fixtures/zone_add_validate_ok.json create mode 100644 providers/dns/eurodns/internal/fixtures/zone_get.json create mode 100644 providers/dns/eurodns/internal/fixtures/zone_remove.json create mode 100644 providers/dns/eurodns/internal/types.go diff --git a/README.md b/README.md index 5f183a458..3d815387a 100644 --- a/README.md +++ b/README.md @@ -138,168 +138,168 @@ If your DNS provider is not supported, please open an [issue](https://github.com Efficient IP Epik + EuroDNS Exoscale - External program + External program F5 XC freemyip.com FusionLayer NameSurfer - G-Core + G-Core Gandi Gandi Live DNS (v5) Gigahost.no - Glesys + Glesys Go Daddy Google Cloud Google Domains - Gravity + Gravity Hetzner Hosting.de Hosting.nl - Hostinger + Hostinger Hosttech HTTP request http.net - Huawei Cloud + Huawei Cloud Hurricane Electric DNS HyperOne IBM Cloud (SoftLayer) - IIJ DNS Platform Service + IIJ DNS Platform Service Infoblox Infomaniak Internet Initiative Japan - Internet.bs + Internet.bs INWX Ionos Ionos Cloud - IPv64 + IPv64 ISPConfig 3 ISPConfig 3 - Dynamic DNS (DDNS) Module iwantmyname (Deprecated) - JD Cloud + JD Cloud Joker Joohoi's ACME-DNS KeyHelp - Leaseweb + Leaseweb Liara Lima-City Linode (v4) - Liquid Web + Liquid Web Loopia LuaDNS Mail-in-a-Box - ManageEngine CloudDNS + ManageEngine CloudDNS Manual Metaname Metaregistrar - mijn.host + mijn.host Mittwald myaddr.{tools,dev,io} MyDNS.jp - MythicBeasts + MythicBeasts Name.com Namecheap Namesilo - NearlyFreeSpeech.NET + NearlyFreeSpeech.NET Neodigit Netcup Netlify - Nicmanager + Nicmanager NIFCloud Njalla Nodion - NS1 + NS1 Octenium Open Telekom Cloud Oracle Cloud - OVH + OVH plesk.com Porkbun PowerDNS - Rackspace + Rackspace Rain Yun/雨云 RcodeZero reg.ru - Regfish + Regfish RFC2136 RimuHosting RU CENTER - Sakura Cloud + Sakura Cloud Scaleway Selectel Selectel v2 - SelfHost.(de|eu) + SelfHost.(de|eu) Servercow Shellrent Simply.com - Sonic + Sonic Spaceship Stackpath Syse - Technitium + Technitium Tencent Cloud DNS Tencent EdgeOne Timeweb Cloud - TodayNIC/时代互联 + TodayNIC/时代互联 TransIP UKFast SafeDNS Ultradns - United-Domains + United-Domains Variomedia VegaDNS Vercel - Versio.[nl|eu|uk] + Versio.[nl|eu|uk] VinylDNS Virtualname VK Cloud - Volcano Engine/火山引擎 + Volcano Engine/火山引擎 Vscale Vultr webnames.ca - webnames.ru + webnames.ru Websupport WEDOS West.cn/西部数码 - Yandex 360 + Yandex 360 Yandex Cloud Yandex PDD Zone.ee - ZoneEdit + ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 3dff0ee7a..3a6439f00 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -74,6 +74,7 @@ func allDNSCodes() string { "edgeone", "efficientip", "epik", + "eurodns", "exec", "exoscale", "f5xc", @@ -1562,6 +1563,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/epik`) + case "eurodns": + // generated from: providers/dns/eurodns/eurodns.toml + ew.writeln(`Configuration for EuroDNS.`) + ew.writeln(`Code: 'eurodns'`) + ew.writeln(`Since: 'v4.33.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "EURODNS_API_KEY": API key`) + ew.writeln(` - "EURODNS_APP_ID": Application ID`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "EURODNS_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "EURODNS_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 2)`) + ew.writeln(` - "EURODNS_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 60)`) + ew.writeln(` - "EURODNS_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/eurodns`) + case "exec": // generated from: providers/dns/exec/exec.toml ew.writeln(`Configuration for External program.`) diff --git a/docs/content/dns/zz_gen_eurodns.md b/docs/content/dns/zz_gen_eurodns.md new file mode 100644 index 000000000..cb5a0418d --- /dev/null +++ b/docs/content/dns/zz_gen_eurodns.md @@ -0,0 +1,69 @@ +--- +title: "EuroDNS" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: eurodns +dnsprovider: + since: "v4.33.0" + code: "eurodns" + url: "https://www.eurodns.com/" +--- + + + + + + +Configuration for [EuroDNS](https://www.eurodns.com/). + + + + +- Code: `eurodns` +- Since: v4.33.0 + + +Here is an example bash command using the EuroDNS provider: + +```bash +EURODNS_APP_ID="xxx" \ +EURODNS_API_KEY="yyy" \ +lego --dns eurodns -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `EURODNS_API_KEY` | API key | +| `EURODNS_APP_ID` | Application ID | + +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 | +|--------------------------------|-------------| +| `EURODNS_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `EURODNS_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 2) | +| `EURODNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 60) | +| `EURODNS_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 600) | + +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://docapi.eurodns.com/) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index b68c8dbf6..5736d0ae8 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, artfiles, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bluecatv2, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, czechia, ddnss, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, leaseweb, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, namesurfer, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, artfiles, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bluecatv2, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, czechia, ddnss, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, eurodns, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, leaseweb, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, namesurfer, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/eurodns/eurodns.go b/providers/dns/eurodns/eurodns.go new file mode 100644 index 000000000..21ff3c3a9 --- /dev/null +++ b/providers/dns/eurodns/eurodns.go @@ -0,0 +1,197 @@ +// Package eurodns implements a DNS provider for solving the DNS-01 challenge using EuroDNS. +package eurodns + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/eurodns/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" +) + +// Environment variables names. +const ( + envNamespace = "EURODNS_" + + EnvApplicationID = envNamespace + "APP_ID" + 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 { + ApplicationID string + 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, internal.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 *internal.Client +} + +// NewDNSProvider returns a DNSProvider instance configured for EuroDNS. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvApplicationID, EnvAPIKey) + if err != nil { + return nil, fmt.Errorf("eurodns: %w", err) + } + + config := NewDefaultConfig() + config.ApplicationID = values[EnvApplicationID] + config.APIKey = values[EnvAPIKey] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for EuroDNS. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("eurodns: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.ApplicationID, config.APIKey) + if err != nil { + return nil, fmt.Errorf("eurodns: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("eurodns: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("eurodns: %w", err) + } + + authZone = dns01.UnFqdn(authZone) + + zone, err := d.client.GetZone(ctx, authZone) + if err != nil { + return fmt.Errorf("eurodns: get zone: %w", err) + } + + zone.Records = append(zone.Records, internal.Record{ + Type: "TXT", + Host: subDomain, + TTL: internal.TTLRounder(d.config.TTL), + RData: info.Value, + }) + + validation, err := d.client.ValidateZone(ctx, authZone, zone) + if err != nil { + return fmt.Errorf("eurodns: validate zone: %w", err) + } + + if validation.Report != nil && !validation.Report.IsValid { + return fmt.Errorf("eurodns: validation report: %w", validation.Report) + } + + err = d.client.SaveZone(ctx, authZone, zone) + if err != nil { + return fmt.Errorf("eurodns: save zone: %w", err) + } + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("eurodns: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("eurodns: %w", err) + } + + authZone = dns01.UnFqdn(authZone) + + zone, err := d.client.GetZone(ctx, authZone) + if err != nil { + return fmt.Errorf("eurodns: get zone: %w", err) + } + + var recordsToKeep []internal.Record + + for _, record := range zone.Records { + if record.Type == "TXT" && record.Host == subDomain && record.RData == info.Value { + continue + } + + recordsToKeep = append(recordsToKeep, record) + } + + zone.Records = recordsToKeep + + validation, err := d.client.ValidateZone(ctx, authZone, zone) + if err != nil { + return fmt.Errorf("eurodns: validate zone: %w", err) + } + + if validation.Report != nil && !validation.Report.IsValid { + return fmt.Errorf("eurodns: validation report: %w", validation.Report) + } + + err = d.client.SaveZone(ctx, authZone, zone) + if err != nil { + return fmt.Errorf("eurodns: save zone: %w", err) + } + + 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/eurodns/eurodns.toml b/providers/dns/eurodns/eurodns.toml new file mode 100644 index 000000000..302b15d00 --- /dev/null +++ b/providers/dns/eurodns/eurodns.toml @@ -0,0 +1,24 @@ +Name = "EuroDNS" +Description = '''''' +URL = "https://www.eurodns.com/" +Code = "eurodns" +Since = "v4.33.0" + +Example = ''' +EURODNS_APP_ID="xxx" \ +EURODNS_API_KEY="yyy" \ +lego --dns eurodns -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + EURODNS_APP_ID = "Application ID" + EURODNS_API_KEY = "API key" + [Configuration.Additional] + EURODNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)" + EURODNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)" + EURODNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 600)" + EURODNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "https://docapi.eurodns.com/" diff --git a/providers/dns/eurodns/eurodns_test.go b/providers/dns/eurodns/eurodns_test.go new file mode 100644 index 000000000..abbb4717e --- /dev/null +++ b/providers/dns/eurodns/eurodns_test.go @@ -0,0 +1,215 @@ +package eurodns + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/go-acme/lego/v4/providers/dns/eurodns/internal" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvApplicationID, EnvAPIKey).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvApplicationID: "abc", + EnvAPIKey: "secret", + }, + }, + { + desc: "missing application ID", + envVars: map[string]string{ + EnvApplicationID: "", + EnvAPIKey: "secret", + }, + expected: "eurodns: some credentials information are missing: EURODNS_APP_ID", + }, + { + desc: "missing API secret", + envVars: map[string]string{ + EnvApplicationID: "", + EnvAPIKey: "secret", + }, + expected: "eurodns: some credentials information are missing: EURODNS_APP_ID", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "eurodns: some credentials information are missing: EURODNS_APP_ID,EURODNS_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 + appID string + apiKey string + expected string + }{ + { + desc: "success", + appID: "abc", + apiKey: "secret", + }, + { + desc: "missing application ID", + expected: "eurodns: credentials missing", + apiKey: "secret", + }, + { + desc: "missing API secret", + expected: "eurodns: credentials missing", + appID: "abc", + }, + { + desc: "missing credentials", + expected: "eurodns: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.ApplicationID = test.appID + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.APIKey = "secret" + config.ApplicationID = "abc" + config.HTTPClient = server.Client() + + provider, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + provider.client.BaseURL, _ = url.Parse(server.URL) + + return provider, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With(internal.HeaderAppID, "abc"). + With(internal.HeaderAPIKey, "secret"), + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("GET /example.com", + servermock.ResponseFromInternal("zone_get.json"), + ). + Route("POST /example.com/check", + servermock.ResponseFromInternal("zone_add_validate_ok.json"), + servermock.CheckRequestJSONBodyFromInternal("zone_add.json"), + ). + Route("PUT /example.com", + servermock.Noop(). + WithStatusCode(http.StatusNoContent), + servermock.CheckRequestJSONBodyFromInternal("zone_add.json"), + ). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("GET /example.com", + servermock.ResponseFromInternal("zone_add.json"), + ). + Route("POST /example.com/check", + servermock.ResponseFromInternal("zone_remove.json"), + servermock.CheckRequestJSONBodyFromInternal("zone_remove.json"), + ). + Route("PUT /example.com", + servermock.Noop(). + WithStatusCode(http.StatusNoContent), + servermock.CheckRequestJSONBodyFromInternal("zone_remove.json"), + ). + Build(t) + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/eurodns/internal/client.go b/providers/dns/eurodns/internal/client.go new file mode 100644 index 000000000..1ebf8d143 --- /dev/null +++ b/providers/dns/eurodns/internal/client.go @@ -0,0 +1,199 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" +) + +const defaultBaseURL = "https://rest-api.eurodns.com/dns-zones/" + +const ( + HeaderAppID = "X-APP-ID" + HeaderAPIKey = "X-API-KEY" +) + +// Client the EuroDNS API client. +type Client struct { + appID string + apiKey string + + BaseURL *url.URL + HTTPClient *http.Client +} + +// NewClient creates a new Client. +func NewClient(appID, apiKey string) (*Client, error) { + if appID == "" || apiKey == "" { + return nil, errors.New("credentials missing") + } + + baseURL, _ := url.Parse(defaultBaseURL) + + return &Client{ + appID: appID, + apiKey: apiKey, + BaseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +// GetZone gets a DNS Zone. +// https://docapi.eurodns.com/#/dnsprovider/getdnszone +func (c *Client) GetZone(ctx context.Context, domain string) (*Zone, error) { + endpoint := c.BaseURL.JoinPath(domain) + + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + result := &Zone{} + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result, nil +} + +// SaveZone saves a DNS Zone. +// https://docapi.eurodns.com/#/dnsprovider/savednszone +func (c *Client) SaveZone(ctx context.Context, domain string, zone *Zone) error { + endpoint := c.BaseURL.JoinPath(domain) + + if len(zone.URLForwards) == 0 { + zone.URLForwards = make([]URLForward, 0) + } + + if len(zone.MailForwards) == 0 { + zone.MailForwards = make([]MailForward, 0) + } + + req, err := newJSONRequest(ctx, http.MethodPut, endpoint, zone) + if err != nil { + return err + } + + return c.do(req, nil) +} + +// ValidateZone validates DNS Zone. +// https://docapi.eurodns.com/#/dnsprovider/checkdnszone +func (c *Client) ValidateZone(ctx context.Context, domain string, zone *Zone) (*Zone, error) { + endpoint := c.BaseURL.JoinPath(domain, "check") + + if len(zone.URLForwards) == 0 { + zone.URLForwards = make([]URLForward, 0) + } + + if len(zone.MailForwards) == 0 { + zone.MailForwards = make([]MailForward, 0) + } + + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, zone) + if err != nil { + return nil, err + } + + result := &Zone{} + + err = c.do(req, result) + if err != nil { + return nil, err + } + + return result, nil +} + +func (c *Client) do(req *http.Request, result any) error { + req.Header.Set(HeaderAppID, c.appID) + req.Header.Set(HeaderAPIKey, c.apiKey) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + return parseError(req, resp) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return nil +} + +func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) { + buf := new(bytes.Buffer) + + if payload != nil { + err := json.NewEncoder(buf).Encode(payload) + if err != nil { + return nil, fmt.Errorf("failed to create request JSON body: %w", err) + } + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), buf) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Accept", "application/json") + + if payload != nil { + req.Header.Set("Content-Type", "application/json") + } + + return req, nil +} + +func parseError(req *http.Request, resp *http.Response) error { + raw, _ := io.ReadAll(resp.Body) + + var errAPI APIError + + err := json.Unmarshal(raw, &errAPI) + if err != nil { + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + return fmt.Errorf("%d: %w", resp.StatusCode, &errAPI) +} + +const DefaultTTL = 600 + +// TTLRounder rounds the given TTL in seconds to the next accepted value. +// Accepted TTL values are: 600, 900, 1800,3600, 7200, 14400, 21600, 43200, 86400, 172800, 432000, 604800. +func TTLRounder(ttl int) int { + for _, validTTL := range []int{DefaultTTL, 900, 1800, 3600, 7200, 14400, 21600, 43200, 86400, 172800, 432000, 604800} { + if ttl <= validTTL { + return validTTL + } + } + + return DefaultTTL +} diff --git a/providers/dns/eurodns/internal/client_test.go b/providers/dns/eurodns/internal/client_test.go new file mode 100644 index 000000000..68d1fda84 --- /dev/null +++ b/providers/dns/eurodns/internal/client_test.go @@ -0,0 +1,310 @@ +package internal + +import ( + "context" + "net/http" + "net/http/httptest" + "net/url" + "slices" + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/go-acme/lego/v4/providers/dns/internal/ptr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient("abc", "secret") + if err != nil { + return nil, err + } + + client.HTTPClient = server.Client() + client.BaseURL, _ = url.Parse(server.URL) + + return client, nil + }, + servermock.CheckHeader(). + WithJSONHeaders(). + With(HeaderAppID, "abc"). + With(HeaderAPIKey, "secret"), + ) +} + +func TestClient_GetZone(t *testing.T) { + client := mockBuilder(). + Route("GET /example.com", + servermock.ResponseFromFixture("zone_get.json"), + ). + Build(t) + + zone, err := client.GetZone(context.Background(), "example.com") + require.NoError(t, err) + + expected := &Zone{ + Name: "example.com", + DomainConnect: true, + Records: slices.Concat([]Record{fakeARecord()}), + URLForwards: []URLForward{fakeURLForward()}, + MailForwards: []MailForward{fakeMailForward()}, + } + + assert.Equal(t, expected, zone) +} + +func TestClient_GetZone_error(t *testing.T) { + client := mockBuilder(). + Route("GET /example.com", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized), + ). + Build(t) + + _, err := client.GetZone(context.Background(), "example.com") + require.Error(t, err) + + require.EqualError(t, err, "401: INVALID_API_KEY: Invalid API Key") +} + +func TestClient_SaveZone(t *testing.T) { + client := mockBuilder(). + Route("PUT /example.com", + servermock.Noop(). + WithStatusCode(http.StatusNoContent), + servermock.CheckRequestJSONBodyFromFixture("zone_add.json"), + ). + Build(t) + + record := Record{ + Type: "TXT", + Host: "_acme-challenge", + RData: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 600, + } + + zone := &Zone{ + Name: "example.com", + DomainConnect: true, + Records: []Record{fakeARecord(), record}, + URLForwards: []URLForward{fakeURLForward()}, + MailForwards: []MailForward{fakeMailForward()}, + } + + err := client.SaveZone(context.Background(), "example.com", zone) + require.NoError(t, err) +} + +func TestClient_SaveZone_emptyForwards(t *testing.T) { + client := mockBuilder(). + Route("PUT /example.com", + servermock.Noop(). + WithStatusCode(http.StatusNoContent), + servermock.CheckRequestJSONBodyFromFixture("zone_add_empty_forwards.json"), + ). + Build(t) + + record := Record{ + Type: "TXT", + Host: "_acme-challenge", + RData: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 600, + } + + zone := &Zone{ + Name: "example.com", + DomainConnect: true, + Records: slices.Concat([]Record{fakeARecord(), record}), + } + + err := client.SaveZone(context.Background(), "example.com", zone) + require.NoError(t, err) +} + +func TestClient_SaveZone_error(t *testing.T) { + client := mockBuilder(). + Route("PUT /example.com", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized), + ). + Build(t) + + zone := &Zone{ + Name: "example.com", + DomainConnect: true, + Records: []Record{fakeARecord()}, + URLForwards: []URLForward{fakeURLForward()}, + MailForwards: []MailForward{fakeMailForward()}, + } + + err := client.SaveZone(context.Background(), "example.com", zone) + require.Error(t, err) + + require.EqualError(t, err, "401: INVALID_API_KEY: Invalid API Key") +} + +func TestClient_ValidateZone(t *testing.T) { + client := mockBuilder(). + Route("POST /example.com/check", + servermock.ResponseFromFixture("zone_add_validate_ok.json"), + servermock.CheckRequestJSONBodyFromFixture("zone_add.json"), + ). + Build(t) + + record := Record{ + Type: "TXT", + Host: "_acme-challenge", + RData: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 600, + } + + zone := &Zone{ + Name: "example.com", + DomainConnect: true, + Records: []Record{fakeARecord(), record}, + URLForwards: []URLForward{fakeURLForward()}, + MailForwards: []MailForward{fakeMailForward()}, + } + + zone, err := client.ValidateZone(context.Background(), "example.com", zone) + require.NoError(t, err) + + expected := &Zone{ + Name: "example.com", + DomainConnect: true, + Records: []Record{fakeARecord(), record}, + URLForwards: []URLForward{fakeURLForward()}, + MailForwards: []MailForward{fakeMailForward()}, + Report: &Report{IsValid: true}, + } + + assert.Equal(t, expected, zone) +} + +func TestClient_ValidateZone_report(t *testing.T) { + client := mockBuilder(). + Route("POST /example.com/check", + servermock.ResponseFromFixture("zone_add_validate_ko.json"), + servermock.CheckRequestJSONBodyFromFixture("zone_add.json"), + ). + Build(t) + + record := Record{ + Type: "TXT", + Host: "_acme-challenge", + RData: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: 600, + } + + zone := &Zone{ + Name: "example.com", + DomainConnect: true, + Records: []Record{fakeARecord(), record}, + URLForwards: []URLForward{fakeURLForward()}, + MailForwards: []MailForward{fakeMailForward()}, + } + + zone, err := client.ValidateZone(context.Background(), "example.com", zone) + require.NoError(t, err) + + expected := &Zone{ + Name: "example.com", + DomainConnect: true, + Records: []Record{fakeARecord(), record}, + URLForwards: []URLForward{fakeURLForward()}, + MailForwards: []MailForward{fakeMailForward()}, + Report: fakeReport(), + } + + assert.EqualError(t, zone.Report, `record error (ERROR): "120" is not a valid TTL, URL forward error (ERROR): string, mail forward error (ERROR): string, zone error (ERROR): string`) + + assert.Equal(t, expected, zone) +} + +func TestClient_ValidateZone_error(t *testing.T) { + client := mockBuilder(). + Route("POST /example.com/check", + servermock.ResponseFromFixture("error.json"). + WithStatusCode(http.StatusUnauthorized), + ). + Build(t) + + zone := &Zone{ + Name: "example.com", + DomainConnect: true, + Records: []Record{fakeARecord()}, + URLForwards: []URLForward{fakeURLForward()}, + MailForwards: []MailForward{fakeMailForward()}, + } + + _, err := client.ValidateZone(context.Background(), "example.com", zone) + require.Error(t, err) + + require.EqualError(t, err, "401: INVALID_API_KEY: Invalid API Key") +} + +func fakeARecord() Record { + return Record{ + ID: 1000, + Type: "A", + Host: "@", + TTL: 600, + RData: "string", + Updated: ptr.Pointer(true), + Locked: ptr.Pointer(true), + IsDynDNS: ptr.Pointer(true), + Proxy: "ON", + } +} + +func fakeURLForward() URLForward { + return URLForward{ + ID: 2000, + ForwardType: "FRAME", + Host: "string", + URL: "string", + Title: "string", + Keywords: "string", + Description: "string", + Updated: ptr.Pointer(true), + } +} + +func fakeMailForward() MailForward { + return MailForward{ + ID: 3000, + Source: "string", + Destination: "string", + Updated: ptr.Pointer(true), + } +} + +func fakeReport() *Report { + return &Report{ + IsValid: false, + RecordErrors: []RecordError{{ + Messages: []string{`"120" is not a valid TTL`}, + Severity: "ERROR", + Record: fakeARecord(), + }}, + URLForwardErrors: []URLForwardError{{ + Messages: []string{"string"}, + Severity: "ERROR", + URLForward: fakeURLForward(), + }}, + MailForwardErrors: []MailForwardError{{ + Messages: []string{"string"}, + MailForward: fakeMailForward(), + Severity: "ERROR", + }}, + ZoneErrors: []ZoneError{{ + Message: "string", + Severity: "ERROR", + Records: []Record{fakeARecord()}, + URLForwards: []URLForward{fakeURLForward()}, + MailForwards: []MailForward{fakeMailForward()}, + }}, + } +} diff --git a/providers/dns/eurodns/internal/fixtures/error.json b/providers/dns/eurodns/internal/fixtures/error.json new file mode 100644 index 000000000..82a334598 --- /dev/null +++ b/providers/dns/eurodns/internal/fixtures/error.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "code": "INVALID_API_KEY", + "title": "Invalid API Key" + } + ] +} diff --git a/providers/dns/eurodns/internal/fixtures/zone_add.json b/providers/dns/eurodns/internal/fixtures/zone_add.json new file mode 100644 index 000000000..db8142357 --- /dev/null +++ b/providers/dns/eurodns/internal/fixtures/zone_add.json @@ -0,0 +1,46 @@ +{ + "name": "example.com", + "domainConnect": true, + "records": [ + { + "id": 1000, + "type": "A", + "host": "@", + "ttl": 600, + "rdata": "string", + "updated": true, + "locked": true, + "isDynDns": true, + "proxy": "ON" + }, + { + "type": "TXT", + "host": "_acme-challenge", + "ttl": 600, + "rdata": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "updated": null, + "locked": null, + "isDynDns": null + } + ], + "urlForwards": [ + { + "id": 2000, + "forwardType": "FRAME", + "host": "string", + "url": "string", + "title": "string", + "keywords": "string", + "description": "string", + "updated": true + } + ], + "mailForwards": [ + { + "id": 3000, + "source": "string", + "destination": "string", + "updated": true + } + ] +} diff --git a/providers/dns/eurodns/internal/fixtures/zone_add_empty_forwards.json b/providers/dns/eurodns/internal/fixtures/zone_add_empty_forwards.json new file mode 100644 index 000000000..64f8530c9 --- /dev/null +++ b/providers/dns/eurodns/internal/fixtures/zone_add_empty_forwards.json @@ -0,0 +1,28 @@ +{ + "name": "example.com", + "domainConnect": true, + "records": [ + { + "id": 1000, + "type": "A", + "host": "@", + "ttl": 600, + "rdata": "string", + "updated": true, + "locked": true, + "isDynDns": true, + "proxy": "ON" + }, + { + "type": "TXT", + "host": "_acme-challenge", + "ttl": 600, + "rdata": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "updated": null, + "locked": null, + "isDynDns": null + } + ], + "urlForwards": [], + "mailForwards": [] +} diff --git a/providers/dns/eurodns/internal/fixtures/zone_add_validate_ko.json b/providers/dns/eurodns/internal/fixtures/zone_add_validate_ko.json new file mode 100644 index 000000000..e07d42299 --- /dev/null +++ b/providers/dns/eurodns/internal/fixtures/zone_add_validate_ko.json @@ -0,0 +1,139 @@ +{ + "name": "example.com", + "domainConnect": true, + "records": [ + { + "id": 1000, + "type": "A", + "host": "@", + "ttl": 600, + "rdata": "string", + "updated": true, + "locked": true, + "isDynDns": true, + "proxy": "ON" + }, + { + "type": "TXT", + "host": "_acme-challenge", + "ttl": 600, + "rdata": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "updated": null, + "locked": null, + "isDynDns": null + } + ], + "urlForwards": [ + { + "id": 2000, + "forwardType": "FRAME", + "host": "string", + "url": "string", + "title": "string", + "keywords": "string", + "description": "string", + "updated": true + } + ], + "mailForwards": [ + { + "id": 3000, + "source": "string", + "destination": "string", + "updated": true + } + ], + "report": { + "isValid": false, + "recordErrors": [ + { + "messages": [ + "\"120\" is not a valid TTL" + ], + "record": { + "id": 1000, + "type": "A", + "host": "@", + "ttl": 600, + "rdata": "string", + "updated": true, + "locked": true, + "isDynDns": true, + "proxy": "ON" + }, + "severity": "ERROR" + } + ], + "urlForwardErrors": [ + { + "messages": [ + "string" + ], + "urlForward": { + "id": 2000, + "forwardType": "FRAME", + "host": "string", + "url": "string", + "title": "string", + "keywords": "string", + "description": "string", + "updated": true + }, + "severity": "ERROR" + } + ], + "mailForwardErrors": [ + { + "messages": [ + "string" + ], + "mailForward": { + "id": 3000, + "source": "string", + "destination": "string", + "updated": true + }, + "severity": "ERROR" + } + ], + "zoneErrors": [ + { + "message": "string", + "records": [ + { + "id": 1000, + "type": "A", + "host": "@", + "ttl": 600, + "rdata": "string", + "updated": true, + "locked": true, + "isDynDns": true, + "proxy": "ON" + } + ], + "urlForwards": [ + { + "id": 2000, + "forwardType": "FRAME", + "host": "string", + "url": "string", + "title": "string", + "keywords": "string", + "description": "string", + "updated": true + } + ], + "mailForwards": [ + { + "id": 3000, + "source": "string", + "destination": "string", + "updated": true + } + ], + "severity": "ERROR" + } + ] + } +} diff --git a/providers/dns/eurodns/internal/fixtures/zone_add_validate_ok.json b/providers/dns/eurodns/internal/fixtures/zone_add_validate_ok.json new file mode 100644 index 000000000..ba0ddfefb --- /dev/null +++ b/providers/dns/eurodns/internal/fixtures/zone_add_validate_ok.json @@ -0,0 +1,49 @@ +{ + "name": "example.com", + "domainConnect": true, + "records": [ + { + "id": 1000, + "type": "A", + "host": "@", + "ttl": 600, + "rdata": "string", + "updated": true, + "locked": true, + "isDynDns": true, + "proxy": "ON" + }, + { + "type": "TXT", + "host": "_acme-challenge", + "ttl": 600, + "rdata": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + "updated": null, + "locked": null, + "isDynDns": null + } + ], + "urlForwards": [ + { + "id": 2000, + "forwardType": "FRAME", + "host": "string", + "url": "string", + "title": "string", + "keywords": "string", + "description": "string", + "updated": true + } + ], + "mailForwards": [ + { + "id": 3000, + "source": "string", + "destination": "string", + "updated": true + } + ], + "report": { + "isValid": true + } +} diff --git a/providers/dns/eurodns/internal/fixtures/zone_get.json b/providers/dns/eurodns/internal/fixtures/zone_get.json new file mode 100644 index 000000000..ebbc8593e --- /dev/null +++ b/providers/dns/eurodns/internal/fixtures/zone_get.json @@ -0,0 +1,37 @@ +{ + "name": "example.com", + "domainConnect": true, + "records": [ + { + "id": 1000, + "type": "A", + "host": "@", + "ttl": 600, + "rdata": "string", + "updated": true, + "locked": true, + "isDynDns": true, + "proxy": "ON" + } + ], + "urlForwards": [ + { + "id": 2000, + "forwardType": "FRAME", + "host": "string", + "url": "string", + "title": "string", + "keywords": "string", + "description": "string", + "updated": true + } + ], + "mailForwards": [ + { + "id": 3000, + "source": "string", + "destination": "string", + "updated": true + } + ] +} diff --git a/providers/dns/eurodns/internal/fixtures/zone_remove.json b/providers/dns/eurodns/internal/fixtures/zone_remove.json new file mode 100644 index 000000000..ebbc8593e --- /dev/null +++ b/providers/dns/eurodns/internal/fixtures/zone_remove.json @@ -0,0 +1,37 @@ +{ + "name": "example.com", + "domainConnect": true, + "records": [ + { + "id": 1000, + "type": "A", + "host": "@", + "ttl": 600, + "rdata": "string", + "updated": true, + "locked": true, + "isDynDns": true, + "proxy": "ON" + } + ], + "urlForwards": [ + { + "id": 2000, + "forwardType": "FRAME", + "host": "string", + "url": "string", + "title": "string", + "keywords": "string", + "description": "string", + "updated": true + } + ], + "mailForwards": [ + { + "id": 3000, + "source": "string", + "destination": "string", + "updated": true + } + ] +} diff --git a/providers/dns/eurodns/internal/types.go b/providers/dns/eurodns/internal/types.go new file mode 100644 index 000000000..891b02e14 --- /dev/null +++ b/providers/dns/eurodns/internal/types.go @@ -0,0 +1,136 @@ +package internal + +import ( + "fmt" + "strings" +) + +type APIError struct { + Errors []Error `json:"errors"` +} + +func (a *APIError) Error() string { + var msg []string + + for _, e := range a.Errors { + msg = append(msg, fmt.Sprintf("%s: %s", e.Code, e.Title)) + } + + return strings.Join(msg, ", ") +} + +type Error struct { + Code string `json:"code"` + Title string `json:"title"` +} + +type Zone struct { + Name string `json:"name,omitempty"` + DomainConnect bool `json:"domainConnect,omitempty"` + Records []Record `json:"records"` + URLForwards []URLForward `json:"urlForwards"` + MailForwards []MailForward `json:"mailForwards"` + Report *Report `json:"report,omitempty"` +} + +type Record struct { + ID int `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Host string `json:"host,omitempty"` + TTL int `json:"ttl,omitempty"` + RData string `json:"rdata,omitempty"` + Updated *bool `json:"updated"` + Locked *bool `json:"locked"` + IsDynDNS *bool `json:"isDynDns"` + Proxy string `json:"proxy,omitempty"` +} + +type URLForward struct { + ID int `json:"id,omitempty"` + ForwardType string `json:"forwardType,omitempty"` + Host string `json:"host,omitempty"` + URL string `json:"url,omitempty"` + Title string `json:"title,omitempty"` + Keywords string `json:"keywords,omitempty"` + Description string `json:"description,omitempty"` + Updated *bool `json:"updated,omitempty"` +} + +type MailForward struct { + ID int `json:"id,omitempty"` + Source string `json:"source,omitempty"` + Destination string `json:"destination,omitempty"` + Updated *bool `json:"updated,omitempty"` +} + +type Report struct { + IsValid bool `json:"isValid,omitempty"` + RecordErrors []RecordError `json:"recordErrors,omitempty"` + URLForwardErrors []URLForwardError `json:"urlForwardErrors,omitempty"` + MailForwardErrors []MailForwardError `json:"mailForwardErrors,omitempty"` + ZoneErrors []ZoneError `json:"zoneErrors,omitempty"` +} + +func (r *Report) Error() string { + var msg []string + + for _, e := range r.RecordErrors { + msg = append(msg, e.Error()) + } + + for _, e := range r.URLForwardErrors { + msg = append(msg, e.Error()) + } + + for _, e := range r.MailForwardErrors { + msg = append(msg, e.Error()) + } + + for _, e := range r.ZoneErrors { + msg = append(msg, e.Error()) + } + + return strings.Join(msg, ", ") +} + +type RecordError struct { + Messages []string `json:"messages,omitempty"` + Record Record `json:"record"` + Severity string `json:"severity,omitempty"` +} + +func (e *RecordError) Error() string { + return fmt.Sprintf("record error (%s): %s", e.Severity, strings.Join(e.Messages, ", ")) +} + +type URLForwardError struct { + Messages []string `json:"messages,omitempty"` + URLForward URLForward `json:"urlForward"` + Severity string `json:"severity,omitempty"` +} + +func (e *URLForwardError) Error() string { + return fmt.Sprintf("URL forward error (%s): %s", e.Severity, strings.Join(e.Messages, ", ")) +} + +type MailForwardError struct { + Messages []string `json:"messages,omitempty"` + MailForward MailForward `json:"mailForward"` + Severity string `json:"severity,omitempty"` +} + +func (e *MailForwardError) Error() string { + return fmt.Sprintf("mail forward error (%s): %s", e.Severity, strings.Join(e.Messages, ", ")) +} + +type ZoneError struct { + Message string `json:"message,omitempty"` + Records []Record `json:"records,omitempty"` + URLForwards []URLForward `json:"urlForwards,omitempty"` + MailForwards []MailForward `json:"mailForwards,omitempty"` + Severity string `json:"severity,omitempty"` +} + +func (e *ZoneError) Error() string { + return fmt.Sprintf("zone error (%s): %s", e.Severity, e.Message) +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 66457c550..519cc93ec 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -68,6 +68,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/edgeone" "github.com/go-acme/lego/v4/providers/dns/efficientip" "github.com/go-acme/lego/v4/providers/dns/epik" + "github.com/go-acme/lego/v4/providers/dns/eurodns" "github.com/go-acme/lego/v4/providers/dns/exec" "github.com/go-acme/lego/v4/providers/dns/exoscale" "github.com/go-acme/lego/v4/providers/dns/f5xc" @@ -324,6 +325,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return efficientip.NewDNSProvider() case "epik": return epik.NewDNSProvider() + case "eurodns": + return eurodns.NewDNSProvider() case "exec": return exec.NewDNSProvider() case "exoscale": From 7b1aa50081643440c853a682ad8a9c2bf706929b Mon Sep 17 00:00:00 2001 From: Dane Date: Sun, 8 Mar 2026 23:02:14 +0000 Subject: [PATCH 296/298] safedns: rename UKFast SafeDNS to ANS SafeDNS (#2877) Co-authored-by: Fernandez Ludovic --- README.md | 80 ++++++++++++------------ cmd/zz_gen_cmd_dnshelp.go | 2 +- docs/content/dns/zz_gen_safedns.md | 8 +-- providers/dns/safedns/internal/client.go | 2 +- providers/dns/safedns/safedns.go | 4 +- providers/dns/safedns/safedns.toml | 4 +- 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 3d815387a..45ab93455 100644 --- a/README.md +++ b/README.md @@ -73,202 +73,202 @@ If your DNS provider is not supported, please open an [issue](https://github.com Amazon Route 53 Anexia CloudDNS + ANS SafeDNS ArtFiles - ArvanCloud + ArvanCloud Aurora DNS Autodns Axelname - Azion + Azion Azure (deprecated) Azure DNS Baidu Cloud - Beget.com + Beget.com Binary Lane Bindman Bluecat - Bluecat v2 + Bluecat v2 BookMyName Brandit (deprecated) Bunny - Checkdomain + Checkdomain Civo Cloud.ru CloudDNS - Cloudflare + Cloudflare ClouDNS CloudXNS (Deprecated) ConoHa v2 - ConoHa v3 + ConoHa v3 Constellix Core-Networks CPanel/WHM - Czechia + Czechia DDnss (DynDNS Service) Derak Cloud deSEC.io - Designate DNSaaS for Openstack + Designate DNSaaS for Openstack Digital Ocean DirectAdmin DNS Made Easy - DNSExit + DNSExit dnsHome.de DNSimple DNSPod (deprecated) - Domain Offensive (do.de) + Domain Offensive (do.de) Domeneshop DreamHost Duck DNS - Dyn + Dyn DynDnsFree.de Dynu EasyDNS - EdgeCenter + EdgeCenter Efficient IP Epik EuroDNS - Exoscale + Exoscale External program F5 XC freemyip.com - FusionLayer NameSurfer + FusionLayer NameSurfer G-Core Gandi Gandi Live DNS (v5) - Gigahost.no + Gigahost.no Glesys Go Daddy Google Cloud - Google Domains + Google Domains Gravity Hetzner Hosting.de - Hosting.nl + Hosting.nl Hostinger Hosttech HTTP request - http.net + http.net Huawei Cloud Hurricane Electric DNS HyperOne - IBM Cloud (SoftLayer) + IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox Infomaniak - Internet Initiative Japan + Internet Initiative Japan Internet.bs INWX Ionos - Ionos Cloud + Ionos Cloud IPv64 ISPConfig 3 ISPConfig 3 - Dynamic DNS (DDNS) Module - iwantmyname (Deprecated) + iwantmyname (Deprecated) JD Cloud Joker Joohoi's ACME-DNS - KeyHelp + KeyHelp Leaseweb Liara Lima-City - Linode (v4) + Linode (v4) Liquid Web Loopia LuaDNS - Mail-in-a-Box + Mail-in-a-Box ManageEngine CloudDNS Manual Metaname - Metaregistrar + Metaregistrar mijn.host Mittwald myaddr.{tools,dev,io} - MyDNS.jp + MyDNS.jp MythicBeasts Name.com Namecheap - Namesilo + Namesilo NearlyFreeSpeech.NET Neodigit Netcup - Netlify + Netlify Nicmanager NIFCloud Njalla - Nodion + Nodion NS1 Octenium Open Telekom Cloud - Oracle Cloud + Oracle Cloud OVH plesk.com Porkbun - PowerDNS + PowerDNS Rackspace Rain Yun/雨云 RcodeZero - reg.ru + reg.ru Regfish RFC2136 RimuHosting - RU CENTER + RU CENTER Sakura Cloud Scaleway Selectel - Selectel v2 + Selectel v2 SelfHost.(de|eu) Servercow Shellrent - Simply.com + Simply.com Sonic Spaceship Stackpath - Syse + Syse Technitium Tencent Cloud DNS Tencent EdgeOne - Timeweb Cloud + Timeweb Cloud TodayNIC/时代互联 TransIP - UKFast SafeDNS Ultradns United-Domains diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 3a6439f00..0ecc012b3 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -3417,7 +3417,7 @@ func displayDNSHelp(w io.Writer, name string) error { case "safedns": // generated from: providers/dns/safedns/safedns.toml - ew.writeln(`Configuration for UKFast SafeDNS.`) + ew.writeln(`Configuration for ANS SafeDNS.`) ew.writeln(`Code: 'safedns'`) ew.writeln(`Since: 'v4.6.0'`) ew.writeln() diff --git a/docs/content/dns/zz_gen_safedns.md b/docs/content/dns/zz_gen_safedns.md index e040a8a9f..4c20fca6a 100644 --- a/docs/content/dns/zz_gen_safedns.md +++ b/docs/content/dns/zz_gen_safedns.md @@ -1,12 +1,12 @@ --- -title: "UKFast SafeDNS" +title: "ANS SafeDNS" date: 2019-03-03T16:39:46+01:00 draft: false slug: safedns dnsprovider: since: "v4.6.0" code: "safedns" - url: "https://www.ukfast.co.uk/dns-hosting.html" + url: "https://www.ans.co.uk/" --- @@ -14,7 +14,7 @@ dnsprovider: -Configuration for [UKFast SafeDNS](https://www.ukfast.co.uk/dns-hosting.html). +Configuration for [ANS SafeDNS](https://www.ans.co.uk/). @@ -23,7 +23,7 @@ Configuration for [UKFast SafeDNS](https://www.ukfast.co.uk/dns-hosting.html). - Since: v4.6.0 -Here is an example bash command using the UKFast SafeDNS provider: +Here is an example bash command using the ANS SafeDNS provider: ```bash SAFEDNS_AUTH_TOKEN=xxxxxx \ diff --git a/providers/dns/safedns/internal/client.go b/providers/dns/safedns/internal/client.go index 51b12e99d..628618032 100644 --- a/providers/dns/safedns/internal/client.go +++ b/providers/dns/safedns/internal/client.go @@ -19,7 +19,7 @@ const defaultBaseURL = "https://api.ukfast.io/safedns/v1" const authorizationHeader = "Authorization" -// Client the UKFast SafeDNS client. +// Client the ANS SafeDNS client. type Client struct { authToken string diff --git a/providers/dns/safedns/safedns.go b/providers/dns/safedns/safedns.go index be8ca4fe6..154cfc5ee 100644 --- a/providers/dns/safedns/safedns.go +++ b/providers/dns/safedns/safedns.go @@ -1,4 +1,4 @@ -// Package safedns implements a DNS provider for solving the DNS-01 challenge using UKFast SafeDNS. +// Package safedns implements a DNS provider for solving the DNS-01 challenge using ANS SafeDNS. package safedns import ( @@ -75,7 +75,7 @@ func NewDNSProvider() (*DNSProvider, error) { return NewDNSProviderConfig(config) } -// NewDNSProviderConfig return a DNSProvider instance configured for UKFast SafeDNS. +// NewDNSProviderConfig return a DNSProvider instance configured for ANS SafeDNS. func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { if config == nil { return nil, errors.New("safedns: supplied configuration was nil") diff --git a/providers/dns/safedns/safedns.toml b/providers/dns/safedns/safedns.toml index 188db66a4..f387f2535 100644 --- a/providers/dns/safedns/safedns.toml +++ b/providers/dns/safedns/safedns.toml @@ -1,6 +1,6 @@ -Name = "UKFast SafeDNS" +Name = "ANS SafeDNS" Description = '''''' -URL = "https://www.ukfast.co.uk/dns-hosting.html" +URL = "https://www.ans.co.uk/" Code = "safedns" Since = "v4.6.0" From 9be8cd43ae5de725b39643598e00da367969cab6 Mon Sep 17 00:00:00 2001 From: exsesa <48995997+exsesa@users.noreply.github.com> Date: Tue, 10 Mar 2026 22:58:13 +0100 Subject: [PATCH 297/298] Add DNS provider for Excedo (#2910) Co-authored-by: Fernandez Ludovic --- README.md | 64 +++--- cmd/zz_gen_cmd_dnshelp.go | 22 ++ docs/content/dns/zz_gen_excedo.md | 69 ++++++ docs/data/zz_cli_help.toml | 2 +- providers/dns/excedo/excedo.go | 176 +++++++++++++++ providers/dns/excedo/excedo.toml | 24 ++ providers/dns/excedo/excedo_test.go | 210 ++++++++++++++++++ providers/dns/excedo/internal/client.go | 205 +++++++++++++++++ providers/dns/excedo/internal/client_test.go | 137 ++++++++++++ .../excedo/internal/fixtures/addrecord.json | 15 ++ .../internal/fixtures/deleterecord.json | 14 ++ .../dns/excedo/internal/fixtures/error.json | 18 ++ .../excedo/internal/fixtures/getrecords.json | 23 ++ .../dns/excedo/internal/fixtures/login.json | 7 + providers/dns/excedo/internal/identity.go | 75 +++++++ .../dns/excedo/internal/identity_test.go | 35 +++ providers/dns/excedo/internal/types.go | 65 ++++++ providers/dns/zz_gen_dns_providers.go | 3 + 18 files changed, 1131 insertions(+), 33 deletions(-) create mode 100644 docs/content/dns/zz_gen_excedo.md create mode 100644 providers/dns/excedo/excedo.go create mode 100644 providers/dns/excedo/excedo.toml create mode 100644 providers/dns/excedo/excedo_test.go create mode 100644 providers/dns/excedo/internal/client.go create mode 100644 providers/dns/excedo/internal/client_test.go create mode 100644 providers/dns/excedo/internal/fixtures/addrecord.json create mode 100644 providers/dns/excedo/internal/fixtures/deleterecord.json create mode 100644 providers/dns/excedo/internal/fixtures/error.json create mode 100644 providers/dns/excedo/internal/fixtures/getrecords.json create mode 100644 providers/dns/excedo/internal/fixtures/login.json create mode 100644 providers/dns/excedo/internal/identity.go create mode 100644 providers/dns/excedo/internal/identity_test.go create mode 100644 providers/dns/excedo/internal/types.go diff --git a/README.md b/README.md index 45ab93455..e90e94962 100644 --- a/README.md +++ b/README.md @@ -141,165 +141,165 @@ If your DNS provider is not supported, please open an [issue](https://github.com Epik EuroDNS + Excedo Exoscale External program F5 XC - freemyip.com + freemyip.com FusionLayer NameSurfer G-Core Gandi - Gandi Live DNS (v5) + Gandi Live DNS (v5) Gigahost.no Glesys Go Daddy - Google Cloud + Google Cloud Google Domains Gravity Hetzner - Hosting.de + Hosting.de Hosting.nl Hostinger Hosttech - HTTP request + HTTP request http.net Huawei Cloud Hurricane Electric DNS - HyperOne + HyperOne IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox - Infomaniak + Infomaniak Internet Initiative Japan Internet.bs INWX - Ionos + Ionos Ionos Cloud IPv64 ISPConfig 3 - ISPConfig 3 - Dynamic DNS (DDNS) Module + ISPConfig 3 - Dynamic DNS (DDNS) Module iwantmyname (Deprecated) JD Cloud Joker - Joohoi's ACME-DNS + Joohoi's ACME-DNS KeyHelp Leaseweb Liara - Lima-City + Lima-City Linode (v4) Liquid Web Loopia - LuaDNS + LuaDNS Mail-in-a-Box ManageEngine CloudDNS Manual - Metaname + Metaname Metaregistrar mijn.host Mittwald - myaddr.{tools,dev,io} + myaddr.{tools,dev,io} MyDNS.jp MythicBeasts Name.com - Namecheap + Namecheap Namesilo NearlyFreeSpeech.NET Neodigit - Netcup + Netcup Netlify Nicmanager NIFCloud - Njalla + Njalla Nodion NS1 Octenium - Open Telekom Cloud + Open Telekom Cloud Oracle Cloud OVH plesk.com - Porkbun + Porkbun PowerDNS Rackspace Rain Yun/雨云 - RcodeZero + RcodeZero reg.ru Regfish RFC2136 - RimuHosting + RimuHosting RU CENTER Sakura Cloud Scaleway - Selectel + Selectel Selectel v2 SelfHost.(de|eu) Servercow - Shellrent + Shellrent Simply.com Sonic Spaceship - Stackpath + Stackpath Syse Technitium Tencent Cloud DNS - Tencent EdgeOne + Tencent EdgeOne Timeweb Cloud TodayNIC/时代互联 TransIP - Ultradns + Ultradns United-Domains Variomedia VegaDNS - Vercel + Vercel Versio.[nl|eu|uk] VinylDNS Virtualname - VK Cloud + VK Cloud Volcano Engine/火山引擎 Vscale Vultr - webnames.ca + webnames.ca webnames.ru Websupport WEDOS - West.cn/西部数码 + West.cn/西部数码 Yandex 360 Yandex Cloud Yandex PDD - Zone.ee + Zone.ee ZoneEdit Zonomi - diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 0ecc012b3..f73f3920b 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -75,6 +75,7 @@ func allDNSCodes() string { "efficientip", "epik", "eurodns", + "excedo", "exec", "exoscale", "f5xc", @@ -1584,6 +1585,27 @@ func displayDNSHelp(w io.Writer, name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/eurodns`) + case "excedo": + // generated from: providers/dns/excedo/excedo.toml + ew.writeln(`Configuration for Excedo.`) + ew.writeln(`Code: 'excedo'`) + ew.writeln(`Since: 'v4.33.0'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "EXCEDO_API_KEY": API key`) + ew.writeln(` - "EXCEDO_API_URL": API base URL`) + ew.writeln() + + ew.writeln(`Additional Configuration:`) + ew.writeln(` - "EXCEDO_HTTP_TIMEOUT": API request timeout in seconds (Default: 30)`) + ew.writeln(` - "EXCEDO_POLLING_INTERVAL": Time between DNS propagation check in seconds (Default: 10)`) + ew.writeln(` - "EXCEDO_PROPAGATION_TIMEOUT": Maximum waiting time for DNS propagation in seconds (Default: 300)`) + ew.writeln(` - "EXCEDO_TTL": The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)`) + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/excedo`) + case "exec": // generated from: providers/dns/exec/exec.toml ew.writeln(`Configuration for External program.`) diff --git a/docs/content/dns/zz_gen_excedo.md b/docs/content/dns/zz_gen_excedo.md new file mode 100644 index 000000000..456e6f60a --- /dev/null +++ b/docs/content/dns/zz_gen_excedo.md @@ -0,0 +1,69 @@ +--- +title: "Excedo" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: excedo +dnsprovider: + since: "v4.33.0" + code: "excedo" + url: "https://excedo.se/" +--- + + + + + + +Configuration for [Excedo](https://excedo.se/). + + + + +- Code: `excedo` +- Since: v4.33.0 + + +Here is an example bash command using the Excedo provider: + +```bash +EXCEDO_API_KEY=your-api-key \ +EXCEDO_API_URL=your-base-url \ +lego --dns excedo -d '*.example.com' -d example.com run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `EXCEDO_API_KEY` | API key | +| `EXCEDO_API_URL` | API base URL | + +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 | +|--------------------------------|-------------| +| `EXCEDO_HTTP_TIMEOUT` | API request timeout in seconds (Default: 30) | +| `EXCEDO_POLLING_INTERVAL` | Time between DNS propagation check in seconds (Default: 10) | +| `EXCEDO_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation in seconds (Default: 300) | +| `EXCEDO_TTL` | The TTL of the TXT record used for the DNS challenge in seconds (Default: 60) | + +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](none) + + + + diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml index 5736d0ae8..139143b17 100644 --- a/docs/data/zz_cli_help.toml +++ b/docs/data/zz_cli_help.toml @@ -152,7 +152,7 @@ To display the documentation for a specific DNS provider, run: $ lego dnshelp -c code Supported DNS providers: - acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, artfiles, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bluecatv2, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, czechia, ddnss, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, eurodns, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, leaseweb, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, namesurfer, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi + acme-dns, active24, alidns, aliesa, allinkl, alwaysdata, anexia, artfiles, arvancloud, auroradns, autodns, axelname, azion, azure, azuredns, baiducloud, beget, binarylane, bindman, bluecat, bluecatv2, bookmyname, brandit, bunny, checkdomain, civo, clouddns, cloudflare, cloudns, cloudru, cloudxns, com35, conoha, conohav3, constellix, corenetworks, cpanel, czechia, ddnss, derak, desec, designate, digitalocean, directadmin, dnsexit, dnshomede, dnsimple, dnsmadeeasy, dnspod, dode, domeneshop, dreamhost, duckdns, dyn, dyndnsfree, dynu, easydns, edgecenter, edgedns, edgeone, efficientip, epik, eurodns, excedo, exec, exoscale, f5xc, freemyip, gandi, gandiv5, gcloud, gcore, gigahostno, glesys, godaddy, googledomains, gravity, hetzner, hostingde, hostinger, hostingnl, hosttech, httpnet, httpreq, huaweicloud, hurricane, hyperone, ibmcloud, iij, iijdpf, infoblox, infomaniak, internetbs, inwx, ionos, ionoscloud, ipv64, ispconfig, ispconfigddns, iwantmyname, jdcloud, joker, keyhelp, leaseweb, liara, lightsail, limacity, linode, liquidweb, loopia, luadns, mailinabox, manageengine, manual, metaname, metaregistrar, mijnhost, mittwald, myaddr, mydnsjp, mythicbeasts, namecheap, namedotcom, namesilo, namesurfer, nearlyfreespeech, neodigit, netcup, netlify, nicmanager, nicru, nifcloud, njalla, nodion, ns1, octenium, oraclecloud, otc, ovh, pdns, plesk, porkbun, rackspace, rainyun, rcodezero, regfish, regru, rfc2136, rimuhosting, route53, safedns, sakuracloud, scaleway, selectel, selectelv2, selfhostde, servercow, shellrent, simply, sonic, spaceship, stackpath, syse, technitium, tencentcloud, timewebcloud, todaynic, transip, ultradns, uniteddomains, variomedia, vegadns, vercel, versio, vinyldns, virtualname, vkcloud, volcengine, vscale, vultr, webnames, webnamesca, websupport, wedos, westcn, yandex, yandex360, yandexcloud, zoneedit, zoneee, zonomi More information: https://go-acme.github.io/lego/dns """ diff --git a/providers/dns/excedo/excedo.go b/providers/dns/excedo/excedo.go new file mode 100644 index 000000000..ae9128b94 --- /dev/null +++ b/providers/dns/excedo/excedo.go @@ -0,0 +1,176 @@ +// Package excedo implements a DNS provider for solving the DNS-01 challenge using Excedo. +package excedo + +import ( + "context" + "errors" + "fmt" + "net/http" + "strconv" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/platform/config/env" + "github.com/go-acme/lego/v4/providers/dns/excedo/internal" + "github.com/go-acme/lego/v4/providers/dns/internal/clientdebug" +) + +// Environment variables names. +const ( + envNamespace = "EXCEDO_" + + EnvAPIURL = envNamespace + "API_URL" + EnvAPIKey = envNamespace + "API_KEY" + + EnvTTL = envNamespace + "TTL" + EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" + EnvPollingInterval = envNamespace + "POLLING_INTERVAL" + EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT" +) + +var _ challenge.ProviderTimeout = (*DNSProvider)(nil) + +// Config is used to configure the creation of the DNSProvider. +type Config struct { + APIURL string + 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, 60), + PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 5*time.Minute), + PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 10*time.Second), + HTTPClient: &http.Client{ + Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second), + }, + } +} + +// DNSProvider implements the challenge.Provider interface. +type DNSProvider struct { + config *Config + client *internal.Client + + recordsMu sync.Mutex + records map[string]int64 +} + +// NewDNSProvider returns a DNSProvider instance configured for Excedo. +func NewDNSProvider() (*DNSProvider, error) { + values, err := env.Get(EnvAPIURL, EnvAPIKey) + if err != nil { + return nil, fmt.Errorf("excedo: %w", err) + } + + config := NewDefaultConfig() + config.APIURL = values[EnvAPIURL] + config.APIKey = values[EnvAPIKey] + + return NewDNSProviderConfig(config) +} + +// NewDNSProviderConfig return a DNSProvider instance configured for Excedo. +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, errors.New("excedo: the configuration of the DNS provider is nil") + } + + client, err := internal.NewClient(config.APIURL, config.APIKey) + if err != nil { + return nil, fmt.Errorf("excedo: %w", err) + } + + if config.HTTPClient != nil { + client.HTTPClient = config.HTTPClient + } + + client.HTTPClient = clientdebug.Wrap(client.HTTPClient) + + return &DNSProvider{ + config: config, + client: client, + records: make(map[string]int64), + }, nil +} + +// Present creates a TXT record using the specified parameters. +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("excedo: could not find zone for domain %q: %w", domain, err) + } + + subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) + if err != nil { + return fmt.Errorf("excedo: %w", err) + } + + record := internal.Record{ + DomainName: dns01.UnFqdn(authZone), + Name: subDomain, + Type: "TXT", + Content: info.Value, + TTL: strconv.Itoa(d.config.TTL), + } + + recordID, err := d.client.AddRecord(ctx, record) + if err != nil { + return fmt.Errorf("excedo: add record: %w", err) + } + + d.recordsMu.Lock() + d.records[token] = recordID + d.recordsMu.Unlock() + + return nil +} + +// CleanUp removes the TXT record matching the specified parameters. +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + ctx := context.Background() + + info := dns01.GetChallengeInfo(domain, keyAuth) + + authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN) + if err != nil { + return fmt.Errorf("excedo: could not find zone for domain %q: %w", domain, err) + } + + d.recordsMu.Lock() + recordID, ok := d.records[token] + d.recordsMu.Unlock() + + if !ok { + return fmt.Errorf("excedo: unknown record ID for '%s'", info.EffectiveFQDN) + } + + err = d.client.DeleteRecord(ctx, dns01.UnFqdn(authZone), strconv.FormatInt(recordID, 10)) + if err != nil { + return fmt.Errorf("excedo: delete record: %w", err) + } + + d.recordsMu.Lock() + delete(d.records, token) + d.recordsMu.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/excedo/excedo.toml b/providers/dns/excedo/excedo.toml new file mode 100644 index 000000000..9f9874c62 --- /dev/null +++ b/providers/dns/excedo/excedo.toml @@ -0,0 +1,24 @@ +Name = "Excedo" +Description = '''''' +URL = "https://excedo.se/" +Code = "excedo" +Since = "v4.33.0" + +Example = ''' +EXCEDO_API_KEY=your-api-key \ +EXCEDO_API_URL=your-base-url \ +lego --dns excedo -d '*.example.com' -d example.com run +''' + +[Configuration] + [Configuration.Credentials] + EXCEDO_API_KEY = "API key" + EXCEDO_API_URL = "API base URL" + [Configuration.Additional] + EXCEDO_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 10)" + EXCEDO_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 300)" + EXCEDO_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 60)" + EXCEDO_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)" + +[Links] + API = "none" diff --git a/providers/dns/excedo/excedo_test.go b/providers/dns/excedo/excedo_test.go new file mode 100644 index 000000000..f2350c035 --- /dev/null +++ b/providers/dns/excedo/excedo_test.go @@ -0,0 +1,210 @@ +package excedo + +import ( + "net/http/httptest" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/require" +) + +const envDomain = envNamespace + "DOMAIN" + +var envTest = tester.NewEnvTest(EnvAPIURL, EnvAPIKey).WithDomain(envDomain) + +func TestNewDNSProvider(t *testing.T) { + testCases := []struct { + desc string + envVars map[string]string + expected string + }{ + { + desc: "success", + envVars: map[string]string{ + EnvAPIURL: "https://example.com", + EnvAPIKey: "secret", + }, + }, + { + desc: "missing the API key", + envVars: map[string]string{ + EnvAPIURL: "https://example.com", + EnvAPIKey: "", + }, + expected: "excedo: some credentials information are missing: EXCEDO_API_KEY", + }, + { + desc: "missing the API URL", + envVars: map[string]string{ + EnvAPIURL: "", + EnvAPIKey: "secret", + }, + expected: "excedo: some credentials information are missing: EXCEDO_API_URL", + }, + { + desc: "missing credentials", + envVars: map[string]string{}, + expected: "excedo: some credentials information are missing: EXCEDO_API_URL,EXCEDO_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 + apiURL string + apiKey string + expected string + }{ + { + desc: "success", + apiURL: "https://example.com", + apiKey: "secret", + }, + { + desc: "missing the API key", + apiURL: "https://example.com", + expected: "excedo: credentials missing", + }, + { + desc: "missing the API URL", + apiKey: "secret", + expected: "excedo: credentials missing", + }, + { + desc: "missing credentials", + expected: "excedo: credentials missing", + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + config := NewDefaultConfig() + config.APIURL = test.apiURL + 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) +} + +func mockBuilder() *servermock.Builder[*DNSProvider] { + return servermock.NewBuilder( + func(server *httptest.Server) (*DNSProvider, error) { + config := NewDefaultConfig() + config.APIURL = server.URL + config.APIKey = "secret" + config.HTTPClient = server.Client() + + p, err := NewDNSProviderConfig(config) + if err != nil { + return nil, err + } + + return p, nil + }, + ) +} + +func TestDNSProvider_Present(t *testing.T) { + provider := mockBuilder(). + Route("GET /authenticate/login/", + servermock.ResponseFromInternal("login.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer secret"), + ). + Route("POST /dns/addrecord/", + servermock.ResponseFromInternal("addrecord.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer session-token"), + servermock.CheckForm().Strict(). + With("content", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"). + With("domainName", "example.com"). + With("name", "_acme-challenge"). + With("ttl", "60"). + With("type", "TXT"), + ). + Build(t) + + err := provider.Present("example.com", "abc", "123d==") + require.NoError(t, err) +} + +func TestDNSProvider_CleanUp(t *testing.T) { + provider := mockBuilder(). + Route("GET /authenticate/login/", + servermock.ResponseFromInternal("login.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer secret"), + ). + Route("POST /dns/deleterecord/", + servermock.ResponseFromInternal("deleterecord.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer session-token"), + ). + Build(t) + + provider.records["abc"] = 19695822 + + err := provider.CleanUp("example.com", "abc", "123d==") + require.NoError(t, err) +} diff --git a/providers/dns/excedo/internal/client.go b/providers/dns/excedo/internal/client.go new file mode 100644 index 000000000..a5d8be88b --- /dev/null +++ b/providers/dns/excedo/internal/client.go @@ -0,0 +1,205 @@ +package internal + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "sync" + "time" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/providers/dns/internal/errutils" + "github.com/go-acme/lego/v4/providers/dns/internal/useragent" + querystring "github.com/google/go-querystring/query" +) + +type responseChecker interface { + Check() error +} + +// Client the Excedo API client. +type Client struct { + apiKey string + + baseURL *url.URL + HTTPClient *http.Client + + token *ExpirableToken + muToken sync.Mutex +} + +// NewClient creates a new Client. +func NewClient(apiURL, apiKey string) (*Client, error) { + if apiURL == "" || apiKey == "" { + return nil, errors.New("credentials missing") + } + + baseURL, err := url.Parse(apiURL) + if err != nil { + return nil, err + } + + return &Client{ + apiKey: apiKey, + baseURL: baseURL, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, + }, nil +} + +func (c *Client) AddRecord(ctx context.Context, record Record) (int64, error) { + payload, err := querystring.Values(record) + if err != nil { + return 0, err + } + + endpoint := c.baseURL.JoinPath("/dns/addrecord/") + + req, err := newFormRequest(ctx, http.MethodPost, endpoint, payload) + if err != nil { + return 0, err + } + + result := new(AddRecordResponse) + + err = c.doAuthenticated(ctx, req, result) + if err != nil { + return 0, err + } + + return result.RecordID, nil +} + +func (c *Client) DeleteRecord(ctx context.Context, zone, recordID string) error { + endpoint := c.baseURL.JoinPath("/dns/deleterecord/") + + data := map[string]string{ + "domainname": dns01.UnFqdn(zone), + "recordid": recordID, + } + + req, err := newMultipartRequest(ctx, http.MethodPost, endpoint, data) + if err != nil { + return err + } + + result := new(BaseResponse) + + err = c.doAuthenticated(ctx, req, result) + if err != nil { + return err + } + + return nil +} + +func (c *Client) GetRecords(ctx context.Context, zone string) (map[string]Zone, error) { + endpoint := c.baseURL.JoinPath("/dns/getrecords/") + + query := endpoint.Query() + query.Set("domainname", zone) + + endpoint.RawQuery = query.Encode() + + req, err := newFormRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return nil, err + } + + result := new(GetRecordsResponse) + + err = c.doAuthenticated(ctx, req, result) + if err != nil { + return nil, err + } + + return result.DNS, nil +} + +func (c *Client) do(req *http.Request, result responseChecker) error { + useragent.SetHeader(req.Header) + + resp, err := c.HTTPClient.Do(req) + if err != nil { + return errutils.NewHTTPDoError(req, err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode/100 != 2 { + raw, _ := io.ReadAll(resp.Body) + + return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw) + } + + if result == nil { + return nil + } + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return errutils.NewReadResponseError(req, resp.StatusCode, err) + } + + err = json.Unmarshal(raw, result) + if err != nil { + return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) + } + + return result.Check() +} + +func newMultipartRequest(ctx context.Context, method string, endpoint *url.URL, data map[string]string) (*http.Request, error) { + buf := new(bytes.Buffer) + + writer := multipart.NewWriter(buf) + + for k, v := range data { + err := writer.WriteField(k, v) + if err != nil { + return nil, err + } + } + + err := writer.Close() + if err != nil { + return nil, err + } + + body := bytes.NewReader(buf.Bytes()) + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), body) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + req.Header.Set("Content-Type", writer.FormDataContentType()) + + return req, nil +} + +func newFormRequest(ctx context.Context, method string, endpoint *url.URL, form url.Values) (*http.Request, error) { + var body io.Reader + + if len(form) > 0 { + body = bytes.NewReader([]byte(form.Encode())) + } else { + body = http.NoBody + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), body) + if err != nil { + return nil, fmt.Errorf("unable to create request: %w", err) + } + + if method == http.MethodPost { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } + + return req, nil +} diff --git a/providers/dns/excedo/internal/client_test.go b/providers/dns/excedo/internal/client_test.go new file mode 100644 index 000000000..f4fd52c00 --- /dev/null +++ b/providers/dns/excedo/internal/client_test.go @@ -0,0 +1,137 @@ +package internal + +import ( + "net/http/httptest" + "testing" + "time" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func mockBuilder() *servermock.Builder[*Client] { + return servermock.NewBuilder[*Client]( + func(server *httptest.Server) (*Client, error) { + client, err := NewClient(server.URL, "secret") + if err != nil { + return nil, err + } + + client.HTTPClient = server.Client() + + return client, nil + }, + ) +} + +func TestClient_AddRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /dns/addrecord/", + servermock.ResponseFromFixture("addrecord.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer session-token"), + servermock.CheckForm().Strict(). + With("content", "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY"). + With("domainName", "example.com"). + With("name", "_acme-challenge"). + With("ttl", "60"). + With("type", "TXT"), + ). + Build(t) + + client.token = &ExpirableToken{ + Token: "session-token", + Expires: time.Now().Add(6 * time.Hour), + } + + record := Record{ + DomainName: "example.com", + Name: "_acme-challenge", + Type: "TXT", + Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: "60", + } + + recordID, err := client.AddRecord(t.Context(), record) + require.NoError(t, err) + + assert.EqualValues(t, 19695822, recordID) +} + +func TestClient_AddRecord_error(t *testing.T) { + client := mockBuilder(). + Route("POST /dns/addrecord/", + servermock.ResponseFromFixture("error.json"), + ). + Build(t) + + client.token = &ExpirableToken{ + Token: "session-token", + Expires: time.Now().Add(6 * time.Hour), + } + + record := Record{ + DomainName: "example.com", + Name: "_acme-challenge", + Type: "TXT", + Content: "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", + TTL: "60", + } + + _, err := client.AddRecord(t.Context(), record) + require.EqualError(t, err, "2003: Required parameter missing") +} + +func TestClient_DeleteRecord(t *testing.T) { + client := mockBuilder(). + Route("POST /dns/deleterecord/", + servermock.ResponseFromFixture("deleterecord.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer session-token"), + ). + Build(t) + + client.token = &ExpirableToken{ + Token: "session-token", + Expires: time.Now().Add(6 * time.Hour), + } + + err := client.DeleteRecord(t.Context(), "example.com", "19695822") + require.NoError(t, err) +} + +func TestClient_GetRecords(t *testing.T) { + client := mockBuilder(). + Route("GET /dns/getrecords/", + servermock.ResponseFromFixture("getrecords.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer session-token"), + servermock.CheckQueryParameter().Strict(). + With("domainname", "example.com"), + ). + Build(t) + + client.token = &ExpirableToken{ + Token: "session-token", + Expires: time.Now().Add(6 * time.Hour), + } + + zones, err := client.GetRecords(t.Context(), "example.com") + require.NoError(t, err) + + expected := map[string]Zone{ + "example.com": { + DNSType: "type", + Records: []Record{{ + RecordID: "1234", + Name: "_acme-challenge.example.com", + Type: "TXT", + Content: "txt-value", + TTL: "60", + }}, + }, + } + + assert.Equal(t, expected, zones) +} diff --git a/providers/dns/excedo/internal/fixtures/addrecord.json b/providers/dns/excedo/internal/fixtures/addrecord.json new file mode 100644 index 000000000..f1f7bf958 --- /dev/null +++ b/providers/dns/excedo/internal/fixtures/addrecord.json @@ -0,0 +1,15 @@ +{ + "code": 1000, + "desc": "Command completed successfully", + "recordid": 19695822, + "session": { + "accID": "1234", + "usrID": "1234", + "status": "active", + "expire": { + "date": "2026-03-10 19:03:18", + "seconds": 5678 + } + }, + "runtime": 0.2852 +} diff --git a/providers/dns/excedo/internal/fixtures/deleterecord.json b/providers/dns/excedo/internal/fixtures/deleterecord.json new file mode 100644 index 000000000..5c2431b1c --- /dev/null +++ b/providers/dns/excedo/internal/fixtures/deleterecord.json @@ -0,0 +1,14 @@ +{ + "code": 1000, + "desc": "Command completed successfully", + "session": { + "accID": "1234", + "usrID": "1234", + "status": "active", + "expire": { + "date": "2026-03-10 19:03:18", + "seconds": 5678 + } + }, + "runtime": 0.2852 +} diff --git a/providers/dns/excedo/internal/fixtures/error.json b/providers/dns/excedo/internal/fixtures/error.json new file mode 100644 index 000000000..5a24ec247 --- /dev/null +++ b/providers/dns/excedo/internal/fixtures/error.json @@ -0,0 +1,18 @@ +{ + "code": 2003, + "desc": "Required parameter missing", + "missing": [ + "domainname", + "recordid" + ], + "session": { + "accID": "1234", + "usrID": "1234", + "status": "active", + "expire": { + "date": "2026-03-10 19:03:18", + "seconds": 5485 + } + }, + "runtime": 0.0534 +} diff --git a/providers/dns/excedo/internal/fixtures/getrecords.json b/providers/dns/excedo/internal/fixtures/getrecords.json new file mode 100644 index 000000000..215a8abb2 --- /dev/null +++ b/providers/dns/excedo/internal/fixtures/getrecords.json @@ -0,0 +1,23 @@ +{ + "code": 1000, + "desc": "Command completed successfully", + "dns": { + "example.com": { + "dnstype": "type", + "recordusage": { + "used": 74 + }, + "records": [ + { + "recordid": "1234", + "name": "_acme-challenge.example.com", + "type": "TXT", + "content": "txt-value", + "ttl": "60", + "prio": null, + "change_date": null + } + ] + } + } +} diff --git a/providers/dns/excedo/internal/fixtures/login.json b/providers/dns/excedo/internal/fixtures/login.json new file mode 100644 index 000000000..2defb9843 --- /dev/null +++ b/providers/dns/excedo/internal/fixtures/login.json @@ -0,0 +1,7 @@ +{ + "code": 1000, + "desc": "Command completed successfully", + "parameters": { + "token": "session-token" + } +} diff --git a/providers/dns/excedo/internal/identity.go b/providers/dns/excedo/internal/identity.go new file mode 100644 index 000000000..5c9ca119d --- /dev/null +++ b/providers/dns/excedo/internal/identity.go @@ -0,0 +1,75 @@ +package internal + +import ( + "context" + "fmt" + "net/http" + "time" +) + +type ExpirableToken struct { + Token string + Expires time.Time +} + +func (t *ExpirableToken) IsExpired() bool { + return time.Now().After(t.Expires) +} + +func (c *Client) Login(ctx context.Context) (string, error) { + endpoint := c.baseURL.JoinPath("/authenticate/login/") + + req, err := newFormRequest(ctx, http.MethodGet, endpoint, nil) + if err != nil { + return "", err + } + + req.Header.Set("Authorization", "Bearer "+c.apiKey) + + result := new(LoginResponse) + + err = c.do(req, result) + if err != nil { + return "", err + } + + if result.Code != 1000 && result.Code != 1300 { + return "", fmt.Errorf("%d: %s", result.Code, result.Description) + } + + return result.Parameters.Token, nil +} + +func (c *Client) authenticate(ctx context.Context) (string, error) { + c.muToken.Lock() + defer c.muToken.Unlock() + + if c.token == nil || c.token.IsExpired() { + token, err := c.Login(ctx) + if err != nil { + return "", err + } + + c.token = &ExpirableToken{ + Token: token, + Expires: time.Now().Add(2*time.Hour - time.Minute), + } + + return token, nil + } + + return c.token.Token, nil +} + +func (c *Client) doAuthenticated(ctx context.Context, req *http.Request, result responseChecker) error { + token, err := c.authenticate(ctx) + if err != nil { + return err + } + + if token != "" { + req.Header.Set("Authorization", "Bearer "+token) + } + + return c.do(req, result) +} diff --git a/providers/dns/excedo/internal/identity_test.go b/providers/dns/excedo/internal/identity_test.go new file mode 100644 index 000000000..86b7eb9d8 --- /dev/null +++ b/providers/dns/excedo/internal/identity_test.go @@ -0,0 +1,35 @@ +package internal + +import ( + "testing" + + "github.com/go-acme/lego/v4/platform/tester/servermock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestClient_Login(t *testing.T) { + client := mockBuilder(). + Route("GET /authenticate/login/", + servermock.ResponseFromFixture("login.json"), + servermock.CheckHeader(). + WithAuthorization("Bearer secret"), + ). + Build(t) + + token, err := client.Login(t.Context()) + require.NoError(t, err) + + assert.Equal(t, "session-token", token) +} + +func TestClient_Login_error(t *testing.T) { + client := mockBuilder(). + Route("GET /authenticate/login/", + servermock.ResponseFromFixture("error.json"), + ). + Build(t) + + _, err := client.Login(t.Context()) + require.EqualError(t, err, "2003: Required parameter missing") +} diff --git a/providers/dns/excedo/internal/types.go b/providers/dns/excedo/internal/types.go new file mode 100644 index 000000000..eb6ce8462 --- /dev/null +++ b/providers/dns/excedo/internal/types.go @@ -0,0 +1,65 @@ +package internal + +import "fmt" + +type BaseResponse struct { + Code int `json:"code"` + Description string `json:"desc"` +} + +func (r BaseResponse) Check() error { + // Response codes: + // - 1000: Command completed successfully + // - 1300: Command completed successfully; no messages + // - 2001: Command syntax error + // - 2002: Command use error + // - 2003: Required parameter missing + // - 2004: Parameter value range error + // - 2104: Billing failure + // - 2200: Authentication error + // - 2201: Authorization error + // - 2303: Object does not exist + // - 2304: Object status prohibits operation + // - 2309: Object duplicate found + // - 2400: Command failed + // - 2500: Command failed; server closing connection + if r.Code != 1000 && r.Code != 1300 { + return fmt.Errorf("%d: %s", r.Code, r.Description) + } + + return nil +} + +type GetRecordsResponse struct { + BaseResponse + + DNS map[string]Zone `json:"dns"` +} + +type Zone struct { + DNSType string `json:"dnstype"` + Records []Record `json:"records"` +} + +type Record struct { + DomainName string `json:"domainName,omitempty" url:"domainName,omitempty"` + RecordID string `json:"recordid,omitempty" url:"recordid,omitempty"` + Name string `json:"name,omitempty" url:"name,omitempty"` + Type string `json:"type,omitempty" url:"type,omitempty"` + Content string `json:"content,omitempty" url:"content,omitempty"` + TTL string `json:"ttl,omitempty" url:"ttl,omitempty"` +} + +type AddRecordResponse struct { + BaseResponse + + RecordID int64 `json:"recordid"` +} + +type LoginResponse struct { + BaseResponse + + Parameters struct { + Token string `json:"token"` + } `json:"parameters"` +} diff --git a/providers/dns/zz_gen_dns_providers.go b/providers/dns/zz_gen_dns_providers.go index 519cc93ec..9c4bc9e61 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -69,6 +69,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/efficientip" "github.com/go-acme/lego/v4/providers/dns/epik" "github.com/go-acme/lego/v4/providers/dns/eurodns" + "github.com/go-acme/lego/v4/providers/dns/excedo" "github.com/go-acme/lego/v4/providers/dns/exec" "github.com/go-acme/lego/v4/providers/dns/exoscale" "github.com/go-acme/lego/v4/providers/dns/f5xc" @@ -327,6 +328,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return epik.NewDNSProvider() case "eurodns": return eurodns.NewDNSProvider() + case "excedo": + return excedo.NewDNSProvider() case "exec": return exec.NewDNSProvider() case "exoscale": From 87b172f103b26c8ea40c5e811576ad454f7b6891 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 12 Mar 2026 21:27:46 +0100 Subject: [PATCH 298/298] gigahostno: remove unused Zone fields (#2913) --- .../dns/gigahostno/internal/client_test.go | 60 +++++-------------- .../gigahostno/internal/fixtures/zones.json | 6 +- providers/dns/gigahostno/internal/types.go | 20 ++----- 3 files changed, 23 insertions(+), 63 deletions(-) diff --git a/providers/dns/gigahostno/internal/client_test.go b/providers/dns/gigahostno/internal/client_test.go index aac65bceb..8d1298947 100644 --- a/providers/dns/gigahostno/internal/client_test.go +++ b/providers/dns/gigahostno/internal/client_test.go @@ -38,55 +38,25 @@ func TestClient_GetZones(t *testing.T) { expected := []Zone{ { - ID: "123", - Name: "example.com", - NameDisplay: "example.com", - Type: "NATIVE", - Active: "1", - Protected: "1", - IsRegistered: "1", - Updated: false, - CustomerID: "16030", - DomainRegistrar: "norid", - DomainStatus: "active", - DomainExpiryDate: "2026-11-23 15:17:38", - DomainAutoRenew: "1", - ExternalDNS: "0", - RecordCount: 4, + ID: "123", + Name: "example.com", + NameDisplay: "example.com", + Type: "NATIVE", + Active: "1", }, { - ID: "226", - Name: "example.org", - NameDisplay: "example.org", - Type: "NATIVE", - Active: "1", - Protected: "1", - IsRegistered: "1", - Updated: false, - CustomerID: "16030", - DomainRegistrar: "norid", - DomainStatus: "active", - DomainExpiryDate: "2026-11-23 14:15:01", - DomainAutoRenew: "1", - ExternalDNS: "0", - RecordCount: 5, + ID: "226", + Name: "example.org", + NameDisplay: "example.org", + Type: "NATIVE", + Active: "1", }, { - ID: "229", - Name: "example.xn--zckzah", - NameDisplay: "example.テスト", - Type: "NATIVE", - Active: "1", - Protected: "1", - IsRegistered: "1", - Updated: false, - CustomerID: "16030", - DomainRegistrar: "norid", - DomainStatus: "active", - DomainExpiryDate: "2026-12-01 12:40:48", - DomainAutoRenew: "1", - ExternalDNS: "0", - RecordCount: 4, + ID: "229", + Name: "example.xn--zckzah", + NameDisplay: "example.テスト", + Type: "NATIVE", + Active: "1", }, } diff --git a/providers/dns/gigahostno/internal/fixtures/zones.json b/providers/dns/gigahostno/internal/fixtures/zones.json index f4d927335..d45b0ac49 100644 --- a/providers/dns/gigahostno/internal/fixtures/zones.json +++ b/providers/dns/gigahostno/internal/fixtures/zones.json @@ -30,7 +30,7 @@ "domain_dnssec_data": null, "domain_protected_email": null, "zone_created": "2025-11-23 16:17:29", - "zone_updated": false, + "zone_updated": 1700000000, "external_dns": "0", "record_count": 4, "zone_name_display": "example.com" @@ -59,7 +59,7 @@ "domain_dnssec_data": null, "domain_protected_email": null, "zone_created": "2025-11-23 15:13:27", - "zone_updated": false, + "zone_updated": 1700000000, "external_dns": "0", "record_count": 5, "zone_name_display": "example.org" @@ -88,7 +88,7 @@ "domain_dnssec_data": null, "domain_protected_email": null, "zone_created": "2025-11-23 16:37:15", - "zone_updated": false, + "zone_updated": 1700000000, "external_dns": "0", "record_count": 4, "zone_name_display": "example.\u30C6\u30B9\u30C8" diff --git a/providers/dns/gigahostno/internal/types.go b/providers/dns/gigahostno/internal/types.go index cbb7b8b23..e998dc084 100644 --- a/providers/dns/gigahostno/internal/types.go +++ b/providers/dns/gigahostno/internal/types.go @@ -26,21 +26,11 @@ type APIResponse[T any] struct { } type Zone struct { - ID string `json:"zone_id,omitempty"` - Name string `json:"zone_name,omitempty"` - NameDisplay string `json:"zone_name_display,omitempty"` - Type string `json:"zone_type,omitempty"` - Active string `json:"zone_active,omitempty"` - Protected string `json:"zone_protected,omitempty"` - IsRegistered string `json:"zone_is_registered,omitempty"` - Updated bool `json:"zone_updated,omitempty"` - CustomerID string `json:"cust_id,omitempty"` - DomainRegistrar string `json:"domain_registrar,omitempty"` - DomainStatus string `json:"domain_status,omitempty"` - DomainExpiryDate string `json:"domain_expiry_date,omitempty"` - DomainAutoRenew string `json:"domain_auto_renew,omitempty"` - ExternalDNS string `json:"external_dns,omitempty"` - RecordCount int `json:"record_count,omitempty"` + ID string `json:"zone_id,omitempty"` + Name string `json:"zone_name,omitempty"` + NameDisplay string `json:"zone_name_display,omitempty"` + Type string `json:"zone_type,omitempty"` + Active string `json:"zone_active,omitempty"` } type Record struct {