diff --git a/.github/ISSUE_TEMPLATE/new_dns_provider.yml b/.github/ISSUE_TEMPLATE/new_dns_provider.yml index b319bc287..cfd6e5c8c 100644 --- a/.github/ISSUE_TEMPLATE/new_dns_provider.yml +++ b/.github/ISSUE_TEMPLATE/new_dns_provider.yml @@ -14,15 +14,9 @@ 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 can test an implementation with the help of the maintainers if someone creates a pull request. + - label: Yes, I'm able to test an implementation if someone creates a pull request to add the support of this DNS provider. required: false - type: dropdown diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 33ca106cc..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.10 + GOLANGCI_LINT_VERSION: v2.8.0 HUGO_VERSION: 0.148.2 CGO_ENABLED: 0 LEGO_E2E_TESTS: CI @@ -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 - run: docker run -d --rm -p 11211:11211 memcached:1.6-alpine + uses: niden/actions-memcached@v7 - name: Make run: | diff --git a/.golangci.yml b/.golangci.yml index b6ab51ccc..b851169ff 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -183,9 +183,6 @@ 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: diff --git a/CHANGELOG.md b/CHANGELOG.md index ae73f70f3..ee191cdb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,36 +6,6 @@ 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/README.md b/README.md index e90e94962..c02347e23 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # Lego -[ACME](https://www.rfc-editor.org/rfc/rfc8555.html) client and library for Let's Encrypt and other ACME CAs written in Go. +Let's Encrypt client and ACME library 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) @@ -73,233 +73,223 @@ If your DNS provider is not supported, please open an [issue](https://github.com Amazon Route 53 Anexia CloudDNS - ANS SafeDNS - ArtFiles - ArvanCloud Aurora DNS + Autodns Axelname - Azion Azure (deprecated) + Azure DNS Baidu Cloud - Beget.com Binary Lane + Bindman Bluecat - - Bluecat v2 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 - Czechia - DDnss (DynDNS Service) + CPanel/WHM Derak Cloud deSEC.io - Designate DNSaaS for Openstack + Digital Ocean DirectAdmin DNS Made Easy - - DNSExit dnsHome.de + DNSimple DNSPod (deprecated) - Domain Offensive (do.de) Domeneshop + DreamHost Duck DNS - Dyn DynDnsFree.de + Dynu EasyDNS - EdgeCenter Efficient IP - Epik - EuroDNS - Excedo + Epik Exoscale External program F5 XC freemyip.com - FusionLayer NameSurfer G-Core Gandi - Gandi Live DNS (v5) + Gigahost.no Glesys Go Daddy - Google Cloud + Google Domains Gravity Hetzner - Hosting.de + Hosting.nl Hostinger Hosttech - HTTP request + http.net Huawei Cloud Hurricane Electric DNS - HyperOne + IBM Cloud (SoftLayer) IIJ DNS Platform Service Infoblox - Infomaniak + Internet Initiative Japan Internet.bs INWX - Ionos + Ionos Cloud IPv64 ISPConfig 3 - ISPConfig 3 - Dynamic DNS (DDNS) Module + iwantmyname (Deprecated) JD Cloud Joker - Joohoi's ACME-DNS - KeyHelp - Leaseweb - Liara + KeyHelp + Liara Lima-City Linode (v4) + Liquid Web Loopia - LuaDNS Mail-in-a-Box + ManageEngine CloudDNS Manual - Metaname Metaregistrar + mijn.host Mittwald - myaddr.{tools,dev,io} MyDNS.jp + MythicBeasts Name.com - Namecheap Namesilo + NearlyFreeSpeech.NET Neodigit - Netcup Netlify + Nicmanager NIFCloud - Njalla Nodion + NS1 Octenium - Open Telekom Cloud Oracle Cloud + OVH plesk.com - Porkbun PowerDNS + Rackspace Rain Yun/雨云 - RcodeZero reg.ru + Regfish RFC2136 - RimuHosting RU CENTER + Sakura Cloud Scaleway - Selectel Selectel v2 + SelfHost.(de|eu) Servercow - Shellrent Simply.com + Sonic Spaceship - Stackpath Syse + Technitium Tencent Cloud DNS - Tencent EdgeOne Timeweb Cloud - TodayNIC/时代互联 - TransIP + TransIP + UKFast SafeDNS Ultradns United-Domains + Variomedia VegaDNS - Vercel Versio.[nl|eu|uk] + VinylDNS Virtualname - VK Cloud Volcano Engine/火山引擎 + Vscale Vultr - webnames.ca webnames.ru + Websupport WEDOS - West.cn/西部数码 Yandex 360 + Yandex Cloud Yandex PDD - Zone.ee ZoneEdit + Zonomi + + diff --git a/acme/api/identifier.go b/acme/api/identifier.go index 245ed8515..42a8fd391 100644 --- a/acme/api/identifier.go +++ b/acme/api/identifier.go @@ -2,6 +2,7 @@ package api import ( "cmp" + "maps" "net" "slices" @@ -9,9 +10,7 @@ import ( ) func createIdentifiers(domains []string) []acme.Identifier { - uniqIdentifiers := make(map[string]struct{}) - - var identifiers []acme.Identifier + uniqIdentifiers := make(map[string]acme.Identifier) for _, domain := range domains { if _, ok := uniqIdentifiers[domain]; ok { @@ -24,12 +23,10 @@ func createIdentifiers(domains []string) []acme.Identifier { ident.Type = "ip" } - identifiers = append(identifiers, ident) - - uniqIdentifiers[domain] = struct{}{} + uniqIdentifiers[domain] = ident } - return identifiers + return slices.AppendSeq(make([]acme.Identifier, 0, len(uniqIdentifiers)), maps.Values(uniqIdentifiers)) } // compareIdentifiers compares 2 slices of [acme.Identifier]. diff --git a/acme/api/internal/sender/useragent.go b/acme/api/internal/sender/useragent.go index 51a1b4770..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.32.0" + 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/acme/api/service.go b/acme/api/service.go index 22ce05124..65518e1d9 100644 --- a/acme/api/service.go +++ b/acme/api/service.go @@ -1,11 +1,8 @@ package api import ( - "fmt" "net/http" "regexp" - "strconv" - "time" ) type service struct { @@ -59,29 +56,3 @@ 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 57ea45708..2dbd795c9 100644 --- a/acme/api/service_test.go +++ b/acme/api/service_test.go @@ -3,10 +3,8 @@ package api import ( "net/http" "testing" - "time" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func Test_getLink(t *testing.T) { @@ -55,38 +53,3 @@ 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/acme/errors.go b/acme/errors.go index cd447d7b4..be4721c9d 100644 --- a/acme/errors.go +++ b/acme/errors.go @@ -29,18 +29,18 @@ type ProblemDetails struct { } func (p *ProblemDetails) Error() string { - msg := new(strings.Builder) + var msg strings.Builder - _, _ = fmt.Fprintf(msg, "acme: error: %d", p.HTTPStatus) + msg.WriteString(fmt.Sprintf("acme: error: %d", p.HTTPStatus)) if p.Method != "" || p.URL != "" { - _, _ = fmt.Fprintf(msg, " :: %s :: %s", p.Method, p.URL) + msg.WriteString(fmt.Sprintf(" :: %s :: %s", p.Method, p.URL)) } - _, _ = fmt.Fprintf(msg, " :: %s :: %s", p.Type, p.Detail) + msg.WriteString(fmt.Sprintf(" :: %s :: %s", p.Type, p.Detail)) for _, sub := range p.SubProblems { - _, _ = fmt.Fprintf(msg, ", problem: %q :: %s", sub.Type, sub.Detail) + msg.WriteString(fmt.Sprintf(", problem: %q :: %s", sub.Type, sub.Detail)) } if p.Instance != "" { diff --git a/certcrypto/crypto.go b/certcrypto/crypto.go index 800bb3f5b..00f0654b9 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, cert.IPAddresses) + return getMainDomain(cert.Subject, cert.DNSNames) } func GetCSRMainDomain(cert *x509.CertificateRequest) (string, error) { - return getMainDomain(cert.Subject, cert.DNSNames, cert.IPAddresses) + return getMainDomain(cert.Subject, cert.DNSNames) } -func getMainDomain(subject pkix.Name, dnsNames []string, ips []net.IP) (string, error) { - if subject.CommonName == "" && len(dnsNames) == 0 && len(ips) == 0 { +func getMainDomain(subject pkix.Name, dnsNames []string) (string, error) { + if subject.CommonName == "" && len(dnsNames) == 0 { return "", errors.New("missing domain") } @@ -258,11 +258,7 @@ func getMainDomain(subject pkix.Name, dnsNames []string, ips []net.IP) (string, return subject.CommonName, nil } - if len(dnsNames) > 0 { - return dnsNames[0], nil - } - - return ips[0].String(), nil + return dnsNames[0], nil } func ExtractDomains(cert *x509.Certificate) []string { diff --git a/certificate/renewal.go b/certificate/renewal.go index 59d31cfb5..15e804745 100644 --- a/certificate/renewal.go +++ b/certificate/renewal.go @@ -11,7 +11,6 @@ import ( "time" "github.com/go-acme/lego/v4/acme" - "github.com/go-acme/lego/v4/acme/api" ) // RenewalInfoRequest contains the necessary renewal information. @@ -93,9 +92,9 @@ func (c *Certifier) GetRenewalInfo(req RenewalInfoRequest) (*RenewalInfoResponse } if retry := resp.Header.Get("Retry-After"); retry != "" { - info.RetryAfter, err = api.ParseRetryAfter(retry) + info.RetryAfter, err = time.ParseDuration(retry + "s") if err != nil { - return nil, fmt.Errorf("failed to parse Retry-After header: %w", err) + return nil, err } } diff --git a/certificate/renewal_test.go b/certificate/renewal_test.go index 23209638a..6ce43e0aa 100644 --- a/certificate/renewal_test.go +++ b/certificate/renewal_test.go @@ -74,42 +74,6 @@ 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/errors.go b/challenge/resolver/errors.go index 65a6ccdb7..6a859922c 100644 --- a/challenge/resolver/errors.go +++ b/challenge/resolver/errors.go @@ -3,8 +3,6 @@ package resolver import ( "bytes" "fmt" - "maps" - "slices" "sort" ) @@ -27,7 +25,3 @@ 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 deleted file mode 100644 index d4ab3c481..000000000 --- a/challenge/resolver/errors_test.go +++ /dev/null @@ -1,70 +0,0 @@ -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)) - }) - } -} diff --git a/challenge/resolver/prober.go b/challenge/resolver/prober.go index 66b12c7a7..aac1016d8 100644 --- a/challenge/resolver/prober.go +++ b/challenge/resolver/prober.go @@ -98,24 +98,11 @@ 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 @@ -124,8 +111,6 @@ func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) { continue } - - uniq[authSolver.authz.Identifier.Value+chlg.Token] = struct{}{} } // Solve challenge @@ -138,43 +123,22 @@ func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) { continue } - if _, ok := uniq[authSolver.authz.Identifier.Value+chlg.Token]; ok || chlg.Token == "" { - // Clean challenge - cleanUp(authSolver.solver, authSolver.authz) + // 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) - } - - delete(uniq, authSolver.authz.Identifier.Value+chlg.Token) - } else { - log.Infof("acme: duplicate token for %q (DNS-01); skipping cleanup.", authSolver.authz.Identifier.Value) + if len(authSolvers)-1 > i { + solvr := authSolver.solver.(sequential) + _, interval := solvr.Sequential() + log.Infof("sequence: wait for %s", interval) + time.Sleep(interval) } } } 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 { @@ -186,16 +150,6 @@ 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 dc7ad8dec..5a91fe075 100644 --- a/challenge/resolver/prober_mock_test.go +++ b/challenge/resolver/prober_mock_test.go @@ -1,7 +1,6 @@ package resolver import ( - "fmt" "time" "github.com/go-acme/lego/v4/acme" @@ -12,68 +11,34 @@ 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{ - Wildcard: wildcard, - Status: status, - Expires: time.Now(), + Status: status, + Expires: time.Now(), Identifier: acme.Identifier{ - Type: "dns", + Type: challenge.HTTP01.String(), Value: domain, }, - Challenges: chlgs, + Challenges: []acme.Challenge{ + { + Type: challenge.HTTP01.String(), + Validated: time.Now(), + Error: nil, + }, + }, } } diff --git a/challenge/resolver/prober_test.go b/challenge/resolver/prober_test.go index 829b16883..06ff07d2c 100644 --- a/challenge/resolver/prober_test.go +++ b/challenge/resolver/prober_test.go @@ -2,22 +2,19 @@ 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 - expectedCounters map[challenge.Type]string + desc string + solvers map[challenge.Type]solver + authz []acme.Authorization + expectedError string }{ { desc: "success", @@ -33,30 +30,6 @@ 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", @@ -72,9 +45,6 @@ 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", @@ -99,9 +69,6 @@ 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", @@ -128,9 +95,6 @@ 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", - }, }, } @@ -148,10 +112,6 @@ 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)) - } }) } } diff --git a/challenge/resolver/solver_manager.go b/challenge/resolver/solver_manager.go index 87cf6e2d8..48d9194b9 100644 --- a/challenge/resolver/solver_manager.go +++ b/challenge/resolver/solver_manager.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "sort" + "strconv" "time" "github.com/cenkalti/backoff/v5" @@ -93,20 +94,22 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error { return nil } - retryAfter, err := api.ParseRetryAfter(chlng.RetryAfter) - if err != nil || retryAfter == 0 { + ra, err := strconv.Atoi(chlng.RetryAfter) + if err != nil { // The ACME server MUST return a Retry-After. - // If it doesn't, or if it's invalid, we'll just poll hard. + // If it doesn't, 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 - retryAfter = 5 * time.Second + ra = 5 } + initialInterval := time.Duration(ra) * time.Second + ctx := context.Background() bo := backoff.NewExponentialBackOff() - bo.InitialInterval = retryAfter - bo.MaxInterval = 10 * retryAfter + bo.InitialInterval = initialInterval + bo.MaxInterval = 10 * initialInterval // After the path is sent, the ACME server will access our server. // Repeatedly check the server for an updated status on our request. @@ -131,7 +134,7 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error { return wait.Retry(ctx, operation, backoff.WithBackOff(bo), - backoff.WithMaxElapsedTime(100*retryAfter)) + backoff.WithMaxElapsedTime(100*initialInterval)) } func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) { diff --git a/cmd/cmd_list.go b/cmd/cmd_list.go index 53cd12c3c..483592d47 100644 --- a/cmd/cmd_list.go +++ b/cmd/cmd_list.go @@ -3,7 +3,6 @@ package cmd import ( "encoding/json" "fmt" - "net" "net/url" "os" "path/filepath" @@ -101,11 +100,6 @@ 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() @@ -156,12 +150,3 @@ 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, ", ") -} diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index 5924c4b66..16814b4de 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 private keys -generated by lego and certificates obtained from the ACME -server. Making regular backups of this folder is ideal. +configuration directory will also contain certificates and +private keys obtained from the ACME server so making regular +backups of this folder is ideal. ` func run(ctx *cli.Context) error { diff --git a/cmd/lego/zz_gen_version.go b/cmd/lego/zz_gen_version.go index cf9ad00ef..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.32.0+dev-detach" +const defaultVersion = "v4.31.0+dev-release" var version = "" diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index f73f3920b..a918a1484 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -19,7 +19,6 @@ func allDNSCodes() string { "allinkl", "alwaysdata", "anexia", - "artfiles", "arvancloud", "auroradns", "autodns", @@ -32,7 +31,6 @@ func allDNSCodes() string { "binarylane", "bindman", "bluecat", - "bluecatv2", "bookmyname", "brandit", "bunny", @@ -49,14 +47,11 @@ func allDNSCodes() string { "constellix", "corenetworks", "cpanel", - "czechia", - "ddnss", "derak", "desec", "designate", "digitalocean", "directadmin", - "dnsexit", "dnshomede", "dnsimple", "dnsmadeeasy", @@ -74,8 +69,6 @@ func allDNSCodes() string { "edgeone", "efficientip", "epik", - "eurodns", - "excedo", "exec", "exoscale", "f5xc", @@ -115,7 +108,6 @@ func allDNSCodes() string { "jdcloud", "joker", "keyhelp", - "leaseweb", "liara", "lightsail", "limacity", @@ -136,7 +128,6 @@ func allDNSCodes() string { "namecheap", "namedotcom", "namesilo", - "namesurfer", "nearlyfreespeech", "neodigit", "netcup", @@ -178,7 +169,6 @@ func allDNSCodes() string { "technitium", "tencentcloud", "timewebcloud", - "todaynic", "transip", "ultradns", "uniteddomains", @@ -269,10 +259,8 @@ 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() @@ -363,27 +351,6 @@ 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.`) @@ -653,31 +620,6 @@ 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.`) @@ -1029,47 +971,6 @@ 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).`) - 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.`) @@ -1185,26 +1086,6 @@ 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.`) @@ -1564,48 +1445,6 @@ 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 "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.`) @@ -2424,26 +2263,6 @@ 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.`) @@ -2459,7 +2278,6 @@ 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() @@ -2852,30 +2670,6 @@ 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.`) @@ -3439,7 +3233,7 @@ func displayDNSHelp(w io.Writer, name string) error { case "safedns": // generated from: providers/dns/safedns/safedns.toml - ew.writeln(`Configuration for ANS SafeDNS.`) + ew.writeln(`Configuration for UKFast SafeDNS.`) ew.writeln(`Code: 'safedns'`) ew.writeln(`Since: 'v4.6.0'`) ew.writeln() @@ -3780,27 +3574,6 @@ 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_alidns.md b/docs/content/dns/zz_gen_alidns.md index 4ded782ab..e498a31dd 100644 --- a/docs/content/dns/zz_gen_alidns.md +++ b/docs/content/dns/zz_gen_alidns.md @@ -58,10 +58,8 @@ 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/docs/content/dns/zz_gen_artfiles.md b/docs/content/dns/zz_gen_artfiles.md deleted file mode 100644 index 15ac2d964..000000000 --- a/docs/content/dns/zz_gen_artfiles.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -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/content/dns/zz_gen_bluecatv2.md b/docs/content/dns/zz_gen_bluecatv2.md deleted file mode 100644 index 7d748df99..000000000 --- a/docs/content/dns/zz_gen_bluecatv2.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -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/content/dns/zz_gen_czechia.md b/docs/content/dns/zz_gen_czechia.md deleted file mode 100644 index 7b1cdd1ae..000000000 --- a/docs/content/dns/zz_gen_czechia.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -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/content/dns/zz_gen_ddnss.md b/docs/content/dns/zz_gen_ddnss.md deleted file mode 100644 index e159d58b4..000000000 --- a/docs/content/dns/zz_gen_ddnss.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -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/content/dns/zz_gen_dnsexit.md b/docs/content/dns/zz_gen_dnsexit.md deleted file mode 100644 index aca5357e8..000000000 --- a/docs/content/dns/zz_gen_dnsexit.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -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/content/dns/zz_gen_eurodns.md b/docs/content/dns/zz_gen_eurodns.md deleted file mode 100644 index cb5a0418d..000000000 --- a/docs/content/dns/zz_gen_eurodns.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -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/content/dns/zz_gen_excedo.md b/docs/content/dns/zz_gen_excedo.md deleted file mode 100644 index 456e6f60a..000000000 --- a/docs/content/dns/zz_gen_excedo.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -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/content/dns/zz_gen_leaseweb.md b/docs/content/dns/zz_gen_leaseweb.md deleted file mode 100644 index 13ded490a..000000000 --- a/docs/content/dns/zz_gen_leaseweb.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -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/content/dns/zz_gen_liara.md b/docs/content/dns/zz_gen_liara.md index 658ce8077..8a6ddbd99 100644 --- a/docs/content/dns/zz_gen_liara.md +++ b/docs/content/dns/zz_gen_liara.md @@ -50,7 +50,6 @@ 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/docs/content/dns/zz_gen_manual.md b/docs/content/dns/zz_gen_manual.md index 832ccaf58..056726c74 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 -configuration directory at "./.lego/accounts". + 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 private keys -generated by lego and certificates obtained from the ACME -server. Making regular backups of this folder is ideal. + 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 diff --git a/docs/content/dns/zz_gen_namesurfer.md b/docs/content/dns/zz_gen_namesurfer.md deleted file mode 100644 index 9a2802d0e..000000000 --- a/docs/content/dns/zz_gen_namesurfer.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -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/content/dns/zz_gen_safedns.md b/docs/content/dns/zz_gen_safedns.md index 4c20fca6a..e040a8a9f 100644 --- a/docs/content/dns/zz_gen_safedns.md +++ b/docs/content/dns/zz_gen_safedns.md @@ -1,12 +1,12 @@ --- -title: "ANS SafeDNS" +title: "UKFast SafeDNS" date: 2019-03-03T16:39:46+01:00 draft: false slug: safedns dnsprovider: since: "v4.6.0" code: "safedns" - url: "https://www.ans.co.uk/" + url: "https://www.ukfast.co.uk/dns-hosting.html" --- @@ -14,7 +14,7 @@ dnsprovider: -Configuration for [ANS SafeDNS](https://www.ans.co.uk/). +Configuration for [UKFast SafeDNS](https://www.ukfast.co.uk/dns-hosting.html). @@ -23,7 +23,7 @@ Configuration for [ANS SafeDNS](https://www.ans.co.uk/). - Since: v4.6.0 -Here is an example bash command using the ANS SafeDNS provider: +Here is an example bash command using the UKFast SafeDNS provider: ```bash SAFEDNS_AUTH_TOKEN=xxxxxx \ diff --git a/docs/content/dns/zz_gen_todaynic.md b/docs/content/dns/zz_gen_todaynic.md deleted file mode 100644 index 7b06c012d..000000000 --- a/docs/content/dns/zz_gen_todaynic.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -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 139143b17..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, 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 + 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 b8e88428e..42e11820b 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.21.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 @@ -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.15 + github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 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.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/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/aziontech/azionapi-go-sdk v0.144.0 - github.com/baidubce/bce-sdk-go v0.9.260 + 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.33 github.com/go-acme/alidns-20150109/v4 v4.7.0 - github.com/go-acme/esa-20240910/v2 v2.48.0 + 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.3.24 - github.com/go-acme/tencentedgdeone v1.3.38 + 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.5.0 + github.com/go-viper/mapstructure/v2 v2.4.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.187 + 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.65.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.72 + 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.2.0 + github.com/nrdcg/auroradns v1.1.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.108.2 - github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 + 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 @@ -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.48 + 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.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 + 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.267.0 - gopkg.in/ns1/ns1-go.v2 v2.17.2 + 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.7.0 ) require ( - cloud.google.com/go/auth v0.18.1 // 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 @@ -119,23 +119,22 @@ 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.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/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.17 // 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.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/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/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 @@ -161,8 +160,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.11 // indirect - github.com/googleapis/gax-go/v2 v2.17.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.7 // 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 @@ -206,23 +205,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.39.0 // indirect - go.opentelemetry.io/otel/metric v1.39.0 // indirect - go.opentelemetry.io/otel/trace v1.39.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.32.0 // indirect + golang.org/x/mod v0.30.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/tools v0.41.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-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // 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.1 // 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 f5b87c9fe..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.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs= -cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA= +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= @@ -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.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/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= @@ -121,10 +121,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.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= @@ -171,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.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 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.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/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/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.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/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.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.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/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/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.260 h1:1v1+2GTP+NGK3L24rJ+bnoiTaDaIy2YoaUM+ot2GTcw= -github.com/baidubce/bce-sdk-go v0.9.260/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= @@ -241,8 +239,6 @@ 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= @@ -319,14 +315,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.48.0 h1:muSDyhjDTejxUGe3FTthCPCqRaEdYY9cG3N/AmU52Lc= -github.com/go-acme/esa-20240910/v2 v2.48.0/go.mod h1:shPb6hzc1rJL15IJBY8HQ4GZk4E8RC52+52twutEwIg= +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.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-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= 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= @@ -370,8 +366,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.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= -github.com/go-viper/mapstructure/v2 v2.5.0/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= @@ -471,12 +467,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.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= -github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= +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.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= -github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= +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= @@ -541,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.187 h1:J+U6+eUjIsBhefolFdZW5hQNJbkMj+7msxZrv56Cg2g= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.187/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= @@ -616,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.65.0 h1:SdsuGD8VSsPWeShXpE7ihl5vec+fD3MgwhnfYC/rj7k= -github.com/linode/linodego v1.65.0/go.mod h1:tOFiTErdjkbVnV+4S0+NmIE9dqqZUEM2HsJaGu8wMh8= +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= @@ -653,8 +649,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.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= -github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= +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= @@ -695,8 +691,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.2.0 h1:Jg407vTdXZvZKsART9CNWMp8rQOyhBk04q0MsOf0YR4= -github.com/nrdcg/auroradns v1.2.0/go.mod h1:hnByA4Z7MOmV4EPRw5eOmEaNRFavcCIz6kONpNxp9LI= +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.1 h1:ilpKmCr4gGsLcyq3RHfHNmlRzm9fzT2XbWxoVaUCS0s= @@ -715,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.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/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= @@ -908,10 +904,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.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/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/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= @@ -926,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.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/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= @@ -938,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.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/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= @@ -973,16 +969,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.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 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.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +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= @@ -1035,8 +1031,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.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +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= @@ -1080,8 +1076,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.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +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= @@ -1139,16 +1135,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.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +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.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= -golang.org/x/oauth2 v0.35.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= @@ -1252,8 +1248,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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.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= @@ -1268,8 +1264,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.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +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= @@ -1288,8 +1284,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.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +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= @@ -1355,8 +1351,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.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +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= @@ -1385,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.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE= -google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0= +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= @@ -1425,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-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/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= @@ -1479,12 +1475,11 @@ 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.17.2 h1:x8YKHqCJWkC/hddfUhw7FRqTG0x3fr/0ZnWYN+i4THs= -gopkg.in/ns1/ns1-go.v2 v2.17.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/alidns/alidns.go b/providers/dns/alidns/alidns.go index cdd8e75e0..a5c883fcb 100644 --- a/providers/dns/alidns/alidns.go +++ b/providers/dns/alidns/alidns.go @@ -27,7 +27,6 @@ const ( EnvSecretKey = envNamespace + "SECRET_KEY" EnvSecurityToken = envNamespace + "SECURITY_TOKEN" EnvRegionID = envNamespace + "REGION_ID" - EnvLine = envNamespace + "LINE" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -46,7 +45,6 @@ type Config struct { SecretKey string SecurityToken string RegionID string - Line string PropagationTimeout time.Duration PollingInterval time.Duration TTL int @@ -76,7 +74,6 @@ 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 { @@ -257,18 +254,12 @@ func (d *DNSProvider) newTxtRecord(zone, fqdn, value string) (*alidns.AddDomainR return nil, err } - adrr := new(alidns.AddDomainRecordRequest). + return new(alidns.AddDomainRecordRequest). SetType("TXT"). SetDomainName(zone). SetRR(rr). SetValue(value). - SetTTL(int64(d.config.TTL)) - - if d.config.Line != "" { - adrr.SetLine(d.config.Line) - } - - return adrr, nil + SetTTL(int64(d.config.TTL)), 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 b78e1859d..9a93bd24f 100644 --- a/providers/dns/alidns/alidns.toml +++ b/providers/dns/alidns/alidns.toml @@ -23,8 +23,6 @@ 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)" diff --git a/providers/dns/allinkl/allinkl.go b/providers/dns/allinkl/allinkl.go index 376b0903c..4a0aadd2b 100644 --- a/providers/dns/allinkl/allinkl.go +++ b/providers/dns/allinkl/allinkl.go @@ -11,7 +11,6 @@ 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" @@ -122,6 +121,11 @@ 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) @@ -131,11 +135,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error { ctx = internal.WithContext(ctx, credential) - authZone, err := d.findZone(ctx, info.EffectiveFQDN) - if err != nil { - return fmt.Errorf("allinkl: %w", err) - } - subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone) if err != nil { return fmt.Errorf("allinkl: %w", err) @@ -193,17 +192,3 @@ 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) -} diff --git a/providers/dns/allinkl/allinkl_test.go b/providers/dns/allinkl/allinkl_test.go index 7da47aee4..b42adce5d 100644 --- a/providers/dns/allinkl/allinkl_test.go +++ b/providers/dns/allinkl/allinkl_test.go @@ -1,18 +1,9 @@ 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" ) @@ -152,108 +143,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.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 d4403cac5..d747e9b36 100644 --- a/providers/dns/allinkl/internal/client.go +++ b/providers/dns/allinkl/internal/client.go @@ -6,21 +6,16 @@ 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 defaultBaseURL = "https://kasapi.kasserver.com/soap/" - -const apiPath = "KasApi.php" +const apiEndpoint = "https://kasapi.kasserver.com/soap/KasApi.php" type Authentication interface { Authentication(ctx context.Context, sessionLifetime int, sessionUpdateLifetime bool) (string, error) @@ -33,21 +28,16 @@ type Client struct { floodTime time.Time muFloodTime sync.Mutex - maxElapsedTime time.Duration - - BaseURL *url.URL + baseURL string HTTPClient *http.Client } // NewClient creates a new Client. func NewClient(login string) *Client { - baseURL, _ := url.Parse(defaultBaseURL) - return &Client{ - login: login, - BaseURL: baseURL, - maxElapsedTime: 3 * time.Minute, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, + login: login, + baseURL: apiEndpoint, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, } } @@ -61,9 +51,14 @@ func (c *Client) GetDNSSettings(ctx context.Context, zone, recordID string) ([]R requestParams["record_id"] = recordID } - var g APIResponse[GetDNSSettingsResponse] + req, err := c.newRequest(ctx, "get_dns_settings", requestParams) + if err != nil { + return nil, err + } - err := c.doRequest(ctx, "get_dns_settings", requestParams, &g) + var g GetDNSSettingsAPIResponse + + err = c.do(req, &g) if err != nil { return nil, err } @@ -75,9 +70,14 @@ 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) { - var g APIResponse[AddDNSSettingsResponse] + req, err := c.newRequest(ctx, "add_dns_settings", record) + if err != nil { + return "", err + } - err := c.doRequest(ctx, "add_dns_settings", record, &g) + var g AddDNSSettingsAPIResponse + + err = c.do(req, &g) if err != nil { return "", err } @@ -91,9 +91,14 @@ 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} - var g APIResponse[DeleteDNSSettingsResponse] + req, err := c.newRequest(ctx, "delete_dns_settings", requestParams) + if err != nil { + return "", err + } - err := c.doRequest(ctx, "delete_dns_settings", requestParams, &g) + var g DeleteDNSSettingsAPIResponse + + err = c.do(req, &g) if err != nil { return "", err } @@ -119,9 +124,7 @@ func (c *Client) newRequest(ctx context.Context, action string, requestParams an payload := []byte(strings.TrimSpace(fmt.Sprintf(kasAPIEnvelope, body))) - endpoint := c.BaseURL.JoinPath(apiPath) - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), bytes.NewReader(payload)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL, bytes.NewReader(payload)) if err != nil { return nil, fmt.Errorf("unable to create request: %w", err) } @@ -129,21 +132,6 @@ 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)) @@ -151,40 +139,29 @@ func (c *Client) do(req *http.Request, result any) error { resp, err := c.HTTPClient.Do(req) if err != nil { - return backoff.Permanent(errutils.NewHTTPDoError(req, err)) + return errutils.NewHTTPDoError(req, err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { - return backoff.Permanent(errutils.NewUnexpectedResponseStatusCodeError(req, resp)) + return errutils.NewUnexpectedResponseStatusCodeError(req, resp) } envlp, err := decodeXML[KasAPIResponseEnvelope](resp.Body) if err != nil { - return backoff.Permanent(err) + return err } if envlp.Body.Fault != nil { - 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) + return envlp.Body.Fault } raw := getValue(envlp.Body.KasAPIResponse.Return) err = mapstructure.Decode(raw, result) if err != nil { - return backoff.Permanent(fmt.Errorf("response struct decode: %w", err)) + return 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 949f45bf9..4b111e31c 100644 --- a/providers/dns/allinkl/internal/client_test.go +++ b/providers/dns/allinkl/internal/client_test.go @@ -2,9 +2,7 @@ package internal import ( "net/http/httptest" - "net/url" "testing" - "time" "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" @@ -13,17 +11,15 @@ import ( func setupClient(server *httptest.Server) (*Client, error) { client := NewClient("user") - client.BaseURL, _ = url.Parse(server.URL) + client.baseURL = 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 /KasApi.php", servermock.ResponseFromFixture("get_dns_settings.xml"), + Route("POST /", servermock.ResponseFromFixture("get_dns_settings.xml"), servermock.CheckRequestBodyFromFixture("get_dns_settings-request.xml"). IgnoreWhitespace()). Build(t) @@ -100,24 +96,9 @@ 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 /KasApi.php", servermock.ResponseFromFixture("add_dns_settings.xml"), + Route("POST /", servermock.ResponseFromFixture("add_dns_settings.xml"), servermock.CheckRequestBodyFromFixture("add_dns_settings-request.xml"). IgnoreWhitespace()). Build(t) @@ -137,7 +118,7 @@ func TestClient_AddDNSSettings(t *testing.T) { func TestClient_DeleteDNSSettings(t *testing.T) { client := servermock.NewBuilder[*Client](setupClient). - Route("POST /KasApi.php", servermock.ResponseFromFixture("delete_dns_settings.xml"), + Route("POST /", 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 deleted file mode 100644 index 1cba86f10..000000000 --- a/providers/dns/allinkl/internal/fixtures/auth-request.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - {"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 deleted file mode 100644 index b8da10fab..000000000 --- a/providers/dns/allinkl/internal/fixtures/flood_protection.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - 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 deleted file mode 100644 index 478d07a3a..000000000 --- a/providers/dns/allinkl/internal/fixtures/get_dns_settings-zone_not_found.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - 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 deleted file mode 100644 index c77d733db..000000000 --- a/providers/dns/allinkl/internal/fixtures/get_dns_settings-zone_syntax_incorrect.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - 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 e95e78899..ba8d4d90e 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" ) -const authPath = "KasAuth.php" +// authEndpoint represents the Identity API endpoint to call. +const authEndpoint = "https://kasapi.kasserver.com/soap/KasAuth.php" type token string @@ -24,19 +24,17 @@ type Identifier struct { login string password string - BaseURL *url.URL - HTTPClient *http.Client + authEndpoint string + HTTPClient *http.Client } // NewIdentifier creates a new Identifier. func NewIdentifier(login, password string) *Identifier { - baseURL, _ := url.Parse(defaultBaseURL) - return &Identifier{ - login: login, - password: password, - BaseURL: baseURL, - HTTPClient: &http.Client{Timeout: 10 * time.Second}, + login: login, + password: password, + authEndpoint: authEndpoint, + HTTPClient: &http.Client{Timeout: 10 * time.Second}, } } @@ -64,9 +62,7 @@ func (c *Identifier) Authentication(ctx context.Context, sessionLifetime int, se payload := []byte(strings.TrimSpace(fmt.Sprintf(kasAuthEnvelope, body))) - endpoint := c.BaseURL.JoinPath(authPath) - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), bytes.NewReader(payload)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.authEndpoint, 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 41d092b13..7b93b7688 100644 --- a/providers/dns/allinkl/internal/identity_test.go +++ b/providers/dns/allinkl/internal/identity_test.go @@ -3,7 +3,6 @@ package internal import ( "context" "net/http/httptest" - "net/url" "testing" "github.com/go-acme/lego/v4/platform/tester/servermock" @@ -13,7 +12,7 @@ import ( func setupIdentifierClient(server *httptest.Server) (*Identifier, error) { client := NewIdentifier("user", "secret") - client.BaseURL, _ = url.Parse(server.URL) + client.authEndpoint = server.URL client.HTTPClient = server.Client() return client, nil @@ -27,13 +26,10 @@ func mockContext(t *testing.T) context.Context { func TestIdentifier_Authentication(t *testing.T) { client := servermock.NewBuilder[*Identifier](setupIdentifierClient). - Route("POST /KasAuth.php", - servermock.ResponseFromFixture("auth.xml"), - servermock.CheckRequestBodyFromFixture("auth-request.xml"). - IgnoreWhitespace()). + Route("POST /", servermock.ResponseFromFixture("auth.xml")). Build(t) - credentialToken, err := client.Authentication(t.Context(), 60, true) + credentialToken, err := client.Authentication(t.Context(), 60, false) require.NoError(t, err) assert.Equal(t, "593959ca04f0de9689b586c6a647d15d", credentialToken) @@ -41,7 +37,7 @@ func TestIdentifier_Authentication(t *testing.T) { func TestIdentifier_Authentication_error(t *testing.T) { client := servermock.NewBuilder[*Identifier](setupIdentifierClient). - Route("POST /KasAuth.php", servermock.ResponseFromFixture("auth_fault.xml")). + Route("POST /", 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 51f7065b5..b0aa9b4ff 100644 --- a/providers/dns/allinkl/internal/types.go +++ b/providers/dns/allinkl/internal/types.go @@ -26,11 +26,10 @@ 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: %s", f.Actor, f.Code, f.Message, f.Detail) +func (f Fault) Error() string { + return fmt.Sprintf("%s: %s: %s", f.Actor, f.Code, f.Message) } // KasResponse a KAS SOAP response. diff --git a/providers/dns/allinkl/internal/types_api.go b/providers/dns/allinkl/internal/types_api.go index a11f3aac0..22f2c32ed 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 APIResponse[T any] struct { - Response T `json:"Response" mapstructure:"Response"` +type GetDNSSettingsAPIResponse struct { + Response GetDNSSettingsResponse `json:"Response" mapstructure:"Response"` } type GetDNSSettingsResponse struct { @@ -73,12 +73,20 @@ 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"` diff --git a/providers/dns/artfiles/artfiles.go b/providers/dns/artfiles/artfiles.go deleted file mode 100644 index c918d77f6..000000000 --- a/providers/dns/artfiles/artfiles.go +++ /dev/null @@ -1,204 +0,0 @@ -// 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 deleted file mode 100644 index 00ff12342..000000000 --- a/providers/dns/artfiles/artfiles.toml +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index 42490f10d..000000000 --- a/providers/dns/artfiles/artfiles_test.go +++ /dev/null @@ -1,228 +0,0 @@ -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 deleted file mode 100644 index 61b350511..000000000 --- a/providers/dns/artfiles/internal/client.go +++ /dev/null @@ -1,133 +0,0 @@ -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 deleted file mode 100644 index cc76f06f5..000000000 --- a/providers/dns/artfiles/internal/client_test.go +++ /dev/null @@ -1,89 +0,0 @@ -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 deleted file mode 100644 index b8a1247d2..000000000 --- a/providers/dns/artfiles/internal/fixtures/domains.txt +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index fa672e0e1..000000000 --- a/providers/dns/artfiles/internal/fixtures/get_dns.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "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 deleted file mode 100644 index 7cacb33e5..000000000 --- a/providers/dns/artfiles/internal/fixtures/set_dns.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "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 deleted file mode 100644 index 461489c77..000000000 --- a/providers/dns/artfiles/internal/fixtures/txt_record-multiple.txt +++ /dev/null @@ -1,8 +0,0 @@ -_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 deleted file mode 100644 index 5a6259b14..000000000 --- a/providers/dns/artfiles/internal/fixtures/txt_record.txt +++ /dev/null @@ -1,7 +0,0 @@ -_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 deleted file mode 100644 index c70ab34da..000000000 --- a/providers/dns/artfiles/internal/types.go +++ /dev/null @@ -1,109 +0,0 @@ -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 deleted file mode 100644 index 3b219f39f..000000000 --- a/providers/dns/artfiles/internal/types_test.go +++ /dev/null @@ -1,183 +0,0 @@ -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/azure/azure.go b/providers/dns/azure/azure.go index 8bfc6cfe1..fd00bcbe2 100644 --- a/providers/dns/azure/azure.go +++ b/providers/dns/azure/azure.go @@ -8,7 +8,6 @@ import ( "io" "net/http" "net/url" - "strings" "time" "github.com/Azure/go-autorest/autorest" @@ -38,8 +37,6 @@ const ( EnvPollingInterval = envNamespace + "POLLING_INTERVAL" ) -const EnvLegoAzureBypassDeprecation = "LEGO_AZURE_BYPASS_DEPRECATION" - const defaultMetadataEndpoint = "http://169.254.169.254" var _ challenge.ProviderTimeout = (*DNSProvider)(nil) @@ -136,18 +133,6 @@ 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 c4fec4359..44fb81eef 100644 --- a/providers/dns/azure/azure_test.go +++ b/providers/dns/azure/azure_test.go @@ -14,7 +14,6 @@ import ( const envDomain = envNamespace + "DOMAIN" var envTest = tester.NewEnvTest( - EnvLegoAzureBypassDeprecation, EnvEnvironment, EnvClientID, EnvClientSecret, @@ -58,8 +57,6 @@ func TestNewDNSProvider(t *testing.T) { envTest.ClearEnv() - test.envVars[EnvLegoAzureBypassDeprecation] = "true" - envTest.Apply(test.envVars) p, err := NewDNSProvider() @@ -143,11 +140,6 @@ 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() diff --git a/providers/dns/binarylane/internal/types.go b/providers/dns/binarylane/internal/types.go index 06d4be5c0..987e5c356 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 { - msg := new(strings.Builder) + var msg strings.Builder - _, _ = fmt.Fprintf(msg, "%d: %s: %s: %s: %s", a.Status, a.Type, a.Title, a.Detail, a.Instance) + 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 { - _, _ = fmt.Fprintf(msg, ": %s: %s", s, strings.Join(values, ", ")) + msg.WriteString(fmt.Sprintf(": %s: %s", s, strings.Join(values, ", "))) } return msg.String() diff --git a/providers/dns/bluecatv2/bluecatv2.go b/providers/dns/bluecatv2/bluecatv2.go deleted file mode 100644 index 0efe99661..000000000 --- a/providers/dns/bluecatv2/bluecatv2.go +++ /dev/null @@ -1,249 +0,0 @@ -// 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 deleted file mode 100644 index 6ec3781c6..000000000 --- a/providers/dns/bluecatv2/bluecatv2.toml +++ /dev/null @@ -1,33 +0,0 @@ -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 deleted file mode 100644 index d852f0e18..000000000 --- a/providers/dns/bluecatv2/bluecatv2_test.go +++ /dev/null @@ -1,414 +0,0 @@ -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 deleted file mode 100644 index d3c801154..000000000 --- a/providers/dns/bluecatv2/internal/client.go +++ /dev/null @@ -1,221 +0,0 @@ -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 deleted file mode 100644 index 2559af66e..000000000 --- a/providers/dns/bluecatv2/internal/client_test.go +++ /dev/null @@ -1,208 +0,0 @@ -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 deleted file mode 100644 index 38ae2db6e..000000000 --- a/providers/dns/bluecatv2/internal/fixtures/deleteResourceRecord.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "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 deleted file mode 100644 index d3d2b8b5f..000000000 --- a/providers/dns/bluecatv2/internal/fixtures/error.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "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 deleted file mode 100644 index b1a4938ad..000000000 --- a/providers/dns/bluecatv2/internal/fixtures/getZoneDeployments.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "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 deleted file mode 100644 index e62048eb9..000000000 --- a/providers/dns/bluecatv2/internal/fixtures/postSession-request.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "username": "userA", - "password": "secret" -} diff --git a/providers/dns/bluecatv2/internal/fixtures/postSession.json b/providers/dns/bluecatv2/internal/fixtures/postSession.json deleted file mode 100644 index 4599ad0ad..000000000 --- a/providers/dns/bluecatv2/internal/fixtures/postSession.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "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 deleted file mode 100644 index 099573a84..000000000 --- a/providers/dns/bluecatv2/internal/fixtures/postZoneDeployment-request.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "QuickDeployment" -} diff --git a/providers/dns/bluecatv2/internal/fixtures/postZoneDeployment.json b/providers/dns/bluecatv2/internal/fixtures/postZoneDeployment.json deleted file mode 100644 index fd26781fb..000000000 --- a/providers/dns/bluecatv2/internal/fixtures/postZoneDeployment.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "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 deleted file mode 100644 index 2de733c71..000000000 --- a/providers/dns/bluecatv2/internal/fixtures/postZoneResourceRecord-request.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "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 deleted file mode 100644 index 78d028ee3..000000000 --- a/providers/dns/bluecatv2/internal/fixtures/postZoneResourceRecord.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "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 deleted file mode 100644 index b9f2dfa8f..000000000 --- a/providers/dns/bluecatv2/internal/fixtures/zones.json +++ /dev/null @@ -1,185 +0,0 @@ -{ - "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 deleted file mode 100644 index af9355ab2..000000000 --- a/providers/dns/bluecatv2/internal/identity.go +++ /dev/null @@ -1,60 +0,0 @@ -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 deleted file mode 100644 index 3a1c4d2a2..000000000 --- a/providers/dns/bluecatv2/internal/identity_test.go +++ /dev/null @@ -1,82 +0,0 @@ -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 deleted file mode 100644 index 8ed6f714b..000000000 --- a/providers/dns/bluecatv2/internal/predicates.go +++ /dev/null @@ -1,64 +0,0 @@ -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 deleted file mode 100644 index 6913e8729..000000000 --- a/providers/dns/bluecatv2/internal/predicates_test.go +++ /dev/null @@ -1,78 +0,0 @@ -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 deleted file mode 100644 index 562fd60b0..000000000 --- a/providers/dns/bluecatv2/internal/types.go +++ /dev/null @@ -1,122 +0,0 @@ -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/cloudflare/internal/types.go b/providers/dns/cloudflare/internal/types.go index 50a7bbbf9..4a7f9e031 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 { - msg := new(strings.Builder) + var msg strings.Builder for _, item := range e { - _, _ = fmt.Fprintf(msg, "%d: %s", item.Code, item.Message) + msg.WriteString(fmt.Sprintf("%d: %s", item.Code, item.Message)) for _, link := range item.ErrorChain { - _, _ = fmt.Fprintf(msg, "; %d: %s", link.Code, link.Message) + msg.WriteString(fmt.Sprintf("; %d: %s", link.Code, link.Message)) } } diff --git a/providers/dns/czechia/czechia.go b/providers/dns/czechia/czechia.go deleted file mode 100644 index 3ff397c35..000000000 --- a/providers/dns/czechia/czechia.go +++ /dev/null @@ -1,159 +0,0 @@ -// 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 deleted file mode 100644 index 2a66d2054..000000000 --- a/providers/dns/czechia/czechia.toml +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 7d9a2676c..000000000 --- a/providers/dns/czechia/czechia_test.go +++ /dev/null @@ -1,165 +0,0 @@ -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 deleted file mode 100644 index f3e0e462e..000000000 --- a/providers/dns/czechia/internal/client.go +++ /dev/null @@ -1,124 +0,0 @@ -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 deleted file mode 100644 index c6f1141c5..000000000 --- a/providers/dns/czechia/internal/client_test.go +++ /dev/null @@ -1,67 +0,0 @@ -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 deleted file mode 100644 index ed5830093..000000000 --- a/providers/dns/czechia/internal/fixtures/add_txt_record-request.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "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 deleted file mode 100644 index ed5830093..000000000 --- a/providers/dns/czechia/internal/fixtures/delete_txt_record-request.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "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 deleted file mode 100644 index f4a9bfef7..000000000 --- a/providers/dns/czechia/internal/types.go +++ /dev/null @@ -1,8 +0,0 @@ -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/ddnss/ddnss.go b/providers/dns/ddnss/ddnss.go deleted file mode 100644 index 381151c55..000000000 --- a/providers/dns/ddnss/ddnss.go +++ /dev/null @@ -1,130 +0,0 @@ -// 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 deleted file mode 100644 index 0d0a7132c..000000000 --- a/providers/dns/ddnss/ddnss.toml +++ /dev/null @@ -1,23 +0,0 @@ -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 deleted file mode 100644 index 5b1d7df58..000000000 --- a/providers/dns/ddnss/ddnss_test.go +++ /dev/null @@ -1,168 +0,0 @@ -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 deleted file mode 100644 index a0cf4b4a6..000000000 --- a/providers/dns/ddnss/internal/client.go +++ /dev/null @@ -1,137 +0,0 @@ -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 deleted file mode 100644 index 3faddded0..000000000 --- a/providers/dns/ddnss/internal/client_test.go +++ /dev/null @@ -1,56 +0,0 @@ -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 deleted file mode 100644 index f0599ad9a..000000000 --- a/providers/dns/ddnss/internal/fixtures/error.html +++ /dev/null @@ -1,12 +0,0 @@ - - - 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 deleted file mode 100644 index f51957334..000000000 --- a/providers/dns/ddnss/internal/fixtures/success.html +++ /dev/null @@ -1,8 +0,0 @@ - - - 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 deleted file mode 100644 index 37d41e076..000000000 --- a/providers/dns/ddnss/internal/types.go +++ /dev/null @@ -1,39 +0,0 @@ -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/dnsexit/dnsexit.go b/providers/dns/dnsexit/dnsexit.go deleted file mode 100644 index ce9373a50..000000000 --- a/providers/dns/dnsexit/dnsexit.go +++ /dev/null @@ -1,163 +0,0 @@ -// 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 deleted file mode 100644 index 0d5321835..000000000 --- a/providers/dns/dnsexit/dnsexit.toml +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 31fe61497..000000000 --- a/providers/dns/dnsexit/dnsexit_test.go +++ /dev/null @@ -1,165 +0,0 @@ -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 deleted file mode 100644 index 9b0164846..000000000 --- a/providers/dns/dnsexit/internal/client.go +++ /dev/null @@ -1,156 +0,0 @@ -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 deleted file mode 100644 index 26ea01203..000000000 --- a/providers/dns/dnsexit/internal/client_test.go +++ /dev/null @@ -1,111 +0,0 @@ -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 deleted file mode 100644 index 6e5e2b520..000000000 --- a/providers/dns/dnsexit/internal/fixtures/add_record-request.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "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 deleted file mode 100644 index dcfef9cdf..000000000 --- a/providers/dns/dnsexit/internal/fixtures/delete_record-request.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "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 deleted file mode 100644 index 9ba835895..000000000 --- a/providers/dns/dnsexit/internal/fixtures/error.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "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 deleted file mode 100644 index 3af47a936..000000000 --- a/providers/dns/dnsexit/internal/fixtures/success.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "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 deleted file mode 100644 index db254549f..000000000 --- a/providers/dns/dnsexit/internal/types.go +++ /dev/null @@ -1,41 +0,0 @@ -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 { - msg := new(strings.Builder) - - _, _ = fmt.Fprintf(msg, "%s (code=%d)", a.Message, a.Code) - - for _, detail := range a.Details { - _, _ = fmt.Fprintf(msg, ", %s", detail) - } - - return msg.String() -} diff --git a/providers/dns/eurodns/eurodns.go b/providers/dns/eurodns/eurodns.go deleted file mode 100644 index 21ff3c3a9..000000000 --- a/providers/dns/eurodns/eurodns.go +++ /dev/null @@ -1,197 +0,0 @@ -// 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 deleted file mode 100644 index 302b15d00..000000000 --- a/providers/dns/eurodns/eurodns.toml +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index abbb4717e..000000000 --- a/providers/dns/eurodns/eurodns_test.go +++ /dev/null @@ -1,215 +0,0 @@ -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 deleted file mode 100644 index 1ebf8d143..000000000 --- a/providers/dns/eurodns/internal/client.go +++ /dev/null @@ -1,199 +0,0 @@ -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 deleted file mode 100644 index 68d1fda84..000000000 --- a/providers/dns/eurodns/internal/client_test.go +++ /dev/null @@ -1,310 +0,0 @@ -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 deleted file mode 100644 index 82a334598..000000000 --- a/providers/dns/eurodns/internal/fixtures/error.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "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 deleted file mode 100644 index db8142357..000000000 --- a/providers/dns/eurodns/internal/fixtures/zone_add.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "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 deleted file mode 100644 index 64f8530c9..000000000 --- a/providers/dns/eurodns/internal/fixtures/zone_add_empty_forwards.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "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 deleted file mode 100644 index e07d42299..000000000 --- a/providers/dns/eurodns/internal/fixtures/zone_add_validate_ko.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "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 deleted file mode 100644 index ba0ddfefb..000000000 --- a/providers/dns/eurodns/internal/fixtures/zone_add_validate_ok.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "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 deleted file mode 100644 index ebbc8593e..000000000 --- a/providers/dns/eurodns/internal/fixtures/zone_get.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "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 deleted file mode 100644 index ebbc8593e..000000000 --- a/providers/dns/eurodns/internal/fixtures/zone_remove.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "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 deleted file mode 100644 index 891b02e14..000000000 --- a/providers/dns/eurodns/internal/types.go +++ /dev/null @@ -1,136 +0,0 @@ -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/excedo/excedo.go b/providers/dns/excedo/excedo.go deleted file mode 100644 index ae9128b94..000000000 --- a/providers/dns/excedo/excedo.go +++ /dev/null @@ -1,176 +0,0 @@ -// 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 deleted file mode 100644 index 9f9874c62..000000000 --- a/providers/dns/excedo/excedo.toml +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index f2350c035..000000000 --- a/providers/dns/excedo/excedo_test.go +++ /dev/null @@ -1,210 +0,0 @@ -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 deleted file mode 100644 index a5d8be88b..000000000 --- a/providers/dns/excedo/internal/client.go +++ /dev/null @@ -1,205 +0,0 @@ -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 deleted file mode 100644 index f4fd52c00..000000000 --- a/providers/dns/excedo/internal/client_test.go +++ /dev/null @@ -1,137 +0,0 @@ -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 deleted file mode 100644 index f1f7bf958..000000000 --- a/providers/dns/excedo/internal/fixtures/addrecord.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "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 deleted file mode 100644 index 5c2431b1c..000000000 --- a/providers/dns/excedo/internal/fixtures/deleterecord.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "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 deleted file mode 100644 index 5a24ec247..000000000 --- a/providers/dns/excedo/internal/fixtures/error.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "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 deleted file mode 100644 index 215a8abb2..000000000 --- a/providers/dns/excedo/internal/fixtures/getrecords.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "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 deleted file mode 100644 index 2defb9843..000000000 --- a/providers/dns/excedo/internal/fixtures/login.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "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 deleted file mode 100644 index 5c9ca119d..000000000 --- a/providers/dns/excedo/internal/identity.go +++ /dev/null @@ -1,75 +0,0 @@ -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 deleted file mode 100644 index 86b7eb9d8..000000000 --- a/providers/dns/excedo/internal/identity_test.go +++ /dev/null @@ -1,35 +0,0 @@ -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 deleted file mode 100644 index eb6ce8462..000000000 --- a/providers/dns/excedo/internal/types.go +++ /dev/null @@ -1,65 +0,0 @@ -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/gigahostno/internal/client_test.go b/providers/dns/gigahostno/internal/client_test.go index 8d1298947..aac65bceb 100644 --- a/providers/dns/gigahostno/internal/client_test.go +++ b/providers/dns/gigahostno/internal/client_test.go @@ -38,25 +38,55 @@ func TestClient_GetZones(t *testing.T) { expected := []Zone{ { - ID: "123", - Name: "example.com", - NameDisplay: "example.com", - Type: "NATIVE", - Active: "1", + 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", + 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", + 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, }, } diff --git a/providers/dns/gigahostno/internal/fixtures/zones.json b/providers/dns/gigahostno/internal/fixtures/zones.json index d45b0ac49..f4d927335 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": 1700000000, + "zone_updated": false, "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": 1700000000, + "zone_updated": false, "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": 1700000000, + "zone_updated": false, "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 e998dc084..cbb7b8b23 100644 --- a/providers/dns/gigahostno/internal/types.go +++ b/providers/dns/gigahostno/internal/types.go @@ -26,11 +26,21 @@ 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"` + 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 { diff --git a/providers/dns/godaddy/internal/types.go b/providers/dns/godaddy/internal/types.go index 3bd5c9560..c1e6d6638 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 { - msg := new(strings.Builder) + var msg strings.Builder - _, _ = fmt.Fprintf(msg, "%s: %s", a.Code, a.Message) + msg.WriteString(fmt.Sprintf("%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 872bc070f..cb6e99083 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 { - msg := new(strings.Builder) + var msg strings.Builder - _, _ = fmt.Fprintf(msg, "status: %s, error: %s", a.Status, a.ErrorMsg) + msg.WriteString(fmt.Sprintf("status: %s, error: %s", a.Status, a.ErrorMsg)) if a.Code != 0 { - _, _ = fmt.Fprintf(msg, ", code: %d", a.Code) + msg.WriteString(fmt.Sprintf(", code: %d", a.Code)) } if len(a.Context) != 0 { for k, v := range a.Context { - _, _ = fmt.Fprintf(msg, ", %s: %s", k, v) + msg.WriteString(fmt.Sprintf(", %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 2b38a8a8c..47e8a3f91 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 { - msg := new(strings.Builder) + var msg strings.Builder - _, _ = fmt.Fprintf(msg, "%s: %s", i.Code, i.Message) + msg.WriteString(fmt.Sprintf("%s: %s", i.Code, i.Message)) if i.Details.Announcement != "" { - _, _ = fmt.Fprintf(msg, ": %s", i.Details.Announcement) + msg.WriteString(fmt.Sprintf(": %s", i.Details.Announcement)) } for _, limit := range i.Details.Limits { - _, _ = fmt.Fprintf(msg, "limit: %s", limit.Name) + msg.WriteString(fmt.Sprintf("limit: %s", limit.Name)) } for _, field := range i.Details.Fields { - _, _ = fmt.Fprintf(msg, "field: %s: %s", field.Name, strings.Join(field.Messages, ", ")) + msg.WriteString(fmt.Sprintf("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 c1a02ff8c..534dfa5e5 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 { - msg := new(strings.Builder) + var msg strings.Builder - _, _ = fmt.Fprintf(msg, "%s: %s", a.CorrelationID, a.Message) + msg.WriteString(fmt.Sprintf("%s: %s", a.CorrelationID, a.Message)) for field, values := range a.Errors { - _, _ = fmt.Fprintf(msg, ": %s: %s", field, strings.Join(values, ", ")) + msg.WriteString(fmt.Sprintf(": %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 a4b5b564d..854fc4883 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 { - msg := new(strings.Builder) + var msg strings.Builder - _, _ = fmt.Fprintf(msg, "%d: %s", a.StatusCode, a.Message) + msg.WriteString(fmt.Sprintf("%d: %s", a.StatusCode, a.Message)) for k, v := range a.Errors { - _, _ = fmt.Fprintf(msg, " %s: %v", k, v) + msg.WriteString(fmt.Sprintf(" %s: %v", k, v)) } return msg.String() diff --git a/providers/dns/internal/useragent/useragent.go b/providers/dns/internal/useragent/useragent.go index 090c9109a..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.32.0" + 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. diff --git a/providers/dns/leaseweb/internal/client.go b/providers/dns/leaseweb/internal/client.go deleted file mode 100644 index 01619d49b..000000000 --- a/providers/dns/leaseweb/internal/client.go +++ /dev/null @@ -1,216 +0,0 @@ -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 deleted file mode 100644 index 5762aad4b..000000000 --- a/providers/dns/leaseweb/internal/client_test.go +++ /dev/null @@ -1,149 +0,0 @@ -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 deleted file mode 100644 index af53fcf04..000000000 --- a/providers/dns/leaseweb/internal/fixtures/createResourceRecordSet-request.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "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 deleted file mode 100644 index 8ca040d63..000000000 --- a/providers/dns/leaseweb/internal/fixtures/createResourceRecordSet.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "_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 deleted file mode 100644 index 1a980b6bb..000000000 --- a/providers/dns/leaseweb/internal/fixtures/error_400.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "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 deleted file mode 100644 index 47d8a311d..000000000 --- a/providers/dns/leaseweb/internal/fixtures/error_401.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index 1deaf5606..000000000 --- a/providers/dns/leaseweb/internal/fixtures/error_404.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "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 deleted file mode 100644 index fd48f60c6..000000000 --- a/providers/dns/leaseweb/internal/fixtures/getResourceRecordSet.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "_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 deleted file mode 100644 index abf3fb4c3..000000000 --- a/providers/dns/leaseweb/internal/fixtures/getResourceRecordSet2.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "_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 deleted file mode 100644 index e781958c8..000000000 --- a/providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet-request.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "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 deleted file mode 100644 index 0acc314de..000000000 --- a/providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet-request2.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "content": [ - "foo" - ], - "ttl": 3600 -} diff --git a/providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet.json b/providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet.json deleted file mode 100644 index 2b877982c..000000000 --- a/providers/dns/leaseweb/internal/fixtures/updateResourceRecordSet.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "_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 deleted file mode 100644 index 7a4547584..000000000 --- a/providers/dns/leaseweb/internal/types.go +++ /dev/null @@ -1,35 +0,0 @@ -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 deleted file mode 100644 index fafaf1c4d..000000000 --- a/providers/dns/leaseweb/leaseweb.go +++ /dev/null @@ -1,187 +0,0 @@ -// 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 deleted file mode 100644 index 2c3503291..000000000 --- a/providers/dns/leaseweb/leaseweb.toml +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index 0450cd2c2..000000000 --- a/providers/dns/leaseweb/leaseweb_test.go +++ /dev/null @@ -1,204 +0,0 @@ -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/liara/internal/client.go b/providers/dns/liara/internal/client.go index 95c39695b..93cdcf7c8 100644 --- a/providers/dns/liara/internal/client.go +++ b/providers/dns/liara/internal/client.go @@ -20,23 +20,17 @@ 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, teamID string) *Client { +func NewClient(hc *http.Client) *Client { baseURL, _ := url.Parse(defaultBaseURL) if hc == nil { hc = &http.Client{Timeout: 10 * time.Second} } - return &Client{ - httpClient: hc, - baseURL: baseURL, - teamID: teamID, - } + return &Client{httpClient: hc, baseURL: baseURL} } // GetRecords gets the records of a domain. @@ -44,7 +38,7 @@ func NewClient(hc *http.Client, teamID string) *Client { func (c *Client) GetRecords(ctx context.Context, domainName string) ([]Record, error) { endpoint := c.baseURL.JoinPath("api", "v1", "zones", domainName, "dns-records") - req, err := c.newJSONRequest(ctx, http.MethodGet, endpoint, nil) + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, fmt.Errorf("create request: %w", err) } @@ -79,7 +73,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 := c.newJSONRequest(ctx, http.MethodPost, endpoint, record) + req, err := newJSONRequest(ctx, http.MethodPost, endpoint, record) if err != nil { return nil, fmt.Errorf("create request: %w", err) } @@ -114,7 +108,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 := c.newJSONRequest(ctx, http.MethodGet, endpoint, nil) + req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil) if err != nil { return nil, fmt.Errorf("create request: %w", err) } @@ -149,7 +143,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 := c.newJSONRequest(ctx, http.MethodDelete, endpoint, nil) + req, err := newJSONRequest(ctx, http.MethodDelete, endpoint, nil) if err != nil { return fmt.Errorf("create request: %w", err) } @@ -168,14 +162,7 @@ func (c *Client) DeleteRecord(ctx context.Context, domainName, recordID string) return nil } -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() - } - +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/liara/internal/client_test.go b/providers/dns/liara/internal/client_test.go index b6d007046..57ac7e8b3 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(teamID string) *servermock.Builder[*Client] { +func mockBuilder() *servermock.Builder[*Client] { return servermock.NewBuilder[*Client]( func(server *httptest.Server) (*Client, error) { - client := NewClient(OAuthStaticAccessToken(server.Client(), apiKey), teamID) + client := NewClient(OAuthStaticAccessToken(server.Client(), apiKey)) client.baseURL, _ = url.Parse(server.URL) return client, nil @@ -26,7 +26,7 @@ func mockBuilder(teamID string) *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,47 +108,8 @@ 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)). @@ -159,7 +120,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)). @@ -170,7 +131,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 c7e403eed..b91b004cc 100644 --- a/providers/dns/liara/liara.go +++ b/providers/dns/liara/liara.go @@ -23,7 +23,6 @@ const ( envNamespace = "LIARA_" EnvAPIKey = envNamespace + "API_KEY" - EnvTeamID = envNamespace + "TEAM_ID" EnvTTL = envNamespace + "TTL" EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" @@ -40,9 +39,7 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil) // Config is used to configure the creation of the DNSProvider. type Config struct { - APIKey string - TeamID string - + APIKey string TTL int PropagationTimeout time.Duration PollingInterval time.Duration @@ -80,7 +77,6 @@ func NewDNSProvider() (*DNSProvider, error) { config := NewDefaultConfig() config.APIKey = values[EnvAPIKey] - config.TeamID = env.GetOrFile(EnvTeamID) return NewDNSProviderConfig(config) } @@ -116,7 +112,6 @@ 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 f471de04e..4ed53ec75 100644 --- a/providers/dns/liara/liara.toml +++ b/providers/dns/liara/liara.toml @@ -13,7 +13,6 @@ 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)" diff --git a/providers/dns/manual/manual.toml b/providers/dns/manual/manual.toml index fc47a8fae..aca67536d 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 -configuration directory at "./.lego/accounts". + 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 private keys -generated by lego and certificates obtained from the ACME -server. Making regular backups of this folder is ideal. + 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 diff --git a/providers/dns/mittwald/internal/types.go b/providers/dns/mittwald/internal/types.go index 86cdf065c..ce49cb820 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 { - msg := new(strings.Builder) + var msg strings.Builder - _, _ = fmt.Fprintf(msg, "%s: %s", a.Type, a.Message) + msg.WriteString(fmt.Sprintf("%s: %s", a.Type, a.Message)) if len(a.ValidationErrors) > 0 { for _, validationError := range a.ValidationErrors { - _, _ = fmt.Fprintf(msg, " [%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)) } } diff --git a/providers/dns/namesurfer/internal/client.go b/providers/dns/namesurfer/internal/client.go deleted file mode 100644 index e40a7988c..000000000 --- a/providers/dns/namesurfer/internal/client.go +++ /dev/null @@ -1,226 +0,0 @@ -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 deleted file mode 100644 index 9e8f917bc..000000000 --- a/providers/dns/namesurfer/internal/client_test.go +++ /dev/null @@ -1,158 +0,0 @@ -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 deleted file mode 100644 index 660109aae..000000000 --- a/providers/dns/namesurfer/internal/fixtures/addDNSRecord-request.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "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 deleted file mode 100644 index f41779e30..000000000 --- a/providers/dns/namesurfer/internal/fixtures/addDNSRecord.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "id": 1, - "result": true -} diff --git a/providers/dns/namesurfer/internal/fixtures/error.json b/providers/dns/namesurfer/internal/fixtures/error.json deleted file mode 100644 index 8ddf8df25..000000000 --- a/providers/dns/namesurfer/internal/fixtures/error.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "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 deleted file mode 100644 index 06689de7a..000000000 --- a/providers/dns/namesurfer/internal/fixtures/listZones-request.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "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 deleted file mode 100644 index 37fa2053b..000000000 --- a/providers/dns/namesurfer/internal/fixtures/listZones.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "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 deleted file mode 100644 index 4a88340e2..000000000 --- a/providers/dns/namesurfer/internal/fixtures/searchDNSHosts-request.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "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 deleted file mode 100644 index 822459148..000000000 --- a/providers/dns/namesurfer/internal/fixtures/searchDNSHosts.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "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 deleted file mode 100644 index 494de20c6..000000000 --- a/providers/dns/namesurfer/internal/fixtures/updateDNSHost-request.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "id": 1, - "method": "updateDNSHost", - "params": [ - "user", - "510e63288ac874c1d5ba313a9411591daa346e5621fb0153263adc278794e378", - "example.com", - "viewA", - { - "name": "_acme-challenge", - "type": "TXT", - "data": "ADw2sEd82DUgXcQ9hNBZThJs7zVJkR5v9JeSbAb9mZY", - "ttl": 300 - }, - { - "name": "", - "type": "", - "data": "", - "ttl": 0 - } - ] -} diff --git a/providers/dns/namesurfer/internal/fixtures/updateDNSHost.json b/providers/dns/namesurfer/internal/fixtures/updateDNSHost.json deleted file mode 100644 index f41779e30..000000000 --- a/providers/dns/namesurfer/internal/fixtures/updateDNSHost.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "id": 1, - "result": true -} diff --git a/providers/dns/namesurfer/internal/types.go b/providers/dns/namesurfer/internal/types.go deleted file mode 100644 index d364c1876..000000000 --- a/providers/dns/namesurfer/internal/types.go +++ /dev/null @@ -1,72 +0,0 @@ -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"` - Type string `json:"type"` - Data string `json:"data"` - TTL int `json:"ttl"` -} - -// 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 { - msg := new(strings.Builder) - - _, _ = fmt.Fprintf(msg, "code: %v", e.Code) - - if e.Filename != "" { - _, _ = fmt.Fprintf(msg, ", filename: %s", e.Filename) - } - - if e.LineNumber > 0 { - _, _ = fmt.Fprintf(msg, ", line: %d", e.LineNumber) - } - - if e.Message != "" { - _, _ = fmt.Fprintf(msg, ", message: %s", e.Message) - } - - if len(e.Detail) > 0 { - _, _ = fmt.Fprintf(msg, ", detail: %v", strings.Join(e.Detail, " ")) - } - - return msg.String() -} diff --git a/providers/dns/namesurfer/namesurfer.go b/providers/dns/namesurfer/namesurfer.go deleted file mode 100644 index 6b7f48402..000000000 --- a/providers/dns/namesurfer/namesurfer.go +++ /dev/null @@ -1,214 +0,0 @@ -// 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 deleted file mode 100644 index fd914ec0c..000000000 --- a/providers/dns/namesurfer/namesurfer.toml +++ /dev/null @@ -1,28 +0,0 @@ -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 deleted file mode 100644 index ce3aa37af..000000000 --- a/providers/dns/namesurfer/namesurfer_test.go +++ /dev/null @@ -1,174 +0,0 @@ -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/safedns/internal/client.go b/providers/dns/safedns/internal/client.go index 628618032..51b12e99d 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 ANS SafeDNS client. +// Client the UKFast SafeDNS client. type Client struct { authToken string diff --git a/providers/dns/safedns/safedns.go b/providers/dns/safedns/safedns.go index 154cfc5ee..be8ca4fe6 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 ANS SafeDNS. +// Package safedns implements a DNS provider for solving the DNS-01 challenge using UKFast SafeDNS. package safedns import ( @@ -75,7 +75,7 @@ func NewDNSProvider() (*DNSProvider, error) { return NewDNSProviderConfig(config) } -// NewDNSProviderConfig return a DNSProvider instance configured for ANS SafeDNS. +// NewDNSProviderConfig return a DNSProvider instance configured for UKFast 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 f387f2535..188db66a4 100644 --- a/providers/dns/safedns/safedns.toml +++ b/providers/dns/safedns/safedns.toml @@ -1,6 +1,6 @@ -Name = "ANS SafeDNS" +Name = "UKFast SafeDNS" Description = '''''' -URL = "https://www.ans.co.uk/" +URL = "https://www.ukfast.co.uk/dns-hosting.html" Code = "safedns" Since = "v4.6.0" diff --git a/providers/dns/timewebcloud/internal/types.go b/providers/dns/timewebcloud/internal/types.go index 80cdb2c70..81da4df5c 100644 --- a/providers/dns/timewebcloud/internal/types.go +++ b/providers/dns/timewebcloud/internal/types.go @@ -3,11 +3,9 @@ package internal import "fmt" type DNSRecord struct { - 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). + ID int `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Value string `json:"value,omitempty"` SubDomain string `json:"subdomain,omitempty"` } diff --git a/providers/dns/timewebcloud/timewebcloud.go b/providers/dns/timewebcloud/timewebcloud.go index a599566e3..d71beea4b 100644 --- a/providers/dns/timewebcloud/timewebcloud.go +++ b/providers/dns/timewebcloud/timewebcloud.go @@ -110,10 +110,15 @@ 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: dns01.UnFqdn(info.EffectiveFQDN), + SubDomain: subDomain, } response, err := d.client.CreateRecord(context.Background(), authZone, record) diff --git a/providers/dns/todaynic/internal/client.go b/providers/dns/todaynic/internal/client.go deleted file mode 100644 index 2c537f4a7..000000000 --- a/providers/dns/todaynic/internal/client.go +++ /dev/null @@ -1,141 +0,0 @@ -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 deleted file mode 100644 index 71ee7f8b7..000000000 --- a/providers/dns/todaynic/internal/client_test.go +++ /dev/null @@ -1,94 +0,0 @@ -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 deleted file mode 100644 index 27f34d71c..000000000 --- a/providers/dns/todaynic/internal/fixtures/add_record.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "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 deleted file mode 100644 index 3ea9c9310..000000000 --- a/providers/dns/todaynic/internal/fixtures/error.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "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 deleted file mode 100644 index 0a15c7da8..000000000 --- a/providers/dns/todaynic/internal/types.go +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index 3a3734033..000000000 --- a/providers/dns/todaynic/todaynic.go +++ /dev/null @@ -1,164 +0,0 @@ -// 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 deleted file mode 100644 index 16d55ccc0..000000000 --- a/providers/dns/todaynic/todaynic.toml +++ /dev/null @@ -1,25 +0,0 @@ -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 deleted file mode 100644 index c73bf6cc5..000000000 --- a/providers/dns/todaynic/todaynic_test.go +++ /dev/null @@ -1,207 +0,0 @@ -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 9c4bc9e61..7099a3f4c 100644 --- a/providers/dns/zz_gen_dns_providers.go +++ b/providers/dns/zz_gen_dns_providers.go @@ -13,7 +13,6 @@ 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" @@ -26,7 +25,6 @@ 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" @@ -43,14 +41,11 @@ 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" "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" @@ -68,8 +63,6 @@ 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/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" @@ -109,7 +102,6 @@ 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" @@ -130,7 +122,6 @@ 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" @@ -172,7 +163,6 @@ 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" @@ -216,8 +206,6 @@ 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": @@ -242,8 +230,6 @@ 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": @@ -276,10 +262,6 @@ 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": return derak.NewDNSProvider() case "desec": @@ -290,8 +272,6 @@ 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": @@ -326,10 +306,6 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return efficientip.NewDNSProvider() case "epik": return epik.NewDNSProvider() - case "eurodns": - return eurodns.NewDNSProvider() - case "excedo": - return excedo.NewDNSProvider() case "exec": return exec.NewDNSProvider() case "exoscale": @@ -408,8 +384,6 @@ 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": @@ -450,8 +424,6 @@ 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": @@ -534,8 +506,6 @@ 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":